【问题标题】:Haskell Function Composition - (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)Haskell 函数组合 - (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
【发布时间】:2016-07-06 07:02:21
【问题描述】:

我想了解如何在无点中完成以下操作:

withinBounds :: [Int] -> Bool
withinBounds xs = (all (>= 0) xs) && (all (<= 8) xs)

我知道为了可读性/理智起见以这种方式编写它会更好,但我想了解更多关于如何编写函数的信息。我一直在摸索如何做到这一点。 整个(扩展?)类型签名是

[Int] -> ([Int] -> Bool) -> ([Int] -> Bool) -> (Bool -> Bool -> Bool) -> Bool

我想要得到的组合的类型签名是

(a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)

我以混蛋-lambda 形式写了以下注释。如果有办法在某种程度上简化 lambda 演算的问题,如果也能解释一下就太好了:

\L@[] ->  \f1@([] -> Bool) -> \f2@([] -> Bool) -> \f3@(Bool -> Bool -> Bool) -> f3.(f1.L).(f2.L) 

在上面,. 是应用程序,@ 是捕获(所以 f3 是 (Bool -> Bool -> Bool) 的另一个名称)。 非常感谢。

编辑:我知道这不是最优化或可重用的代码,而且我知道把它变成无点会使它在可读性等方面变得更糟。为了澄清,我问我如何才能把它变成无点因为我想了解有关 haskell 和组合的更多信息

编辑2:A really good SO answer on point-free

【问题讨论】:

  • 您的withinBounds 不可组合,最好编写单个元素的检查,然后调用all。事实上,我可能只是内联 all withinBounds 其中 withinBounds 用于单个元素。
  • 谢谢。是否有迹象表明该功能不可组合? (例如,在正文中的多个位置都需要输入?)
  • @MIJOTHY:通常,如果类型更通用,它可能更可重用,例如withinBounds :: Ord e =&gt; (e, e) -&gt; e -&gt; Bool; withinBounds (a,b) x = a &lt;= x &amp;&amp; x &lt;= b。现在你原来的函数就是all (withinBounds (0,8))。此外,我可以将其用作过滤器的谓词:filter (withinBounds (4,100)) [1..103]
  • 是的,我可以理解通用 = 更可重用,但我想知道是否有某种方法可以判断函数是否可以用无点(使用组合运算符)编写。也许我对“可组合”的使用是错误的。所以用你写的更通用的版本来重新表述这个问题,我可以写withinBounds (a,b) x = a &lt;= x &amp;&amp; x &lt;= b in point-free吗?
  • @MIJOTHY:哦,对不起,我忘了告诉你。 Hackage 上有pointfree 包,#haskell 上有 lambdabot,pointfree.io 上有。话虽如此,我通常会移动参数,直到最终得到一个 eta 可约函数,例如stackoverflow.com/a/36542287/1139697.

标签: haskell lambda function-composition pointfree


【解决方案1】:

您可以使用函数是 Applicative 的事实。 然后这样写withinBounds

withinBounds = pure (&&) <*> all (>= 0) <*> all (<= 8)

或者这样:

withinBounds = (&&) <$> all (>= 0) <*> all (<= 8)

您可以阅读 Applicatives herehere

【讨论】:

  • 我的评论为什么被删除/为什么消失了?
  • 不知道:/昨天它有两个赞成票,一个来自我
【解决方案2】:

有一个类基本上专门用于具有多个“通道”的无点组合Arrow。如果您决心让一切变得毫无意义,那么这就是 IMO 的必经之路。丑陋的一点是你经常需要 uncurry 函数:

import Control.Arrow

withinBounds = all (>=0) &&& all (<=8) >>> uncurry (&&)

使用图表可以更好地理解其工作原理:

      all (>=0) ────
       ╱                ╲
──── &&&            >>>  uncurry (&&) ───
       ╲                ╱
      all (<=8) ──── 

Arrow 在通用环境中工作;不仅适用于 Hask 功能,还适用于任何合适的类别。但将其仅应用于函数就足够有用了。

【讨论】:

  • 谢谢,图表也很有帮助。我会读一些关于箭头的书。
【解决方案3】:

围绕整个问题做一个结束,我想我可能会这样写:

import Data.Ix
withinBounds = all (inRange (0, 8))

当然,这有点扯,从那以后自然会问如何以无点的方式实现inRange。如果你绝对不能使用inRange,那么我会以这种方式内联实现它:

withinBounds = all (liftA2 (&&) (>=0) (<=8))

这使用 reader applicative 为两个函数提供单个参数。 liftA2 是您请求的组合函数,但参数翻转:

requested :: (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
liftA2    :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)

【讨论】:

  • 哦,我明白了!因此,由于liftA2 f a b = fmap f a &lt;*&gt; bf &lt;$&gt; x = fmap f xliftA2 f a b = f &lt;$&gt; a &lt;*&gt; b 这很好地连接到@Safareli 的答案。
  • 又因为for ((->) r) fmap = (.),也可以写成withinBounds = (&amp;&amp;) . (all (&gt;= 0)) &lt;*&gt; (all (&lt;= 8))
【解决方案4】:

注意 x=0,所以,只使用前奏,我们可以写

withinBounds :: [Int] -> Bool
withinBounds = all $ (all (>=0)) . (zipWith (+) [0,8]) . (zipWith (*) [1,-1]) . (replicate 2)

基本上我只是将x 映射到[x,x],然后映射到[x,8-x],然后我要求这两者同时>=0。

当然,正如cmets中所指出的,你也可以制作a,b参数,以便以后复用。

希望这会有所帮助。

【讨论】:

  • 投反对票的人是否愿意解释投反对票的原因,以便我改进?事实上,这个技巧在其他类似的任务中也非常有用,并且不需要像其他答案那样的高级知识。
  • 好吧,OP 在问题正文中说可读性不是这里关心的问题:他只是想知道如何以一种毫无意义的方式编写它。另外,我添加了一个解释来完成这个函数的过程。也许我会解释更多?无论如何,感谢您的回复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-27
  • 1970-01-01
  • 1970-01-01
  • 2018-09-14
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多