在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
是列表?
答案 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 xs
,null
有签名null :: [f] -> Bool
,这意味着a ~ [f]
(此处~
表示类型相等)。我们还必须检查表达式n <= 0 || null xs
是否会产生Bool
(因为这是if
- then
- else
的条件。自{{ 1}}的类型为(||)
,这意味着(||) :: Bool -> Bool -> Bool
和n <= 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
表达式需要具有相同的类型,因此我们现在的类型else
与xs
相同。因此,即使不知道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
上致电tail
和xs
。
null :: [a] -> Bool
tail :: [a] -> [a]
两者的参数都是列表,因此Haskell可以推断,如果您呼叫null xs
或tail xs
,xs
的类型必须是[a]
。
答案 2 :(得分:-1)
没有。在尝试编译调用之前,Haskell不知道xs
是一个列表。
当您致电myDrop 3 someList
时,Haskell知道null someList
可以接受someList
,tail someList
可以在someList
上调用并返回列表。 Haskell(魔法)类型系统可以推断(在编译时)如何使用它已经知道的内容编译myDrop n xs
。
理论上,如果您制作了一些适用于tail
和null
的“非列表”内容,则可以调用myDrop 3 notList
并获得合理的结果。