用 pointfree 风格写 f ?

Write f in pointfree-style?

假设我有函数

g :: a -> b, h :: a -> c 

f :: b -> c -> d. 

是否可以写出函数

 f' :: a -> a -> d 

提供
f' x y = f (g x) (h y) 

在点free style?.

可以写函数

f' a -> d, f' x = f (g x) (h x) 

点自由风格通过设置

f' = (f <$> g) <*> h  

但我不知道如何处理更一般的情况。

看看online converter

已转换

f' x y = f (g x) (h y) 

进入

f' = (. h) . f . g

随转换流程

f' = id (fix (const (flip ((.) . f . g) h))) 
f' = fix (const (flip ((.) . f . g) h)) 
f' = fix (const ((. h) . f . g)) 
f' = (. h) . f . g

我们有:

k x y = (f (g x)) (h y)

并且我们希望以无点样式编写k

传递给 k 的第一个参数是 x。我们需要用 x 做什么?好吧,首先我们需要调用 g,然后调用 f,然后做一些花哨的事情将其应用到 (h y).

k = fancy . f . g

这是什么fancy?嗯:

k x y = (fancy . f . g) x y
      = fancy (f (g x)) y
      = f (g x) (h y)

所以我们渴望fancy z y = z (h y)。 Eta-reducing,我们得到fancy z = z . h,或fancy = (. h).

k = (. h) . f . g

一种更自然的思考方式可能是

                             ┌───┐           ┌───┐
                        x ───│ g │─── g x ───│   │
                      /      └───┘           │   │
               (x, y)                        │ f │─── f (g x) (h y)
                      \      ┌───┐           │   │
                        y ───│ h │─── h y ───│   │
                             └───┘           └───┘

                      └──────────────────────────────┘
                                      k

输入Control.Arrow:

k = curry ((g *** h) >>> uncurry f)

这比 (. h) . f. g.

稍长,但更容易理解

首先,稍微重写 f' 以获取一个元组而不是两个参数。 (换句话说,我们正在取消您原来的 f'。)

f' (x, y) = f (g x) (h y)

您可以使用 fstsnd 来分开一个元组,而不是对其进行模式匹配:

f' t = f (g (fst t)) (h (snd t))

使用函数组合,上面变成

f' t = f ((g . fst) t) ((h . snd) t)

嘿,这看起来很像你可以使用应用风格制作的无点版本:

f' = let g' = g . fst
         h' = h . snd
     in (f <$> g') <*> h'

剩下的唯一问题是f' :: (a, a) -> d。您可以通过显式柯里化来解决此问题:

f' :: a -> a -> d
f' = let g' = g . fst
         h' = h . snd
     in curry $ (f <$> g') <*> h'

(顺便说一下,这与 Lynn 添加的 Control.Arrow 解决方案非常相似。)

将"three rules of operator sections"应用于(.)函数组合运算符,

(.) f g  =  (f . g)  =  (f .) g  =  (. g) f   -- the argument goes into the free slot
--       1           2           3

这可以通过几个简单的机械步骤得出:

k x y =  (f (g x)) (h y)                      -- a (b c) === (a . b) c
      =  (f (g x) . h) y
      =  (. h)  (f (g x)) y
      =  (. h)  ((f . g)  x) y
      = ((. h) . (f . g)) x  y

最后,(.) 是关联的,因此可以删除内部括号。

一般的过程是力求达到可以进行eta缩减的情况,即如果参数顺序相同并且在任何括号之外,我们可以去掉它们:

k x y = (......) y
=>
k x   = (......)

Lather, rinse,重复。


另一个技巧是将两个参数变成一个,反之亦然,用等式

curry f x y = f (x,y)     

那么,你的

f (g x) (h y) = (f.g) x (h y)                      -- by B-combinator rule
              = (f.g.fst) (x,y) ((h.snd) (x,y))
              = (f.g.fst <*> h.snd) (x,y)          -- by S-combinator rule
              = curry (f.g.fst <*> h.snd) x y

这与 相同,但更简洁。

所以,你看,你的 (f.g <*> h) x1 就变成了 (f.g.fst <*> h.snd) (x,y)。一样的区别。


1(因为对于函数,(<$>) = (.)

Control.Compose

(g ~> h ~> id) f

Data.Function.Meld

f $* g $$ h *$ id

Data.Function.Tacit

lurryA @N2 (f <$> (g <$> _1) <*> (h <$> _2))
lurryA @N5 (_1 <*> (_2 <*> _4) <*> (_3 <*> _5)) f g h

相关文章