学习haskell语法

时间:2018-01-23 20:38:39

标签: haskell

foo :: [Int] -> [Int]

foo (elem : rest)
    | elem == 0 && null rest = [elem]
    | null rest = [elem - 1]
    | elem == 0 = 0:(foo rest)
    | otherwise = elem - 1 : head rest + length rest : tail rest

我正在研究Haskell。我很确定上面的示例函数是如何工作的。 我知道[Int] -> [Int]表示输入类型和返回类型。

究竟是什么(elem:休息)?它是否像for循环? 我知道|的工作方式与if类似,但我很困惑,因为语法看起来不一致。

请帮我理解这个功能。

2 个答案:

答案 0 :(得分:5)

看起来你需要一个教程,而不是堆栈溢出答案,但我会快速回答你的问题的答案:(elem: rest)

首先,我们正在研究模式匹配。模式匹配基于匹配值与其类型的构造函数将参数分解为组件部分。如果这令人困惑,那只是因为你之前没有使用它。有关更直接的示例,请设想自定义数据类型:

data Foo = Foo Int

Foo这里只是包裹Int,我们可以通过说:

来匹配它
bar :: Foo -> Int
bar (Foo x) = x

通过模式匹配Int的构造函数展开Foo,将Int分配给x

这与上面的代码有什么关系?好吧,Haskell列表有构造函数(:)。这用作运算符,因此您看到它使用了中缀而不是前缀(即3:something而不是: 3 something,但如果将其括在parens中,则可以执行后者:(:) 3 something as与所有运营商)。 (:)被称为“cons”运算符,它会在列表中添加一些内容。

如果我使用(:-:)而不是(:)重新编写列表,我可以写一下:

data MyList a = a :-: MyList
              | Null

这是一种递归定义的数据类型,因为每个元素都是Null,或者包含另一个MyList类型。这与列表相同:每个元素都定义为something : [moreSomethings][]

第一种情况是特殊的,并导致我们在(elem:rest)中所做的事情。我们通过(:)运算符上的模式匹配从尾部拆分列表的头部。对于列表lstelem = head lstrest = tail lst

此语法在任何地方都很常见,但您通常会将其视为(x:xs)而不是(elem:rest)。考虑map的转录:

map :: (a -> b) -> [a] -> [b]
map f (x:xs) = f x : map f xs

从字面上看,这意味着获取传递它的列表的第一个元素,将函数应用于它,并将“cons”传递给map f xs(将相同的函数映射到列表的其余部分)。

唯一缺少的是默认情况 - 当你到达列表的末尾会发生什么?我们之前提到过,列表可以是(x:xs)[],所以让我们的模式匹配[]

-- from above
map _ [] = []

现在,空列表上的任何函数的映射都只是一个空列表。这是必要的,因为map f (x:xs)将不匹配空列表,并且Haskell会抱怨您没有编写总函数,而是partial function

模式匹配是一种特殊的惯用Haskell结构,你应该经常使用它。例如,您可以使用:

解析Char个数字0-9
-- |This is analogous to @digitToInt@ in @Data.Char@, but will not
-- parse hex digits ([a-fA-F]).
myDigitToInt :: Char -> Int
myDigitToInt '0' = 0
myDigitToInt '1' = 1
myDigitToInt '2' = 2
myDigitToInt '3' = 3
myDigitToInt '4' = 4
myDigitToInt '5' = 5
myDigitToInt '6' = 6
myDigitToInt '7' = 7
myDigitToInt '8' = 8
myDigitToInt '9' = 9
myDigitToInt _   = error "Invalid digit"

您可以使用更多模式匹配重写上面非常奇怪的函数:

foo' :: [Int] -> [Int]
foo' []         = []  -- zero-element list
foo' (0:xs)     = 0 : foo' rest  -- n-element list starting with zero
foo' [x]        = [x-1]  -- one-element list
foo' (x1:x2:xs) = (x1-1) : x2 + length xs + 1 : xs  -- n-element list

答案 1 :(得分:2)

foo :: [Int] -> [Int]

foo (elem : rest)
    | elem == 0 && null rest = [elem]
    | null rest = [elem - 1]
    | elem == 0 = 0:(foo rest)
    | otherwise = elem - 1 : head rest + length rest : tail rest

这是两个重要概念的案例:模式匹配和递归。如上所述,我们通过说出输入的外观来“分解”组件。

foo :: [Int]->[Int]
foo (1:2:3:rest) = [rest]

在这种情况下,我们说“如果输入以1开头,后跟2,后跟3,然后休息,则执行此操作。

正如您现在所知,: or "cons"是一个运算符,它通过将参数放在具有相同类型的列表的前面来处理列表。

接下来就是递归。递归函数是一种不同的“循环”方式,就像我们在其他语言中所称的那样。我们有一个初始值,我们用它做一些事情,我们用更新的参数将结果应用于我们自己。

foo :: Int -> Int
foo 0       = 0
foo someInt = someInt + foo (someInt -1)

这里我们将输入与具有减少的输入值的相同函数的结果一起添加。当我们点击基础0时,我们将停止并返回之前添加0的结果,并将该值作为最终结果返回。

在您的示例中,我们只有一个递归选项,其余类似于“基本情况”,并将立即返回计算值作为最终结果

在这种情况下会发生递归魔法:elem == 0 = 0:(foo rest)

现在我们知道了破坏这个功能所需要知道的事情。

foo (elem : rest)    -- if we have some elem followed by some rest
    | elem == 0 && null rest = [elem]    -- We call this the basecase of the recursive function
    | null rest = [elem -1]    -- whatever the value of elem is, if the rest does not exist, we return a list containing the elem decreased by one
    | elem == 0 = 0:(foo rest) -- whatever the rest, if elem equals 0, we take a zero and we add it in the front of whatever list comes out if we use the rest as the input argument to our function.
    | otherwise = elem -1 : head rest + length rest : tail rest
      -- in any other case not mentioned do the following: 
      -- take the first element of the rest, add it together with the lenght of the list, put that element in the front of the remaining elements of the rest, decrease the elem by one and put it in the front of that list

^我希望这不难读懂