七周内使用七种语言的Typo(?)(“组合”)-Haskell部分

时间:2018-10-14 05:30:12

标签: haskell

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),似乎我并不孤单。

2 个答案:

答案 0 :(得分:4)

我不相信以下是该问题的作者的想法,但您可以这样写lazy8th

lazy8th = (. lazy5th) . zipWith (+) . lazy3rd

这是如何工作的?让我们对其进行分解,但让我们从重申复合运算符的类型开始:

Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

用语言描述它,.运算符是一个函数,它接受一个从bc的函数,另一个函数是从a到{{1} },并通过将它们“粘合”到b上来构成它们,以产生从ba的组合函数。

第一步

那么c有哪种类型?

(. lazy5th)

Prelude> :t (. lazy5th) (. lazy5th) :: ([Int] -> c) -> Int -> c 函数具有声明的类型lazy5th,并且在Int -> [Int]类型的a -> b位置处“坐着”。换句话说,.必须为a,而Int必须为b

由于该函数仅部分应用,因此它仍在等待左边的函数,该函数的类型应为[Int]。我们刚刚了解到b -> cb,因此此部分应用的表达式仍在“等待”的函数必须具有类型[Int]。一旦提供了这样的功能,我们就会得到功能[Int] -> c,或者更确切地说,是将a -> c替换为aInt

第二步

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不同,所以我们将其称为cc'

[Int] -> c'适用于[c] -> [c]的唯一方法是,如果[Int] -> c'c,而Intc'。因此,到目前为止的类型带有有用的括号:

[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

因为(.)关联到右边(通常是关联的)。这是最终的无点样式定义。