Haskell无点编程

Haskell无点编程,haskell,Haskell,我试图理解Haskell中的无点编程,我对一些示例提出了疑问,因为我并不真正理解错误发生时给出的解释 1) 我有一个循环函数定义如下: myCycle :: [a] -> [a] myCycle = foldr (++) [] . repeat 为什么myCycle=foldr(++)[]$repeat不起作用 2) 用2添加列表中的每个元素,然后用另一个列表添加 sum :: [Int] -> [Int] -> [Int] sum s = zipWith (+) . map

我试图理解Haskell中的无点编程,我对一些示例提出了疑问,因为我并不真正理解错误发生时给出的解释

1) 我有一个循环函数定义如下:

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)