Haskell无点编程
我试图理解Haskell中的无点编程,我对一些示例提出了疑问,因为我并不真正理解错误发生时给出的解释 1) 我有一个循环函数定义如下:Haskell无点编程,haskell,Haskell,我试图理解Haskell中的无点编程,我对一些示例提出了疑问,因为我并不真正理解错误发生时给出的解释 1) 我有一个循环函数定义如下: myCycle :: [a] -> [a] myCycle = foldr (++) [] . repeat 为什么myCycle=foldr(++)[]$repeat不起作用 2) 用2添加列表中的每个元素,然后用另一个列表添加 sum :: [Int] -> [Int] -> [Int] sum s = zipWith (+) . map
myCycle :: [a] -> [a]
myCycle = foldr (++) [] . repeat
为什么myCycle=foldr(++)[]$repeat
不起作用
2) 用2添加列表中的每个元素,然后用另一个列表添加
sum :: [Int] -> [Int] -> [Int]
sum s = zipWith (+) . map (+ 2) $ s
为什么函数与sum s=zipWith(+)$map(+2)s具有相同的结果,为什么sum l1 l2=zipWith(+)。map(+2)$l1$l2
不起作用myCycle=foldr(++)[]$repeat
相当于myCycle z=(foldr(++)[$repeat)z
(x$y)z
等于(x$y)z
(x.y)z
等于x(y z)
首先,让我们列出所有类型:
foldr :: (a -> b -> b) -> b -> [a] -> b
(++) :: [a] -> [a] -> [a]
[] :: [a]
repeat :: a -> [a]
(.) :: (b -> c) -> (a -> b) -> a -> c
($) :: (a -> b) -> a -> b
foldr (++) :: [a] -> [[a]] -> [a]
foldr (++) [] :: [[a]] -> [a]
现在,正如您所看到的,($)
根本不会更改类型。它的固定性确保了你可以用它来代替括号。让我们看看它们的区别:
($) (foldr (++) []) :: [[a]] -> [a]
(.) (foldr (++) []) :: (b -> [[a]]) -> b -> [a]
由于repeat
具有类型c->[c]
,因此它不适用于($)
。它确实适用于()
,因为c~[a]
工作正常
因此,请始终记住,($)
本身不会做任何事情。它只是改变了优先级/固定性。此外,如果您试图理解/理解无点代码,使用前缀符号而不是中缀有时也会有所帮助:
sum l1 l2 = zipWith (+) (map (+2) l1) l2
= zipWith (+) (map (+2) l1) $ l2
= ($) (zipWith (+) (map (+2) l1)) l2
-- get rid of both ($) and l2:
sum l1 = zipWith (+) (map (+2) l1)
= (zipWith (+)) ((map (+2)) l1)
= f (g l1) -- f = zipWith (+), g = map (+2)
= (f . g) l1
= (zipWith (+) . (map (+2)) l1 -- substitute f and g again
= zipWith (+) . (map (+2) $ l1
-- get rid of $ and l1:
sum = zipWith (+) . map (+2)
如果您检查GHCi中的签名,您将获得
Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
Prelude> :t ($)
($) :: (a -> b) -> a -> b
这表明点运算符对函数进行操作,而美元运算符只是普通函数应用程序的一个奇怪版本(它允许您将例如f(g(hx))
写入f$g$h$x
)
在您的mycycle
示例foldr(++)[/code>具有签名[[a]]->[a]
和重复具有a->[a]
。因此,当键入foldr(++)[]$repeat
Haskell尝试将函数签名a->[a]
与foldr
表达式的第一个参数(即[[a]]
)匹配,这是一个列表。这将失败并给出一个错误。点运算符实际上需要一个函数,并且一切正常
在第二个示例中,sum s=zipWith(+)。map(+2)$s
相当于sum=zipWith(+)。地图(+2)
。类型推断将zipWith(+)
视为返回一元函数的一元函数,并且能够将其与点运算符期望的参数匹配。因此,这里首先组合函数,然后将其应用于s
。在sum s=zipWith(+)$map(+2)s
中,没有组合,只有应用:首先map(+2)
应用于s
,然后zipWith(+)
应用于结果
无点编程的要点是使用更少的函数应用程序和更多的函数组合。在haskell中了解这些内容的最佳方法是根据它们的定义手动展开
(f . g) = \x -> f (g x)
f $ x = f x
因此,每当我们看到(f.g)
,我们都可以用\x->f(gx)
替换它。当我们看到fx
时,我们可以用fx
替换它。让我们看看这会把我们带到哪里
myCycle = foldr (++) [] . repeat
嗯,让我们扩展一下
的定义:
myCycle = \x -> foldr (++) [] (repeat x)
myCycle x = foldr (++) [] (repeat x)
亲爱的,这基本上就是我们想要它做的。连接重复x的列表
现在,让我们看看您是否完成了$
:
myCycle = foldr (++) [] $ repeat
这就变成了:
myCycle = foldr (++) [] repeat
这很好,但这毫无意义。foldr
的第三个参数应该是一个列表,但您给了它一个函数(repeat
)<代码>重复
绝对不是一个列表,所以这整件事有点傻
我们可以在这里尝试同样的方法:
sum s = zipWith (+) . map (+ 2) $ s
sum s = (zipWith (+) . map (+ 2)) s
sum s = zipWith (+) (map (+ 2) s) -- (f . g) x = f (g x)
看看另一个公式:
sum s = zipWith (+) $ map (+ 2) s
sum s = (zipWith (+)) (map (+ 2) s)
sum s = zipWith (+) (map (+ 2) s) -- redundant parentheses
而且…他们是一样的
让我们试试看最后一个做什么:
sum l1 l2 = zipWith (+) . map (+ 2) $ l1 $ l2
sum l1 l2 = zipWith (+) . map (+ 2) $ (l1 l2)
哎呀……您正在尝试执行l1l2
,或者像应用函数一样应用l1
。那没有任何意义<代码>l1
是一个列表,而不是一个函数。所以,在这里你已经明白了为什么这是胡说八道:)如果你想作弊,你可以随时使用它,但我也想了解它的作用。你能解释一下你是如何从zipWith(+)(map(+2)))中获得的吗x
。如果添加其他括号,您将看到f=zipWith(+)
和g=map(+2)
。