简体   繁体   English

为什么加入。 (翻转fmap)有类型((A - > B) - > A) - >(A - > B) - > B?

[英]Why does join . (flip fmap) have type ((A -> B) -> A) -> (A -> B) -> B?

Some playing around with functors and monads in ghci led me to a value whose type and behaviour I would like to understand better. 有些人在ghci玩弄着仿函数和monad,这让我得到了一个我希望更好理解其类型和行为的价值。

The type of \\x -> join . x \\x -> join . x的类型\\x -> join . x \\x -> join . x is (Monad m) => (a -> m (mb)) -> (a -> mb) and the type of \\y -> y . (flip fmap) \\x -> join . x(Monad m) => (a -> m (mb)) -> (a -> mb)\\y -> y . (flip fmap)的类型\\y -> y . (flip fmap) \\y -> y . (flip fmap) is (Functor f) => ((a -> b) -> fb) -> (fa -> c) . \\y -> y . (flip fmap)(Functor f) => ((a -> b) -> fb) -> (fa -> c)

Version 8.2.2 of ghci permits the definition h = join . (flip fmap) ghci版本8.2.2允许定义h = join . (flip fmap) h = join . (flip fmap) . h = join . (flip fmap)

Why does h have type ((A -> B) -> A) -> (A -> B) -> B ? 为什么h有类型((A -> B) -> A) -> (A -> B) -> B

In particular, why do the functor and monad constraints disappear? 特别是,为什么仿函数和monad约束消失了? Is this really the correct and expected behaviour? 这真的是正确和预期的行为吗? As a follow up, I would also like to ask: 作为后续行动,我还想问:

Why does evaluating h (\\f -> fu) (\\x -> x + v) for integers u and v give u + 2v in every case? 为什么评估整数uv h (\\f -> fu) (\\x -> x + v)在每种情况下给出u + 2v

In short : due to type deduction, Haskell knows that m and f are in fact a partially instantiated arrow. 简而言之 :由于类型推导,Haskell知道mf实际上是部分实例化的箭头。

Deriving the type 派生类型

Well let us do the math. 好吧,让我们做数学。 The function join . (flip fmap) 函数join . (flip fmap) join . (flip fmap) is basically your given lambda expression \\x -> join . x join . (flip fmap)基本上是你给定的lambda表达式\\x -> join . x \\x -> join . x with as argument (flip fmap) , so: \\x -> join . x带有参数(flip fmap) ,所以:

h = (\x -> join . x) (flip fmap)

Now the lambda expression has type: 现在lambda表达式有类型:

(\x -> join . x) :: Monad m =>   (a -> m (m b)) -> (a -> m b)

Now the argument flip fmap has type: 现在参数flip fmap有类型:

flip fmap        :: Functor f => f c -> ((c -> d) -> f d)

(we here use c and d instead of a and b to avoid confusion between two possibly different types). (我们这里使用cd而不是ab来避免两种可能不同类型之间的混淆)。

So that means that the type of flip fmap is the same as the type of the argument of the lambda expression, hence we know that: 这意味着flip fmap的类型与lambda表达式的参数类型相同,因此我们知道:

  Monad m =>   a   -> m (m b)
~ Functor f => f c -> ((c -> d) -> f d)
---------------------------------------
a ~ f c, m (m b) ~ ((c -> d) -> f d)

So we now know that a has the same type as fc (this is the meaning of the tilde ~ ). 所以我们现在知道afc具有相同的类型(这是波浪号的含义~ )。

But we have to do some extra computations: 但我们必须做一些额外的计算:

  Monad m =>   m (m b)
~ Functor f => ((c -> d) -> f d)
--------------------------------
m ~ (->) (c -> d), m b ~ f d

Hence we know that m is the same as (->) (c -> d) (basically this is a function where we know that input type, here (c -> d) , and the output type is a type parameter of m . 因此我们知道m(->) (c -> d) (基本上这是一个我们知道输入类型的函数,这里(c -> d) ,输出类型是m的类型参数。

So that means that mb ~ (c -> d) -> b ~ fd , so this means that f ~ (->) (c -> d) and b ~ d . 所以这意味着mb ~ (c -> d) -> b ~ fd ,所以这意味着f ~ (->) (c -> d)b ~ d An extra consequence is that since a ~ fc , we know that a ~ (c -> d) -> c 另外一个结果是,因为a ~ fc ,我们知道a ~ (c -> d) -> c

So to list what we derived: 所以列出我们得到的东西:

f ~ m
m ~ (->) (c -> d)
b ~ d
a ~ (c -> d) -> c

So we now can "specialize" the types of both our lambda expression, and our flip fmap function: 所以我们现在可以“专门化”我们的lambda表达式和我们的flip fmap函数的类型:

(\x -> join . x)
    :: (((c -> d) -> c) -> (c -> d) -> (c -> d) -> d) -> ((c -> d) -> c) -> (c -> d) -> d
flip fmap
    ::  ((c -> d) -> c) -> (c -> d) -> (c -> d) -> d

and type of flip fmap now perfectly matches with the type of the argument of the lambda expression. flip fmap的类型现在完全匹配lambda表达式的参数类型。 So the type of (\\x -> join . x) (flip fmap) is the result type of the lambda expression type, and that is: 所以(\\x -> join . x) (flip fmap)的类型(\\x -> join . x) (flip fmap)是lambda表达式类型的结果类型,即:

(\x -> join . x) (flip fmap)
    :: ((c -> d) -> c) -> (c -> d) -> d

But now we of course did not yet obtained the implementation of this function. 但是现在我们当然还没有获得这个功能的实现。 We are however already a step further. 然而,我们已经向前迈进了一步。

Deriving the implementation 推导实施

Since we now know that m ~ (->) (c -> d) , we know we should lookup the arrow instance of a monad : 既然我们现在知道m ~ (->) (c -> d) ,我们知道应该查找monad箭头实例

 instance Monad ((->) r) where f >>= k = \\ r -> k (fr) r 

So for a given function f :: r -> a , as left operand, and a function k :: a -> (r -> b) ~ a -> r -> b as operand, we construct a new function that maps a variable x to k applied to f applied to x , and x . 因此对于给定函数f :: r -> a ,作为左操作数,以及函数k :: a -> (r -> b) ~ a -> r -> b作为操作数,我们构造一个新的映射函数应用于f的变量xk应用于xx It is thus a way to perform some sort of preprocessing on an input variable x , and then do the processing both taking into account the preprocessing and the original view (well this is an interpretation a human reader can use). 因此,这是一种对输入变量x执行某种预处理的方法,然后在考虑预处理和原始视图的情况下进行处理(这是人类读者可以使用解释)。

Now join :: Monad m => m (ma) -> ma is implemented as : 现在join :: Monad m => m (ma) -> ma 实现为

 join :: Monad m => m (ma) -> ma join x = x >>= id 

So for the (->) r monad, this means that we implement this as: 所以对于(->) r monad,这意味着我们将其实现为:

-- specialized for `m ~ (->) a
join f = \r -> id (f r) r

Since id :: a -> a (the identity function) returns its argument, we can further simplify it to: 由于id :: a -> a (标识函数)返回其参数,我们可以进一步简化它:

-- specialized for `m ~ (->) a
join f = \r -> (f r) r

or cleaner: 或清洁:

-- specialized for `m ~ (->) a
join f x = f x x

So it basically is given a function f , and will then apply an argument twice to that function. 所以它基本上给出了一个函数f ,然后将该参数两次应用于该函数。

Furthermore we know that the Functor instance for the arrow type is defined as : 此外,我们知道箭头类型的Functor实例定义为

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

So it is basically used as a "post processor" on the result of the function: we construct a new function that will do the post processing with the given function. 因此它基本上用作函数结果的“后处理器”:我们构造一个新函数,用于使用给定函数进行后处理。

So now that we specialized the function enough for the given Functor / Monad , we can derive the implementation as: 所以现在我们将这个函数专门用于给定的Functor / Monad ,我们可以将实现派生为:

-- alternative implementation
h = (.) (\f x -> f x x) (flip (.))

or by using more lambda expressions: 或者使用更多的lambda表达式:

h = \a -> (\f x -> f x x) ((flip (.)) a)

which we can now further specialize as: 我们现在可以进一步专注于:

h = \a -> (\f x -> f x x) ((\y z -> z . y) a)

-- apply a in the lambda expression
h = \a -> (\f x -> f x x) (\z -> z . a)

-- apply (\z -> z . a) in the first lambda expression
h = \a -> (\x -> (\z -> z . a) x x)

-- cleaning syntax
h a = (\x -> (\z -> z . a) x x)

-- cleaning syntax
h a x = (\z -> z . a) x x

-- apply lambda expression
h a x = (x . a) x

-- remove the (.) part
h a x = x (a x)

So h basically takes two arguments: a and x , it then performs function application with a as function and x as parameter, and the output is passed to the x function again. 所以h基本上需要两个参数: ax ,然后将其与执行功能的应用程序a作为函数和x为参数,输出被传递给x功能一次。

Sample usage 样品用法

As sample usage you use: 作为样本用法,您使用:

h (\f -> f u) (\x -> x + v)

or nicer: 还是更好的:

h (\f -> f u) (+v)

so we can analyze this like: 所以我们可以这样分析:

   h (\f -> f u) (+v)
-> (+v) ((\f -> f u) (+v))
-> (+v) ((+v) u)
-> (+v) (u+v)
-> ((u+v)+v)

So we add u+v to v . 所以我们将u+v添加到v

Types line up easier with >>> : 使用>>> 可以更轻松地排列类型:

                          a   ->     b                       >>>
                                     b              -> c     ::
                          a   ->                       c   

Here, we have 在这里,我们有

join . flip fmap  ==  flip fmap >>> join

flip fmap :: Functor f => f a -> ((a -> b) -> f b )
join      :: Monad   m =>        (m          (m b)) -> m b
----------------------------------------------------------
flip fmap >>> join ::
 (Functor f, Monad m) =>  f a  ->                      m b   , ((a -> b) ->) ~ m, f ~ m
                   ::
 (Functor f, Monad f) =>  f a  ->                      f b   , f ~ ((a -> b) ->)
                   ::    ((a -> b) -> a) -> ((a -> b) -> b)

Simple, mechanical, mundane. 简单,机械,世俗。


To see what it does , combinatory style definitions are usually easiest to twiddle with, 看看会发生什么 ,组合子样式定义通常是最容易给摆弄,

(join . flip fmap) f g x =
 join (flip fmap f) g x =          -- join f x = f x x
 (`fmap` f) g g x =                -- f `fmap` g = f . g
 (g . f) g x 
 g (f g) x 

So we don't need x after all (or do we?). 所以我们毕竟不需要x (或者我们呢?)。 The join and fmap definitions for functions are given in the margins. 函数的joinfmap定义在边距中给出。 We've arrived at 我们到了

(join . flip fmap) f g = g (f g)   -- f :: (a -> b) -> a,  g :: a -> b 
                                   -- f g :: a  , g (f g) :: b

Another way is starting from the types, going by the rule of modus ponens, 另一种方式是从类型开始,按照modus ponens的规则,

            ((a -> b) -> a)    (a -> b)        --       f  g
            ---------------------------
(a -> b)                 a                     --  g   (f  g)
---------------------------------------
      b

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM