我still正在研究“七周内的七种语言”,但发现错别字或我不理解的内容。
其中一项挑战是:
编写一个函数,该函数接受一个自变量
开始x
并返回一个从x
开始具有第三个数字的惰性序列。然后,编写一个以y
开头的第五个数字的函数。 通过合成组合这些函数以返回每个第八个数字,从x+y
(强调我的)
这是我不确定的“组成”。根据本书的前几部分判断,编写是通过.
运算符进行的,并且从本书的开头定义为
使用一个函数的返回值作为另一个函数的输入的过程
鉴于该定义,我看不到组合如何实现此处描述的内容,因为每个所描述函数的输出为[Int]
,但它们的输入为Int
。我的实现(不使用合成)如下:
lazy3rd :: Int -> [Int]
lazy3rd x = x:(lazy3rd (x+3))
lazy5th :: Int -> [Int]
lazy5th x = x:(lazy5th (x+5))
lazy8th :: Int -> Int -> [Int]
lazy8th x y = zipWith (+) (lazy3rd x) (lazy5th y)
----
[in Prelude]
Prelude> take 9 (Day2.lazy8th 4 6)
[10,18,26,34,42,50,58,66,74]
看着here(搜索#82151
),似乎我并不孤单。
答案 0 :(得分:4)
我不相信以下是该问题的作者的想法,但您可以这样写lazy8th
:
lazy8th = (. lazy5th) . zipWith (+) . lazy3rd
这是如何工作的?让我们对其进行分解,但让我们从重申复合运算符的类型开始:
Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
用语言描述它,.
运算符是一个函数,它接受一个从b
到c
的函数,另一个函数是从a
到{{1} },并通过将它们“粘合”到b
上来构成它们,以产生从b
到a
的组合函数。
那么c
有哪种类型?
(. lazy5th)
Prelude> :t (. lazy5th)
(. lazy5th) :: ([Int] -> c) -> Int -> c
函数具有声明的类型lazy5th
,并且在Int -> [Int]
类型的a -> b
位置处“坐着”。换句话说,.
必须为a
,而Int
必须为b
。
由于该函数仅部分应用,因此它仍在等待左边的函数,该函数的类型应为[Int]
。我们刚刚了解到b -> c
是b
,因此此部分应用的表达式仍在“等待”的函数必须具有类型[Int]
。一旦提供了这样的功能,我们就会得到功能[Int] -> c
,或者更确切地说,是将a -> c
替换为a
,Int
。
Int -> c
有什么类型?
zipWith (+)
鉴于Prelude> :t zipWith (+)
zipWith (+) :: Num c => [c] -> [c] -> [c]
的定义,我希望这并不奇怪。
那么zipWith
是什么类型?
(. lazy5th) . zipWith (+)
我们如何到达那里?让我们回顾一下我们所知道的:
Prelude> :t (. lazy5th) . zipWith (+)
(. lazy5th) . zipWith (+) :: [Int] -> Int -> [Int]
Prelude> :t (. lazy5th)
(. lazy5th) :: ([Int] -> c) -> Int -> c
Prelude> :t zipWith (+)
zipWith (+) :: Num c => [c] -> [c] -> [c]
如何装入[c] -> [c] -> [c]
的模具中?一旦我们意识到,由于{curring},a -> b
也可以写为[c] -> [c] -> [c]
。换句话说,我们可以将此函数视为将单个列表[c] -> ([c] -> [c])
作为输入并返回函数[c]
作为输出的函数。因此,将[c] -> [c]
替换为a
,将[c]
替换为b
,这与[c] -> [c]
的类型相符。
(. lazy5th)
的输入类型为(. lazy5th)
。现在要小心,因为此[Int] -> c
与上一段中的c
不同,所以我们将其称为c
:c'
。
[Int] -> c'
适用于[c] -> [c]
的唯一方法是,如果[Int] -> c'
是c
,而Int
是c'
。因此,到目前为止的类型带有有用的括号:
[Int]
因此,这两个函数的组合是一个函数,该函数接受(. lazy5th) :: ([Int] -> [Int]) -> Int -> [Int]
zipWith (+) :: [Int] -> ([Int] -> [Int])
(zipWith (+)
)的输入,并将输出作为输入传递给[Int]
,从而生成{输入(. lazy5th)
。
Int -> [Int]
是什么类型?
lazy3rd
这是该函数的声明类型,因此在此不会感到惊讶。这与上一步如何组合?
同样,由于存在周期性变化,我们可以将Prelude> :t lazy3rd
lazy3rd :: Int -> [Int]
重写为[Int] -> Int -> [Int]
。现在我们有:
[Int] -> (Int -> [Int])
因此,这两个表达式的组合将输入(. lazy5th) . zipWith (+) :: [Int] -> (Int -> [Int])
lazy3rd :: Int -> [Int]
输入到lazy3rd
,并返回类型为Int
的函数:
(Int -> [Int])
或者,如果方括号有帮助,则可以将其写为:
Prelude> :t (. lazy5th) . zipWith (+) . lazy3rd
(. lazy5th) . zipWith (+) . lazy3rd :: Int -> Int -> [Int]
但是,这意味着相同。
那么可以吗?
(. lazy5th) . zipWith (+) . lazy3rd :: Int -> (Int -> [Int])
是的。
我如何提出这个解决方案?
老实说,我被骗了。我只是在pointfree.io中键入Prelude> lazy8th = (. lazy5th) . zipWith (+) . lazy3rd
Prelude> take 9 $ lazy8th 4 6
[10,18,26,34,42,50,58,66,74]
,它给了我结果。
但是,解释和解释是我自己的。
答案 1 :(得分:3)
我认为这本书只是写点技巧的一种实践。因此,即使您不知道@Will Ness建议的通用组合模式,也可以将任何表达式转换为无点样式。
请记住,如果参数x
位于函数定义和函数主体的末尾,则可以消除它,例如:
fun x = <some function body> $ x
可以改写为
fun = <some function body>
因此,转换的关键是将函数定义末尾的参数x
移动到定义主体的末尾,并且可以以<some function body> $ x
的形式编写,这很重要。
对于您来说,lazy8th x y = zipWith (+) (lazy3rd x) (lazy5th y)
很容易获得。现在,第一步是将y
移动到定义主体的末尾,因为y
在左侧的末尾。这一步很简单:
lazy8th x y = zipWith (+) (lazy3rd x) (lazy5th y)
=> lazy8th x y = zipWith (+) (lazy3rd x) . lazy5th $ y
使用它可以最后消除参数y
并得到:
lazy8th x = zipWith (+) (lazy3rd x) . lazy5th
接下来,我们需要删除x
,但是如何删除?它不像y
那样明显。
要解决此问题,我们需要记住Haskell的一项常见技能-部分,例如x + y
=> (+ y) x
。考虑到这一概念,我们可以按如下方式更改以前的结果:
lazy8th x = zipWith (+) (lazy3rd x) . lazy5th
=> lazy8th x = (. lazy5th) (zipWith (+) (lazy3rd x))
(.)
只是像(+)
这样的二进制运算符,还记得吗?
现在,由于参数x
位于定义主体的末尾,我们可以将其重写为:
lazy8th x = (. lazy5th) (zipWith (+) . lazy3rd $ x)
但是,x
不在末尾。这很容易实现。请记住,(. lazy5th) (zipWith (+) . lazy3rd $ x)
只是f1 (f2 $ x)
,可以轻松地转换成f1 . f2 $ x
。因此,最终结果是显而易见的:
lazy8th x = (. lazy5th) (zipWith (+) . lazy3rd $ x)
=> lazy8th x = (. lazy5th) . (zipWith (+) . lazy3rd) $ x
=> lazy8th = (. lazy5th) . (zipWith (+) . lazy3rd)
=> lazy8th = (. lazy5th) . zipWith (+) . lazy3rd
因为(.)
关联到右边(通常是关联的)。这是最终的无点样式定义。