以无点样式编写函数的一般方案是什么?

时间:2011-12-30 16:19:03

标签: haskell higher-order-functions combinators pointfree

此刻我正在研究20 Intermediate Haskell Exercises,这是一项非常有趣的练习。它涉及实现类型类FunctorMonad的各种实例(以及将FunctorMonad作为参数的函数),但使用可爱的名称,如FurryMisty来掩饰我们正在做的事情(制作一些有趣的代码)。

我一直试图以无点的方式做一些这样的事情,我想知道是否有一个将点 - 完全(?)定义转换为无点定义的一般方案。例如,以下是Misty的类型类:

class Misty m where
  unicorn :: a -> m a
  banana :: (a -> m b) -> m a -> m b

(函数unicornbananareturn>>=,如果不明显的话),这是apple的实现(相当于{ {1}}):

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 (相当于banana1liftM)我能够通过fmap的合适定义以无点样式实现。但是使用其他三个函数我必须使用参数。

我的问题是:是否有将这些定义转化为无点定义的方法

5 个答案:

答案 0 :(得分:11)

pointfree实用程序所示,可以自动执行任何此类转换。但是,结果经常被混淆而不是改进。如果一个人的目标是增强易读性而不是破坏它,那么第一个目标应该是确定为什么表达式具有特定结构,找到合适的抽象,并以这种方式构建。

最简单的结构就是在线性管道中将事物链接在一起,这是简单的函数组合。这让我们自己走得很远,但是你注意到它并没有处理所有事情。

一个概括是具有附加参数的函数,这些参数可以递增地构建。这是一个例子:定义onResult = (. (.))。现在,将onResult n次应用于初始值id,可以为函数组合提供n-ary函数的结果。因此我们可以定义comp2 = onResult (.),然后编写comp2 not (&&)来定义NAND操作。

另一个概括 - 实际上包含上述内容 - 是定义将函数应用于较大值的组件的运算符。这里的一个示例是first中的secondControl.Arrow,它们适用于2元组。 Conal Elliott的Semantic Editor Combinators基于这种方法。

稍微不同的情况是在某个类型b和函数a -> b上有多参数函数,并且需要使用{{1}将它们组合成多参数函数}。对于2-ary函数的常见情况,模块a提供Data.Function组合子,您可以使用它来编写像on这样的表达式来比较其第一个元素上的2元组。 / p>

当一个参数被多次使用时,这是一个棘手的问题,但这里也有一些有意义的重复模式也可以被提取出来。这里的一个常见情况是将多个函数应用于单个参数,然后使用另一个函数收集结果。这恰好与函数的compare `on` fst实例相对应,这使我们可以编写像Applicative这样的表达式来检查数字是否落在给定范围内。

重要的是,如果你想在实际代码中使用任何一个,那就要考虑表达式的意思以及它在结构中的反映方式。如果你这样做,然后使用有意义的组合器将它重构为无点样式,那么你通常会使代码更清晰的意图,而不是(&&) <$> (> 3) <*> (< 9)的典型输出。

答案 1 :(得分:5)

是的!其中一个技巧是用前缀表示法而不是中缀表示你的点。然后你应该能够找到看起来像功能组合的新东西。这是一个例子:

banana2 f = appleTurnover . banana1 f
          = (.) appleTurnover (banana1 f)
          = ((.) appleTurnOver) . banana1 $ f
banana2 = (appleTurnover .) . banana1

pointfree实用程序的源代码包含更多内容,但是这个代码处理了很多情况。

banana4 f x y = appleTurnover . banana3 f x y
              = (.) appleTurnover ((banana3 f x) y)
              = ((.) appleTurnover) . (banana3 f x) $ y
banana4 f x = ((.) appleTurnover) . (banana3 f x)
            = (.) ((.) appleTurnover) (banana3 f x)
            = ((.) ((.) appleTurnover)) ((banana3 f) x)
            = ((.) ((.) appleTurnover)) . (banana3 f) $ x
banana4 f = ((.) ((.) appleTurnover)) . (banana3 f)
          = (.) ((.) ((.) appleTurnover)) (banana3 f)
          = ((.) ((.) ((.) appleTurnover))) (banana3 f)
          = ((.) ((.) ((.) appleTurnover))) . banana3 $ f
banana4 = ((.) ((.) ((.) appleTurnover))) . banana3
        = (((appleTurnover .) .) .) . banana3

答案 2 :(得分:3)

我使用以下术语重写系统:

\x -> f x ------> f 
f y x ----------> flip f x y
\x -> f (g x) --> f . g

它是不完整的(阅读关于组合逻辑的书中的原因),但这已经足够了:

这是banana2:

banana2 f = appleTurnover . banana1 f

重写为lambda:

banana2 = \f -> appleTurnover . banana1 f

以前缀样式写入(。):

banana2 = \f -> (.) appleTurnover (banana1 f)

请注意

banana2 = \f -> ((.) appleTurnover) (banana1 f)

因此可以应用规则3。 f(.) appleTurnovergbanana

banana2 = ((.) appleTurnover) . banana1

答案 3 :(得分:2)

有一个pointfree包,它接受Haskell函数定义并尝试以pointfree样式重写它。我建议尝试用它来获得新的想法。有关详细信息,请参阅this page;该套餐可用here

答案 4 :(得分:0)

由于pointfree样式是组合器样式,只需应用已知的组合器定义,向后读取它们以进行替换:

B f g x = f (g x)     -- (.) , <$> for ((->) a)
C f x y = f y x       -- flip
K x y = x             -- const
I x = x               -- id
S f g x = f x (g x)   -- <*> , ap  for ((->) a)
W f x = f x x         -- join
(f >>= g) x = g (f x) x
(f =<< g) x = f (g x) x

有时liftMxliftAxsequencesequenceA可以简化事情。我还会将foldrunfoldriterateuntil等视为基本组合器。

通常,使用操作符部分也有帮助:

op a b = (a `op` b) = (`op` b) a = (a `op`) b

某些模式可能会变得熟悉,因此直接使用:

((f .) . g) x y = f (g x y)
((. f) . g) x y = g x (f y)

(((f .) .) . g) x y z = (f .) (g x y) z = f (g x y z)
(((. f) .) . g) x y z = (. f) (g x y) z = g x y (f z)