【问题标题】:How to combine binary and unary function to obtain the step function for a fold?如何结合二元和一元函数来获得折叠的阶跃函数?
【发布时间】:2019-12-23 20:20:45
【问题描述】:

在阅读Chapter 4 from Real World Haskell 时,我用以下几行解决了第 97 页的练习 1

asInt :: String -> Int
asInt ('-':x) = asInt x
asInt xs = foldl (\a x -> a*10 + digitToInt x) 0 xs

然后我从链接页面检查了一些cmets,并验证了这是大多数人采用的解决方案。

另一方面,我认为最好不要将函数编写为 lambda (\a x -> a*10 + digitToInt x),因为它非常冗长,并且会为参数(ax)命名,而实际上不需要给定一个,但作为其他函数的“组合”,即二元函数(*)(+)和一元函数digitToInt;但是我不知道如何将这三个组合成一个与上面的 lambda 等效的二进制函数。

我认为构成的成分是(*10),必须作用于foldl 的累加器的一元函数digitToInt,作用于列表元素xs 的一元函数,以及@ 987654334@,那得把这两者结合起来。

【问题讨论】:

  • pointfree.io应该是(.digitToInt).(+).(*10)
  • Pointfree 代码通常是“仅仅因为你可以并不意味着你应该”的情况。当您拥有一长串具有明显输入-输出管道的函数时,它最有用。但是,当您开始使用 (.) 运算符将内容放在正确的位置时,请记住,变量名的发明是有原因的。
  • @FrownyFrog,感谢您让我知道这个 Pointfree.io 网站!但是,如果您可以编写自己的答案来解释该函数组合的工作原理,那就太好了。
  • \a x -> a*10 + digitToInt x 不需要解释,但(.digitToInt).(+).(*10) 需要解释这一事实是关于使用哪个代码的大量提示。无点风格在很多情况下可以非常漂亮,但在其他情况下它很快退化为混淆,不愧为无点风格。区分两者对于生成高质量的代码非常重要。
  • 我明白你的意思,@chi;但是,我仍然渴望了解在这种情况下无意义风格的功能是如何工作的;不一定是因为我会使用它,因为我不能否认它混淆了函数的含义(至少在这种情况下),而是因为我认为我可以从中学到一些东西,而 lambda 只能教语法。

标签: haskell lambda functional-programming pointfree


【解决方案1】:

如果在调用foldl之前将每个数字转换为整数会更简单:

asInt xs = foldl (\a x -> a*10 + x) 0 (map digitToInt xs)
    -- == foldl ((+) . (10 *)) 0 (map digitToInt xs)

您可以进一步 eta 转换为

asInt = foldl ((+) . (10 *)) 0 . map digitToInt

(我相信,由于列表融合,不会创建由map 生成的中间列表。digitToInt 的每次调用的输出都会被foldl 立即使用,而不是放入列表。)

【讨论】:

  • 你关于列表融合的说法是否会根据使用foldlfoldl'而改变?
  • 我不这么认为,尽管我对列表融合的理解是不稳定的。假设foldl 确实受益于列表融合,我认为该项目来自foldl 的倒数第二个参数还是来自输入列表的第一个元素并不重要。
【解决方案2】:

(你想了解无意义的函数在这里是如何工作的,所以在这里。)

首先。

(\a x -> a*10 + digitToInt x)
= 
(\a x -> (+) ((*10) a) (digitToInt x))
= 
(curry $ (+) . (*10) . fst <*> digitToInt . snd)
=
(curry $ uncurry (+) . ((*10) *** digitToInt))

第二个。

(\a x -> a*10 + digitToInt x)
= 
(\a x -> (+) ((*10) a) (digitToInt x))
= 
(\a x -> ((+) . (*10)) a . digitToInt $ x)
= 
(\a   -> ((+) . (*10)) a . digitToInt    )
= 
(\a   -> (. digitToInt) ( ((+) . (*10)) a ) )
= 
(\a   -> (. digitToInt) . ((+) . (*10)) $ a )
= 
         (. digitToInt) .  (+) . (*10)

它是如何工作的

首先。

(curry $ (+) . (*10) . fst <*> digitToInt . snd)  a  x
= {-  curry f a b  =  f (a, b)                               -}
        ((+) . (*10) . fst <*> digitToInt . snd) (a, x)
= {-  (f <*> g) a  =  f a (g a)   ;   (f . g) a  =  f (g a)  -}
   ((+) . (*10)) (fst (a, x)) (digitToInt ( snd  (a, x)))
=
   ((+) . (*10))       a      (digitToInt            x  )
= {-  (f . g) a  =  f (g a)   ;   (`c` b) a  =  (a `c` b)    -}
    (+)   (a*10)              (digitToInt            x  )
= {-  (c) a b  =  (a `c` b)                                  -}
          (a*10)            +  digitToInt            x

和,

(curry $ uncurry (+) . ((*10) *** digitToInt))  a  x
= {-  curry f a b  =  f (a, b)                -}
        (uncurry (+) . ((*10) *** digitToInt)) (a, x)
= {-  (f *** g) a  =  (f $ fst a, g $ snd a)  -}
         uncurry (+) (  (*10) a , digitToInt       x )
= {-  uncurry f (a, b)  =  f a b              -}
                 (+) (  (*10) a) (digitToInt       x )
= {-  (`c` b) a  =  (a `c` b)                 -}
                 (+)   (a*10)    (digitToInt       x )
= {-  (c) a b  =  (a `c` b)                   -}
                       (a*10)  +  digitToInt       x

第二个。

           ((. digitToInt) . (+) . (*10)) a x
= {-  (f . g) a  =  f (g a)     -}
           ((. digitToInt) . (+)) ((*10) a) x
= {-  (`c` b) a  =  (a `c` b)   -}
           ((. digitToInt) . (+)) (a*10)    x
= {-  (f . g) a  =  f (g a)     -}
            (. digitToInt) ( (+)  (a*10) )  x
= {-  (`c` b) a  =  (a `c` b)   -}
 ((+) (a*10) . digitToInt)                  x
= {-  (f . g) a  =  f (g a)     -}
  (+) (a*10) ( digitToInt                   x )
= {-  (c) a b  =  (a `c` b)     -}
      (a*10) + digitToInt                   x  

另一种可能性是部分无点,

foldl  (\a -> (a*10 +) . digitToInt)  ...

它比完整的 lambda 更短,但比所有完全无点的版本更具可读性。

【讨论】:

  • 标有“第二”的两个块对我来说是一个答案,所以我会接受它;但是,我会添加一些文本,以使将来的读者更容易阅读答案,他们可以跳过提到 curryuncurry 的 cmets;此外,一些关于&lt;*&gt;*** 的单词/链接将不胜感激(我只看到* 用作乘法符号并由:kind 的输出显示具体类型。
  • 将审核您的编辑;请注意,它不是*,而是&lt;*&gt;——Applicative 类型类的“应用”方法。稍后还将在答案中添加hackage links。谢谢。
  • 嗨,我喜欢 lambda 周围的括号,我认为它们有助于清晰。这只是我的意见。 :) 所以我宁愿保留它们。
  • Applicative 和 Arrow 是 general 抽象类型类,但与函数一起使用时,它们的 &lt;*&gt;*** 仅此而已比我已经包含在答案中的定义(f &lt;*&gt; g) a = (f a) (g a)(f *** g) a = (f $ fst a, g $ snd a)。所以我只是将这两个运算符用作无点组合器。
猜你喜欢
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-22
  • 1970-01-01
  • 2018-04-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多