Haskell如何知道`xs`是函数定义中的列表?

时间:2018-03-03 14:40:20

标签: haskell functional-programming

book.realworldhaskell.org中,条件评估部分下的类型和函数部分,给出了以下示例:

 -- file: ch02/myDrop.hs
 myDrop n xs = if n <= 0 || null xs
               then xs
               else myDrop (n - 1) (tail xs)

我确实理解了该函数的实现,但我的问题是Haskell如何知道xs列表

3 个答案:

答案 0 :(得分:10)

类型推断

你写道:

myDrop n xs = if n <= 0 || null xs
              then xs
              else myDrop (n - 1) (tail xs)

因此,Haskell首先假设函数具有类型myDrop :: a -> (b -> c)(它首先不对类型做出任何假设)。所以在内存中我们存储:

myDrop :: a -> b -> c
n :: a
xs :: b

但现在它将开始派生类型。

我们看到例如n <= 0。现在,函数(<=)具有签名(<=) :: Ord d => d -> d -> Bool。这意味着0 :: d,我们现在对于数字文字,它就是0 :: Num e => e。因此,我们可以将Num a添加到类型约束中。

我们还看到null xsnull有签名null :: [f] -> Bool,这意味着a ~ [f](此处~表示类型相等)。我们还必须检查表达式n <= 0 || null xs是否会产生Bool(因为这是if - then - else的条件。自{{ 1}}的类型为(||),这意味着(||) :: Bool -> Bool -> Booln <= 0应该返回null xs s。这有效:因为Bool的类型为(<=) }和Ord d -> d -> Bool。所以在第一行的类型推断之后,我们有:

null :: [f] -> Bool

现在我们仍需要检查第二和第三行。在myDrop :: (Num a, Ord a) => a -> [f] -> c n :: (Num a, Ord a) => a xs :: [f] - if - then子句中,else表达式和then表达式需要具有相同的类型,因此我们现在的类型elsexs相同。因此,即使不知道myDrop (n-1) (tail xs)的签名,我们也知道它需要具有myDrop (n-1) (tail xs)类型(此处我们目前不知道myDrop :: g -> h -> [f]g的类型。

由于我们正在推导h的类型,我们可以检查我们到目前为止构造的类型,我们正在调用它,所以我们比较它:

myDrop

因此我们推导出:myDrop :: (Num a, Ord a) => a -> [f] -> c -- currently derived myDrop :: g -> h -> [f] -- called a ~ g。所以现在我们知道c ~ h ~ [f]有类型:

myDrop

我们现在仍然需要对这些论点进行类比检查。例如,我们看到调用中的第一个参数是myDrop :: (Num a, Ord a) => a -> [f] -> [f] n - 1的签名是(-),而(-) :: Num i => i -> i -> i是一个数字文字,所以1 ,因此我们在这个特定的上下文1 :: Num j => j中得出它,结果i ~ j ~ a因此保留了函数的派生类型。

我们也知道n - 1 :: a有签名tail。由于我们将其称为tail :: [k] -> [k],因此我们知道xs :: [f],因而f ~ k,这再次成立。我们不必进一步派生tail xs :: [f]a,因此我们可以将类型设置为:

f

改善功能

上述功能有效,无论我们提供什么输入,它都能正常工作。但是我认为它有点“不安全”,因为我们使用契约来处理契约(myDrop n xs :: (Num a, Ord a) => a -> [f] -> [f] tail)。例如,如果我们提供一个空列表,null将会出错。是的,这永远不会发生,因为tail会检查这一点。但我们必须自己推理。通常最好只使用 total 函数:总是返回有效输出的函数。

我们可以在函数的头部执行模式匹配。 Haskell编译器可以推导出我们缺少模式,因此如果我们打开该功能,那么我们就可以验证是否涵盖了所有情况。

我们可以把它写成:

null

所以这里第一行转换列表为空的情况(无论myDrop :: (Num a, Ord a) => a -> [f] -> [f] myDrop _ [] = [] myDrop n xa@(_:xs) | n <= 0 = xa | otherwise = myDrop (n-1) xs 是什么,我们返回一个空列表)。如果列表不为空,则它具有模式n(我们还保留对(_:xs)的引用,整个列表。如果xa,我们返回n <= 0否则我们递减xa,并在尾部进行递归调用。

答案 1 :(得分:8)

您正在null上致电tailxs

null :: [a] -> Bool
tail :: [a] -> [a]

两者的参数都是列表,因此Haskell可以推断,如果您呼叫null xstail xsxs的类型必须是[a]

答案 2 :(得分:-1)

没有。在尝试编译调用之前,Haskell不知道xs是一个列表。

当您致电myDrop 3 someList时,Haskell知道null someList可以接受someListtail someList可以在someList上调用并返回列表。 Haskell(魔法)类型系统可以推断(在编译时)如何使用它已经知道的内容编译myDrop n xs

理论上,如果您制作了一些适用于tailnull的“非列表”内容,则可以调用myDrop 3 notList并获得合理的结果。