简要说明:这是Miranda考试的过去考试问题,但语法与Haskell非常相似。
问题:以下表达式的类型是什么?它有什么作用? (定义 函数长度和交换如下所示。
(foldr (+) 0) . (foldr ((:) . length . (swap (:) [] )) [])
length [] = 0
length (x:xs) = 1 + length xs
swap f x y = f y x
注意:的
请随时回复haskell语法 - 抱歉使用星形作为多边形但我不想将它错误地翻译成haskell。基本上,如果一个变量的类型为*而另一个变量具有*,则表示它们可以是任何类型,但它们必须是同一类型。如果有**,则意味着它可以但不需要与*具有相同的类型。我认为它对应于haskell usuage中的a,b,c等。
到目前为止我的工作
从长度的定义中你可以看到它找到了任何列表的长度,所以这给出了
length :: [*] -> num.
从定义我认为swap接受一个函数和两个参数并产生交换过的两个参数的函数,所以这给出了
swap :: (* -> ** -> ***) -> ** -> [*] -> ***
foldr将二进制函数(如加号)作为起始值和列表,并使用该函数从右向左折叠列表。这给了
foldr :: (* -> ** -> **) -> ** -> [*] -> **)
我知道在函数组合中它是正确的关联,所以例如第一个点(。)右边的所有内容都需要生成一个列表,因为它将作为第一个折叠器的参数给出。
foldr函数输出一个值(折叠列表的结果),所以我知道返回类型将是某种多义类型而不是多类型列表。
我的问题
我不确定从哪里开始真的。我可以看到swap需要接受另一个参数,所以这个部分应用程序是否意味着整个事情是一个函数?我很困惑!
答案 0 :(得分:9)
你已经得到了答案,我会一步一步地写下推导,这样就可以很容易地看到所有这些:
xxf xs = foldr (+) 0 . foldr ((:) . length . flip (:) []) [] $ xs
== sum $ foldr ((:) . length . (: [])) [] xs
== sum $ foldr (\x -> (:) (length [x])) [] xs
== sum $ foldr (\x r -> length [x]:r) [] xs
== sum $ map (\x -> length [x]) xs
== sum [length [x] | x <- xs]
== sum [1 | x <- xs]
-- == length xs
xxf :: (Num n) => [a] -> n
那么,在米兰达,xxf xs = #xs
。我想它的类型是米兰达语法中的:: [*] -> num
。
Haskell的length
为:: [a] -> Int
,但此处定义为:: (Num n) => [a] -> n
,因为它使用Num
的{{1}}和两个文字,(+)
和0
。
如果您在查看1
时遇到问题,则只需
foldr
答案 1 :(得分:8)
让我们逐步完成。
length
函数显然具有您描述的类型;在哈斯克尔,Num n => [a] -> n
。等效的Haskell函数是length
(它使用Int
而不是任何Num n
。)
swap
函数接受一个函数来调用和反转它的前两个参数。你没有得到正确的签名;它是(a -> b -> c) -> b -> a -> c
。等效的Haskell函数是flip
。
foldr
函数具有您描述的类型;即(a -> b -> b) -> b -> [a] -> b
。等效的Haskell函数是foldr
。
现在,让我们看一下主表达式中每个子表达式的含义。
表达式swap (:) []
接受(:)
函数并交换其参数。 (:)
函数的类型为a -> [a] -> [a]
,因此swap
ping它会产生[a] -> a -> [a]
;因此,整个表达式具有类型a -> [a]
,因为交换的函数应用于[]
。结果函数的作用是它构造给定该项的一个项目的列表。
为简单起见,让我们将该部分提取到一个函数中:
singleton :: a -> [a]
singleton = swap (:) []
现在,下一个表达式为(:) . length . singleton
。 (:)
函数仍具有类型a -> [a] -> [a]
; (.)
函数的作用是它组成函数,因此如果你有一个函数foo :: a -> ...
和一个函数bar :: b -> a
,foo . bar
将具有类型b -> ...
。表达式(:) . length
因此具有类型Num n => [a] -> [n] -> [n]
(请记住,length
返回Num
),而表达式(:) . length . singleton
的类型为Num => a -> [n] -> [n]
。结果表达式的作用有点奇怪:给定a
类型的任何值和一些列表,它将忽略a
并将数字1
添加到该列表中。
为简单起见,让我们从中做出一个功能:
constPrependOne :: Num n => a -> [n] -> [n]
constPrependOne = (:) . length . singleton
您应该已经熟悉foldr
了。它使用函数在列表上执行右侧折叠。在这种情况下,它会在每个元素上调用constPrependOne
,因此表达式foldr constPrependOne []
只构造一个与输入列表长度相等的列表。因此,让我们做一个功能:
listOfOnesWithSameLength :: Num n => [a] -> [n]
listOfOnesWithSameLength = foldr constPrependOne []
如果您有一个列表[2, 4, 7, 2, 5]
,那么在应用[1, 1, 1, 1, 1]
时您将获得listOfOnesWithSameLength
。
然后,foldr (+) 0
功能是另一个右侧折叠。它相当于Haskell中的sum
函数;它总结了列表的元素。
所以,让我们发挥作用:
sum :: Num n => [n] -> n
sum = foldr (+) 0
如果您现在撰写功能:
func = sum . listOfOnesWithSameLength
...你得到了结果表达式。给定一些列表,它创建一个等长的列表,只包含一个列表,然后对该列表的元素求和。换句话说,它的行为与length
完全相同,只使用速度慢得多的算法。所以,最终的功能是:
inefficientLength :: Num n => [a] -> n
inefficientLength = sum . listOfOnesWithSameLength