撰写function组合:(。)。(。)如何工作?

(.)需要两个函数,它们取一个值并返回一个值:

 (.) :: (b -> c) -> (a -> b) -> a -> c 

由于(.)两个参数,所以我觉得(.).(.)应该是无效的,但它是完全正确的:

 (.).(.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c 

这里发生了什么? 我意识到这个问题措辞严厉……所有的function都是由于咖喱而引起的。 也许更好的方式来说这是types不匹配。

让我们先玩typechecker的机械certificate。 之后我会描述一个直观的思考方式。

我想将(.)应用于(.) ,然后将(.)应用于结果。 第一个应用程序可以帮助我们定义variables的一些等价性。

 ((.) :: (b -> c) -> (a -> b) -> a -> c) ((.) :: (b' -> c') -> (a' -> b') -> a' -> c') ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'') let b = (b' -> c') c = (a' -> b') -> a' -> c' ((.) (.) :: (a -> b) -> a -> c) ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'') 

然后我们开始第二个,但很快就卡住了…

 let a = (b'' -> c'') 

这是关键:我们想让let b = (a'' -> b'') -> a'' -> c'' ,但是我们已经定义了b ,所以我们必须尝试统一 – 以匹配我们最好的两个定义。 幸运的是,他们确实匹配

 UNIFY b = (b' -> c') =:= (a'' -> b'') -> a'' -> c'' which implies b' = a'' -> b'' c' = a'' -> c'' 

通过这些定义/统一我们可以继续申请

 ((.) (.) (.) :: (b'' -> c'') -> (a' -> b') -> (a' -> c')) 

然后展开

 ((.) (.) (.) :: (b'' -> c'') -> (a' -> a'' -> b'') -> (a' -> a'' -> c'')) 

并清理它

 substitute b'' -> b c'' -> c a' -> a a'' -> a1 (.).(.) :: (b -> c) -> (a -> a1 -> b) -> (a -> a1 -> c) 

说实话,这是一个违反直觉的结果。


这是直觉。 首先看看fmap

 fmap :: (a -> b) -> (fa -> fb) 

它将一个function“提升”成一个Functor 。 我们可以反复应用

 fmap.fmap.fmap :: (Functor f, Functor g, Functor h) => (a -> b) -> (f (g (ha)) -> f (g (hb))) 

使我们能够将function提升到更深层次的Functors

事实certificate,数据types(r ->)是一个Functor

 instance Functor ((->) r) where fmap = (.) 

这应该看起来很熟悉。 这意味着fmap.fmap转换为(.).(.) 。 因此, (.).(.)就是让我们转换(r ->) Functor的深层和深层参数types。 (r ->) Functor实际上是Reader Monad ,所以分层的Reader就像拥有多个独立的全局不变状态。

或者像有多个input参数不受fmap影响。 (> 1)参数函数的“只是结果”组成一个新的继续函数。


最后值得注意的是,如果你觉得这个东西很有趣,它就构成了控制镜头的核心直觉。

让我们暂时忽略types,只使用lambda微积分。

  • Desugar中缀符号:
    (.) (.) (.)

  • ETA-扩大:
    (\ ab -> (.) ab) (\ cd -> (.) cd) (\ ef -> (.) ef)

  • 内联(.)的定义:
    (\ abx -> a (bx)) (\ cdy -> c (dy)) (\ efz -> e (fz))

  • 替代a
    (\ bx -> (\ cdy -> c (dy)) (bx)) (\ efz -> e (fz))

  • 替补b
    (\ x -> (\ cdy -> c (dy)) ((\ efz -> e (fz)) x))

  • 替代e
    (\ x -> (\ cdy -> c (dy)) (\ fz -> x (fz)))

  • 替补c
    (\ x -> (\ dy -> (\ fz -> x (fz)) (dy)))

  • 替补f
    (\ x -> (\ dy -> (\ z -> x (dyz))))

  • Resugar lambda表示法:
    \ xdyz -> x (dyz)

如果你问GHCi,你会发现这是预期的types。 为什么? 因为函数箭头是右关联的以支持柯里:types(b -> c) -> (a -> b) -> a -> c真的意味着(b -> c) -> ((a -> b) -> (a -> c)) 。 同时,typesvariablesb可以代表任何types, 包括函数types 。 看到连接?

这是一个相同现象的简单例子:

 id :: a -> a id x = x 

id的types说id应该有一个参数。 事实上,我们可以用一个论据来说:

 > id "hello" "hello" 

但事实certificate我们也可以用两个参数来调用它:

 > id not True False 

甚至:

 > id id "hello" "hello" 

到底是怎么回事? 理解id not True关键是先看看id not 。 显然,这是允许的,因为它将id应用于一个参数。 not的types是Bool -> Bool ,所以我们知道id的types应该是Bool -> Bool ,所以我们知道这个id的出现有type:

 id :: (Bool -> Bool) -> (Bool -> Bool) 

或者,用更less的括号:

 id :: (Bool -> Bool) -> Bool -> Bool 

所以这个id的发生实际上有两个参数

同样的推理也适用于id id "hello"(.) . (.) (.) . (.)

这是我认为先把握一般情况,然后再考虑具体情况的简单方法之一。 所以我们来考虑仿函数。 我们知道仿函数提供了一种在结构上映射函数的方法 –

 class Functor f where fmap :: (a -> b) -> fa -> fb 

但是如果我们有两层函数呢? 例如,列表的列表? 在这种情况下,我们可以使用两层fmap

 >>> let xs = [[1,2,3], [4,5,6]] >>> fmap (fmap (+10)) xs [[11,12,13],[14,15,16]] 

但是模式f (gx)(f . g) x完全相同,所以我们可以写出来

 >>> (fmap . fmap) (+10) xs [[11,12,13],[14,15,16]] 

什么是fmap . fmap的typesfmap . fmap fmap . fmap

 >>> :t fmap.fmap :: (Functor g, Functor f) => (a -> b) -> f (ga) -> f (gb) 

我们看到它映射了两层函数,就像我们想要的那样。 但现在请记住(->) r是一个函数(函数的types,你可能更喜欢读作(r ->) ),它的函子实例是

 instance Functor ((->) r) where fmap fg = f . g 

对于一个函数, fmap只是函数组合! 当我们fmap两个fmap我们映射了函数functor的两个级别。 我们最初有一些types(->) s ((->) ra) ,它相当于s -> r -> a ,而我们最终得到了typess -> r -> b ,所以types(.).(.)必须是

 (.).(.) :: (a -> b) -> (s -> r -> a) -> (s -> r -> b) 

它接受它的第一个函数,并使用它来转换第二个(双参数)函数的输出。 例如,函数((.).(.)) show (+)是两个参数的函数,首先将它们的参数加在一起,然后使用show将结果转换为String

 >>> ((.).(.)) show (+) 11 22 "33" 

例如,有一个自然泛化思考更长的fmap

 fmap.fmap.fmap :: (Functor f, Functor g, Functor h) => (a -> b) -> f (g (ha)) -> f (g (hb)) 

它映射三层函数,相当于由三个参数构成的函数:

 (.).(.).(.) :: (a -> b) -> (r -> s -> t -> a) -> (r -> s -> t -> b) 

例如

 >>> import Data.Map >>> ((.).(.).(.)) show insert 1 True empty "fromList [(1,True)]" 

它将True值插入到键1的空映射中,然后使用show将输出转换为string。


这些function通常是有用的,所以你有时看到它们被定义为

 (.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b) (.:) = (.).(.) 

所以你可以写

 >>> let f = show .: (+) >>> f 10 20 "30" 

当然,可以给出一个简单的,有针对性的(.:)定义

 (.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b) (f .: g) xy = f (gxy) 

这可能有助于使(.).(.)神秘化。

你是对的, (.)只有两个参数。 你似乎混淆了haskell的语法。 在expression式(.).(.) ,它实际上是中间的点,将另外两个点作为参数,就像在expression式100 + 200中可以写成(+) 100 200

 (.).(.) === (number the dots) (1.)2.(3.) === (rewrite using just syntax rules) (2.)(1.)(3.) === (unnumber and put spaces) (.) (.) (.) === 

(.) (.) (.)中应该更清楚地知道第一个(.)将第二个(.)和第三个(.)作为参数。

是的,这是由于咖喱。 (.)因为Haskell中的所有函数只有一个参数。 你正在创作的是第一次部分调用每个组合的(.) ,它接受它的第一个参数(组合的第一个函数)。

(先阅读我的函数组合的答案 ,$运算符和无点风格。)

想象一下,你有一个简单的function:它加起来2个数字,然后否定结果。 我们将它称为foo

 foo ab = negate (a + b) 

现在,让我们一步一步地将它看成是免费的,看看我们最终的结果:

 foo ab = negate $ a + b foo ab = negate $ (+) ab foo ab = negate $ (+) a $ b foo ab = negate . (+) a $ b foo a = negate . (+) a -- fx = gx is equivalent to f = g foo a = (.) negate ((+) a) -- any infix operator is just a function foo a = (negate.) ((+) a) -- (2+) is the same as ((+) 2) foo a = (negate.) $ (+) a foo a = (negate.) . (+) $ a foo = (negate.) . (+) foo = ((.) negate) . (+) foo = (.) ((.) negate) (+) -- move dot in the middle in prefix position foo = ((.) ((.) negate)) (+) -- add extra parentheses 

现在我们来更仔细地分析expression式(.) ((.) negate) 。 这是(.)函数的部分应用,其第一个参数是((.) negate) 。 我们可以进一步改造它吗? 我们可以:

 (.) ((.) negate) (.) . (.) $ negate -- because f (fx) is the same as (f . f) x (.)(.)(.) $ negate ((.)(.)(.)) negate 

(.).(.)等价于(.)(.)(.) ,因为在第一个expression式中,中间的点可以移动到前缀位置,并用圆括号括起来,从而产生第二个expression式。

现在我们可以重写我们的foo函数:

 foo = ((.).(.)) negate (+) foo = ((.)(.)(.)) negate (+) -- same as previous one foo = negate .: (+) where (.:) = (.).(.) 

现在你知道(.).(.)等价于(\fgxy -> f (gxy))

 (\fgxy -> f (gxy)) negate (+) 2 3 -- returns -5 ((.).(.)) negate (+) 2 3 -- returns -5