【问题标题】:What is a general scheme for writing a function in pointfree style?以无点风格编写函数的一般方案是什么?
【发布时间】:2011-12-30 16:19:03
【问题描述】:

我目前正在处理20 Intermediate Haskell Exercises,这是一个非常有趣的练习。它涉及实现类型类FunctorMonad 的各种实例(以及以Functors 和Monads 作为参数的函数),但使用FurryMisty 之类的可爱名称来伪装我们是什么做(产生一些有趣的代码)。

我一直在尝试以无点的方式来做一些这样的事情,我想知道是否有一个通用的方案可以将有点 (?) 定义转换为无点定义。例如,这是Misty 的类型类:

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

(函数unicornbananareturn>>=,以防不明显)这是我对apple的实现(相当于flip ap):

apple :: (Misty m) => m a -> m (a -> b) -> m b
apple x f = banana (\g -> banana (unicorn . g) x) f

练习的后面部分让你实现liftMliftM2 等版本。这是我的解决方案:

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(相当于liftMfmap)我能够通过appleTurnover 的适当定义以无点样式实现。但是对于其他三个函数,我不得不使用参数。

我的问题是:有没有办法将这些定义变成无点定义

【问题讨论】:

标签: haskell higher-order-functions combinators pointfree


【解决方案1】:

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

最简单的结构是简单地将事物链接在一个线性管道中,这是简单的函数组合。这让我们靠自己走得很远,但正如您所注意到的,它并不能处理所有事情。

一种概括是具有附加参数的函数,可以增量构建。这是一个示例:定义onResult = (. (.))。现在,将onResult n 次应用于id 的初始值会为您提供具有n 元函数结果的函数组合。所以我们可以定义comp2 = onResult (.),然后写comp2 not (&&)来定义一个NAND操作。

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

稍微不同的情况是,当您在某些类型 b 和函数 a -> b 上有一个多参数函数时,需要使用 a 将它们组合成一个多参数函数。对于 2 元函数的常见情况,模块 Data.Function 提供了 on 组合器,您可以使用它来编写像 compare `on` fst 这样的表达式来比较 2 元组的第一个元素。

当一个参数被多次使用时,这是一个更棘手的问题,但这里也有可以提取的有意义的重复模式。这里的一个常见情况是将多个函数应用于单个参数,然后使用另一个函数收集结果。这恰好对应于函数的 Applicative 实例,这让我们可以编写像 (&&) <$> (> 3) <*> (< 9) 这样的表达式来检查一个数字是否在给定的范围内。

如果您想在实际代码中使用任何这些,重要的是要考虑表达式 的含义 以及它如何反映在结构中。如果您这样做,然后使用有意义的组合符将其重构为无点样式,您通常会使代码的意图更清晰,这与 pointfree 的典型输出不同。

【讨论】:

    【解决方案2】:

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

    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
    

    【讨论】:

    • 这也是让你的函数完全不可读的好方法,当然……
    • 鉴于 return 被称为 unicorn,看来 OP 并不担心 =P。
    • @Claudiu:嗯,这来自 OP 正在进行的练习,这些练习基本上是从头开始推导出 Monad 之类的东西,使用非常愚蠢的定义名称。
    【解决方案3】:

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

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

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

    这里是香蕉2:

    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
    

    【讨论】:

      【解决方案4】:

      有一个 pointfree 包,它采用 Haskell 函数定义并尝试以 pointfree 样式重新编写它。我建议尝试使用它来获得新的想法。详情请见this page;该软件包可用here

      【讨论】:

        【解决方案5】:

        由于无点样式是组合子样式,只需应用已知的组合子定义,向后读取它们即可进行替换:

        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)
        

        等等

        【讨论】:

          猜你喜欢
          • 2010-10-30
          • 2016-07-11
          • 2017-10-31
          • 1970-01-01
          • 1970-01-01
          • 2017-02-22
          • 1970-01-01
          • 2016-01-11
          • 1970-01-01
          相关资源
          最近更新 更多