以 pointfree 风格编写函数的一般方案是什么?
我现在正在做20个中级Haskell练习,这是一个非常有趣的练习。它涉及实现类型类 Functor
和 Monad
的各种实例(以及将 Functor
和 Monad
作为参数的函数)参数),但使用像 Furry
和 Misty
这样可爱的名字来掩盖我们正在做的事情(产生一些有趣的代码)。
我一直在尝试以无点风格来做其中的一些事情,我想知道是否有一个通用方案可以将有点(?)定义转变为无点定义。例如,以下是 Misty
的类型类:(
class Misty m where
unicorn :: a -> m a
banana :: (a -> m b) -> m a -> m b
函数 unicorn
和 banana
分别是 return
和 >>=
,以防不明显)这是我对 apple
的实现(相当于 flip ap
):
apple :: (Misty m) => m a -> m (a -> b) -> m b
apple x f = banana (\g -> banana (unicorn . g) x) f
练习的后面部分让你实施版本liftM
、liftM2
等。这是我的解决方案:
appleTurnover :: (Misty m) => m (a -> b) -> m a -> m b
appleTurnover = flip apple
banana1 :: (Misty m) => (a -> b) -> m a -> m b
banana1 = appleTurnover . unicorn
banana2 :: (Misty m) => (a -> b -> c) -> m a -> m b -> m c
banana2 f = appleTurnover . banana1 f
banana3 :: (Misty m) => (a -> b -> c -> d) -> m a -> m b -> m c -> m d
banana3 f x = appleTurnover . banana2 f x
banana4 :: (Misty m) => (a -> b -> c -> d -> e) -> m a -> m b -> m c -> m d -> m e
banana4 f x y = appleTurnover . banana3 f x y
现在,banana1
(相当于 liftM
或 fmap
)我能够通过 appleTurnover
的适当定义以 pointfree 风格实现。但对于其他三个函数,我必须使用参数。
我的问题是:是否有办法将此类定义转变为无点定义?
I am working through the 20 Intermediate Haskell Exercises at the moment, which is quite a fun exercise. It involves implementing various instances of the typeclasses Functor
and Monad
(and functions that takes Functor
s and Monad
s as arguments) but with cute names like Furry
and Misty
to disguise what we're doing (makes for some interesting code).
I've been trying to do some of this in a point-free style, and I wondered if there's a general scheme for turning a point-ful (?) definition into a point-free definition. For example, here is the typeclass for Misty
:
class Misty m where
unicorn :: a -> m a
banana :: (a -> m b) -> m a -> m b
(the functions unicorn
and banana
are return
and >>=
, in case it's not obvious) and here's my implementation of apple
(equivalent to flip ap
):
apple :: (Misty m) => m a -> m (a -> b) -> m b
apple x f = banana (\g -> banana (unicorn . g) x) f
Later parts of the exercises have you implement versions of liftM
, liftM2
etc. Here are my solutions:
appleTurnover :: (Misty m) => m (a -> b) -> m a -> m b
appleTurnover = flip apple
banana1 :: (Misty m) => (a -> b) -> m a -> m b
banana1 = appleTurnover . unicorn
banana2 :: (Misty m) => (a -> b -> c) -> m a -> m b -> m c
banana2 f = appleTurnover . banana1 f
banana3 :: (Misty m) => (a -> b -> c -> d) -> m a -> m b -> m c -> m d
banana3 f x = appleTurnover . banana2 f x
banana4 :: (Misty m) => (a -> b -> c -> d -> e) -> m a -> m b -> m c -> m d -> m e
banana4 f x y = appleTurnover . banana3 f x y
Now, banana1
(equivalent to liftM
or fmap
) I was able to implement in pointfree style, by a suitable definition of appleTurnover
. But with the other three functions I've had to use parameters.
My question is: is there a recipe for turning definitions like these into point-free definitions?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
正如
pointfree
实用程序所演示的,可以自动执行任何此类转换。然而,结果往往是混乱的而不是改进的。如果一个人的目标是增强易读性而不是破坏它,那么第一个目标应该是确定为什么一个表达式具有特定的结构,找到一个合适的抽象,并以这种方式构建事物。最简单的结构是将事物简单地链接在线性管道中,这是简单的函数组合。这本身就让我们走得很远,但正如你所注意到的,它并不能处理所有事情。
一种概括是带有附加参数的函数,这些参数可以逐步构建。下面是一个示例:定义
onResult = (.(.))
。现在,将onResult
应用到id
初始值 n 次,即可获得具有 n 元函数结果的函数组合。所以我们可以定义comp2 = onResult(.)
,然后写comp2 not (&&)
来定义一个NAND操作。另一个概括(实际上包含上述内容)是定义将函数应用于较大值的组件的运算符。这里的一个例子是
Control.Arrow
中的first
和second
,它们适用于 2 元组。 Conal Elliott 的语义编辑器组合器基于这种方法。稍微不同的情况是,当您在某种类型
b
上有一个多参数函数,并且有一个函数a ->; b
,并且需要使用a
将它们组合成一个多参数函数。对于二元函数的常见情况,模块Data.Function
提供了on
组合器,您可以使用它来编写诸如compare `on` fst 之类的表达式
比较 2 元组的第一个元素。当单个参数被多次使用时,这是一个更棘手的问题,但这里也可以提取有意义的重复模式。这里的常见情况是将多个函数应用于单个参数,然后使用另一个函数收集结果。这恰好对应于函数的
Applicative
实例,它允许我们编写像(&&) <$> 这样的表达式。 (>3)<*> (<9)
检查数字是否在给定范围内。如果您想在实际代码中使用其中任何一个,重要的是要考虑表达式的含义以及它如何在结构中反映。如果您这样做,然后使用有意义的组合器将其重构为 pointfree 样式,您通常会使代码的意图比其他方式更清晰,这与
pointfree< 的典型输出不同/代码>。
As demonstrated by the
pointfree
utility, it's possible to do any such conversion automatically. However, the result is more often obfuscated than improved. If one's goal is to enhance legibility rather than destroy it, then the first goal should be to identify why an expression has a particular structure, find a suitable abstraction, and build things up that way.The simplest structure is simply chaining things together in a linear pipeline, which is plain function composition. This gets us pretty far just on its own, but as you noticed it doesn't handle everything.
One generalization is to functions with additional arguments, which can be built up incrementally. Here's one example: Define
onResult = (. (.))
. Now, applyingonResult
n times to an initial value ofid
gives you function composition with the result of an n-ary function. So we can definecomp2 = onResult (.)
, and then writecomp2 not (&&)
to define a NAND operation.Another generalization--which encompasses the above, really--is to define operators that apply a function to a component of a larger value. An example here would be
first
andsecond
inControl.Arrow
, which work on 2-tuples. Conal Elliott's Semantic Editor Combinators are based on this approach.A slightly different case is when you have a multi-argument function on some type
b
, and a functiona -> b
, and need to combine them into a multi-argument function usinga
. For the common case of 2-ary functions, the moduleData.Function
provides theon
combinator, which you can use to write expressions likecompare `on` fst
to compare 2-tuples on their first elements.It's a trickier issue when a single argument is used more than once, but there are meaningful recurring patterns here that can also be extracted. A common case here is applying multiple functions to a single argument, then collecting the results with another function. This happens to correspond to the
Applicative
instance for functions, which lets us write expressions like(&&) <$> (> 3) <*> (< 9)
to check if a number falls in a given range.The important thing, if you want to use any of this in actual code, is to think about what the expression means and how that's reflected in the structure. If you do that, and then refactor it into pointfree style using meaningful combinators, you'll often make the intent of the code clearer than it would otherwise be, unlike the typical output of
pointfree
.是的!技巧之一是用前缀表示法而不是中缀来写点。然后你应该能够找到看起来像函数组合的新东西。下面是一个示例:
pointfree 实用程序的源代码包含更多内容,但这个可以处理很多情况。
Yes! One of the tricks is to write your dots in prefix notation rather than infix. Then you should be able to find new things that look like function composition. Here's an example:
The source code for the pointfree utility contains more, but this one handles a lot of cases.
我使用以下术语重写系统:
它不完整(请阅读有关组合逻辑的书籍中的原因),但它已经足够了:
这是banana2:
重写为lambda:
以前缀样式写入(.):
请注意,
因此可以应用规则3 。
f
是(.) appleTurnover
,g
是banana
:I use the following term rewrite system:
It is incomplete (read why in books about combinatory logic), but it's enough:
Here is banana2:
Rewrite as a lambda:
Write (.) in prefix style:
Note that
So rule 3 can be applied.
f
is(.) appleTurnover
andg
isbanana
:有一个
pointfree
包,它采用 Haskell 函数定义并尝试以pointfree
风格重写它。我建议尝试一下以获得新想法。请参阅此页面了解更多详细信息;该软件包位于此处。There is a
pointfree
package which takes a Haskell function definition and attempts to re-write it in apointfree
style. I'd suggest experimenting with it to get new ideas. See this page for more details; the package is available here.由于 pointfree 风格是组合器风格,只需应用已知的组合器定义,向后读取它们即可进行替换:
有时
liftMx
、liftAx
、sequence
,sequenceA
可以简化事情。我还会将foldr
、unfoldr
、iterate
、until
等视为基本组合器。通常,使用运算符部分也有帮助:
某些模式可能会变得熟悉,因此可以直接使用:
等等。
Since pointfree style is combinators style, just apply known combinators definitions, reading them backwards to make the substitution:
At times
liftMx
,liftAx
,sequence
,sequenceA
can simplify things. I'd also considerfoldr
,unfoldr
,iterate
,until
etc. as basic combinators.Often, using operator sections helps too:
Some patterns can become familiar and so, used directly:
etc.