简体   繁体   English

bind 可以由 fmap 和 join 组成,那么我们是否必须使用 monadic 函数 a -> mb?

[英]bind can be composed of fmap and join, so do we have to use monadic functions a -> m b?

I don't use Haskell a lot, but I understand the concept of Monads.我不经常使用 Haskell,但我了解 Monads 的概念。

I had been confused by Kleisli triple , and the category, however,我一直对Kleisli 三元组和类别感到困惑,但是,

fmap and join fmap 并加入

Although Haskell defines monads in terms of the return and bind functions, it is also possible to define a monad in terms of return and two other operations, join and fmap .尽管 Haskell 根据 return 和 bind 函数定义 monad,但也可以根据return和其他两个操作joinfmap定义 monad。 This formulation fits more closely with the original definition of monads in category theory.这种表述更符合范畴论中单子的原始定义。 The fmap operation, with type (t → u) → M t → M u , takes a function between two types and produces a function that does the "same thing" to values in the monad. fmap操作的类型为(t → u) → M t → M u ,它在两种类型之间获取一个函数,并生成一个对 monad 中的值执行“相同操作”的函数。 The join operation, with type M (M t) → M t , "flattens" two layers of monadic information into one. join操作,类型为M (M t) → M t ,将两层一元信息“扁平化”为一层。

helps me to the background principle of Monads.帮助我了解 Monads 的背景原理。

The two formulations are related as follows:这两种配方的关系如下:

fmap f m = m >>= (return . f)
join n   = n >>= id

fmap :: (a -> b) -> (m a -> m b)
unit :: a -> m a
join :: m (m a) -> m a
>>=  :: m a -> (a -> m b) -> m b

m >>= f  =  join $ fmap f m

My question is: I think that since >>= can be composed of fmap and join , a monadic function a -> mb is not required and normal functions a -> b will satisfy the operation, but so many tutorials around the web still insist to use a monadic functions since that is the Kleisli triple and the monad-laws.我的问题是:我认为既然>>=可以由fmapjoin组成, fmap一元函数a -> mb正常的函数a -> b就可以满足操作了,但是网上这么多教程还是坚持使用 monadic 函数,因为那是 Kleisli 三元组和 monad-laws。

Well, shouldn't we just use non-monadic functions, as long as they are endo-functions, for the simplicity?好吧,为了简单起见,我们不应该只使用非一元函数,只要它们是内函数吗? What do I miss?我想念什么?

Related topics are相关话题是

Monad join function Monad 连接函数

Haskell Monad bind operator confusion Haskell Monad 绑定运算符混淆

Difference in capability between fmap and bind? fmap 和 bind 之间的功能差异?

In a sense, you're right.从某种意义上说,你是对的。 As every monad m is a functor, we can use fmap f with a function f :: a -> b to turn an ma into an mb , but there's a catch.由于每个 monad m都是函子,我们可以使用fmap f和函数f :: a -> bma变成mb ,但有一个问题。 What's b ?什么是b

I like to think of such an m as meaning "plan-to-get", where "plans" involve some sort of additional interaction beyond pure computation.我喜欢将这样的m视为“计划到获取”,其中“计划”涉及纯计算之外的某种额外交互。 If you have a "plan-to-get Int " and you want a "plan-to-get String ", you can use fmap with a function in Int -> String , but the type of that function tells you that getting the String from the Int involves no further interaction.如果您有一个“计划获取Int ”并且想要一个“计划获取String ”,则可以将fmapInt -> String的函数一起使用,但是该函数的类型告诉您获取StringInt涉及进一步的交互。

That isn't always so: perhaps the Int is a student registration number and the String is their name, so the plan to convert from one to the other needs an external lookup in some table.情况并非总是如此:也许Int是学生注册号而String是他们的姓名,因此从一个转换为另一个的计划需要在某个表中进行外部查找。 Then I don't have a pure function from Int to String , but rather a pure function from Int to "plan-to-get String ".然后我没有从IntString的纯函数,而是从Int到“plan-to-get String ”的纯函数。 If I fmap that across my "plan-to-get Int ", that's fine, but I end up with "plan-to-get (plan-to-get String )" and I need to join the outer and inner plans.如果我将它fmap到我的“计划获取Int ”,那很好,但我最终得到“计划获取(计划获取String )”,我需要join外部和内部计划。

The general situation is that we have enough information to compute the plan to get more.一般情况是我们有足够的信息来计算获得更多的计划。 That's what a -> mb models.这就是a -> mb模型。 In particular, we have return :: a -> ma , which turns the information we have into the plan that gives us exactly that information by taking no further action, and we have (>=>) :: (a -> mb) -> (b -> mc) -> (a -> mc) which composes two such things.特别是,我们有return :: a -> ma ,它将我们拥有的信息转化为计划,通过不采取进一步行动,为我们提供准确的信息,我们有(>=>) :: (a -> mb) -> (b -> mc) -> (a -> mc)由两个这样的东西组成。 We also have that (>=>) is associative and absorbs return on left and right, much the way ;我们也有(>=>)是关联的,并且吸收左右return ,很像; is associative and absorbs skip in classic imperative programming.是关联的,吸收了经典命令式编程中的skip

It's more convenient to build larger plans from smaller ones using this compositional approach, keeping the number of "plan-to-get" layers a consistent one .它是建立使用该构图方法较小的更大的计划更方便,保持数量“从计划到得到”图层一致 Otherwise, you need to build up an n -layer plan with fmap , then do the right number of join s on the outside (which will be a brittle property of the plan).否则,您需要使用fmap构建一个n层计划,然后在外部执行正确数量的join (这将是计划的一个脆弱属性)。

Now, as Haskell is a language with a concept of "free variable" and "scope", the a in现在,哈斯克尔是“自由变量”和“范围”,在一个概念的语言a

(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)

representing the "overall input information" can just be taken to come from the scope of things we already have, leaving代表“整体输入信息”的可以认为来自我们已经拥有的事物的范围,离开

(>>=) ::       m b  -> (b -> m c) ->       m c

and we get back "bind", which is the tool that presents the compositional structure in the most programmer-friendly form, resembling a local definition.我们回到“绑定”,它是一种以对程序员最友好的形式呈现组合结构的工具,类似于本地定义。

To sum up, you can work with a -> b , but often you need b to be "plan-to-get something", and that's the helpful thing to choose if you want to build plans compositionally.总而言之,您可以使用a -> b ,但通常您需要b成为“计划获得某物”,如果您想以组合方式构建计划,那么选择这会很有帮助。

I'm having a bit of a hard time understanding what your question actually is, but I'll take a crack at it anyway.我很难理解你的问题究竟是什么,但无论如何我都会尝试一下。

I think that since >>= can be composed of fmap and join , a monadic function a -> mb is not required and normal functions a -> b will satisfy the operation,我认为由于>>=可以由fmapjoin组成,因此fmap一元函数a -> mb并且普通函数a -> b将满足操作,

I expect you're referring to the "monadic function a -> mb " in the type for >>= , is that correct?我希望您指的是>>=类型中的“monadic function a -> mb ”,对吗? Well, let's see what happens when we replace that with a function of type a -> b :好吧,让我们看看当我们用a -> b类型a -> b函数替换它时会发生什么:

(>>=) :: m a -> (a -> m b) -> m b  -- standard version
(>>=) :: m a -> (a -> b) -> m b    -- your version

But doesn't this look familiar?但这看起来是不是很熟悉? It's equivalent to fmap :: (a -> b) -> ma -> mb , but with the parameters switched.它等效于fmap :: (a -> b) -> ma -> mb ,但参数已切换。 In fact, the implementation is just x >>= y = fmap yx , no need for join .实际上,实现只是x >>= y = fmap yx ,不需要join So there's your answer: if you use a "normal function a -> b " instead of a "monadic function a -> mb ", you no longer have a monad .所以这就是你的答案:如果你使用“普通函数a -> b ”而不是“monadic 函数a -> mb ”,你就不再有 monad 了 Instead, you have a Functor .相反,您有一个Functor


but so many tutorials around the web still insist to use a monadic functions since that is the Kleisli triple and the monad-laws.但是网络上的许多教程仍然坚持使用 monadic 函数,因为那是 Kleisli 三元组和 monad-laws。

I'm not sure which tutorials you're looking at.我不确定你在看哪些教程。 In my experience, the nature of tutorials is that they insist on whatever they're a tutorial for.根据我的经验,教程的本质是他们坚持他们的教程。 It would be weird if a tutorial for Monad s started presenting problems, and then suggesting things other than Monad s as solutions;如果Monad的教程开始提出问题,然后建议Monad以外的东西作为解决方案,那将是很奇怪的; at the very least, that would be out of the tutorial's scope, and a waste of time for anyone reading it to learn about Monad s.至少,这超出了本教程的范围,而且对于任何阅读它以了解Monad的人来说都是浪费时间。


Well, shouldn't we just use non-monadic functions, as long as they are endo-functions, for the simplicity?好吧,为了简单起见,我们不应该只使用非一元函数,只要它们是内函数吗? What do I miss?我想念什么?

Endofunctions are functions of type a -> a .内函数是a -> a类型a -> a函数。 Given the context of your question, I think you actually mean pure functions of type a -> b ("pure" as opposed to inherently monadic functions such as readIORef that need to be type a -> mb ).鉴于您的问题的上下文,我认为您实际上是指类型为a -> b纯函数(“纯”而不是固有的一元函数,例如需要类型为a -> mb readIORef )。 If my assumption is wrong, let me know, and I'll edit the question.如果我的假设是错误的,请告诉我,我会编辑问题。

EDIT:编辑:
As suggested in a comment by @duplode, it's likely that you mean endofunctor , which in Haskell is just any type of Functor .正如@duplode 在评论中所建议的那样,您很可能指的是endfunctor ,它在 Haskell 中只是任何类型的Functor In this case, the below still applies.在这种情况下,以下仍然适用。

In situations where Monad isn't necessary, it is often simpler to use Applicative , Functor , or just basic pure functions.在不需要Monad情况下,使用ApplicativeFunctor或只是基本的纯函数通常更简单。 In these cases, these things should be (and generally are) used in place of a Monad .在这些情况下,这些东西应该(并且通常)用来代替Monad For example:例如:

ws <- getLine >>= return . words  -- Monad
ws <- words <$> getLine           -- Functor (much nicer)

To be clear: If it's possible without a monad, and it's simpler and more readable without a monad, then you should do it without a monad!需要明确的是:如果没有 monad 也是可能的,并且没有 monad 更简单、更易读,那么你应该在没有 monad 的情况下做! If a monad makes the code more complex or confusing than it needs to be, don't use a monad!如果 monad 使代码比它需要的更复杂或更令人困惑,请不要使用 monad! Haskell has monads for the sole purpose of making certain complex computations simpler, easier to read, and easier to reason about. Haskell 拥有 monad 的唯一目的是使某些复杂的计算更简单、更易于阅读和更易于推理。 If that's not happening, you shouldn't be using a monad .如果没有发生这种情况,您不应该使用 monad

There is no reason.没有理由。

I think that since >>= can be composed of fmap and join , a monadic function a -> mb is not required我认为由于>>=可以由fmapjoin组成,因此fmap一元函数a -> mb

Yes, you're totally right.是的,你完全正确。 We don't need to require >>= for a monad, we could also require join instead.对于 monad,我们不需要要求>>= ,我们也可以要求join The two are totally equivalent.两者是完全等价的。 As we can also compose join = (>>= id) , we can do either因为我们也可以组合join = (>>= id) ,我们可以这样做

class Monad m where
    return :: a -> m a
    fmap :: (a -> b) -> m a -> m b
    (=<<) :: (a -> m b) -> m a -> m b
    -- join = (=<<) id

or或者

class Monad m where
    return :: a -> m a
    fmap :: (a -> b) -> m a -> m b
    join :: m (m a) -> m a
    -- (=<<) = (join .) . fmap

It doesn't matter which one we use.我们使用哪一种并不重要。 Admittedly, the latter looks simpler because there is only one higher-order function ( fmap ), in the former the types of fmap and =<< look too similar.诚然,后者看起来更简单,因为只有一个高阶函数( fmap ),在前者中fmap=<<的类型看起来太相似了。 join gives a better idea of what distinguishes a monad from a functor. join可以更好地了解 monad 与函子的区别。

Versatility多功能性

We can derive >>= from fmap and join , but we can derive join from >>= only.我们可以从fmapjoin派生>>= ,但我们只能从>>=派生join In fact, we can even derive fmap from >>= and return .事实上,我们甚至可以从>>=导出fmapreturn So should we say that >>= is more basic than the other?那么我们应该说>>=比另一个更基本吗? More powerful?更有力? Or maybe just: more convoluted?或者也许只是:更复杂?

We would rather like to write我们宁愿写

data Maybe a = Just a | Nothing
implement Functor Maybe where
    fmap = (=<<) . (return .) -- maybe not even write this ourselves
implement Monad Maybe where
    return = Just
    f =<< Just x = f x
    _ =<< Nothing  = Nothing

than

data Maybe a = Just a | Nothing
implement Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap f Nothing  = Nothing
implement Monad Maybe where
    return x = Just x
    join (Just (Just x)) = Just x
    join (Just Nothing)  = Nothing
    join Nothing         = Nothing

The former solution using >>= is minimal.使用>>=的前一种解决方案是最小的。

Convenience方便

Well, shouldn't we just use non-monadic functions for the simplicity?好吧,为了简单起见,我们不应该只使用非一元函数吗?

No. The whole idea of defining the monad typeclass is to ease working with monadic functions.不。定义 monad 类型类的整个想法是简化使用 monadic 函数的工作。 On their own, the return / fmap / join functions are pretty useless, what we are interested in are other functions that return the monadic data type: tryParse :: String -> Maybe Int for example.就其本身而言, return / fmap / join函数非常无用,我们感兴趣的是其他返回 monadic 数据类型的函数:例如tryParse :: String -> Maybe Int

And the whole idea behind monads is that we can arbitrarily chain and nest them, getting back a plain type in the end. monad 背后的整个想法是我们可以任意链接和嵌套它们,最终得到一个普通类型。 After having parsed a number, we need to validate the optional result (giving us another optional result) - in the monad ( fmap validate ) before getting it back out.解析完一个数字后,我们需要monad ( fmap validate ) 中验证可选结果(给我们另一个可选结果) 然后再将其取出。 There are usually no operations that yield nested data directly, we only get nested monad types because we do further monadic operations inside a monadic type.通常没有直接产生嵌套数据的操作,我们只能得到嵌套的 monad 类型,因为我们在 monadic 类型中进行了进一步的 monadic 操作。 And we'd much rather write我们更愿意写

tryRead = (=<<) validate . tryParse

than

tryRead = join . fmap validate . tryParse

That's why >>= is more important for using monads in daily life than join .这就是为什么>>=在日常生活中使用 monad 比join更重要的原因。 I would also guess that having to implement >>= directly, rather than implement join explicitly and have >>= get derived from it, allows for better (easier) compiler optimisations.我也猜想,必须直接实现>>= ,而不是显式实现join并从中派生>>= ,允许更好(更容易)的编译器优化。

Composition of Kleisli arrows a -> [b] Kleisli 箭头 a -> [b] 的组成

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

a, b, c are arbitrary types, m is always the same, here it is [] a、b、c 是任意类型,m 总是相同的,这里是 []

We have to produce a function (a->mc)我们必须产生一个函数 (a->mc)
Functions are produced with a lambda, we have one argument a.函数是用 lambda 生成的,我们有一个参数 a。

f >=> g =  \a -> ..f..g..
           \a -> let mb = f a;
                 in mc = mb >>= g

Above is taken from Bartosz Milewski Category Theory 10.1: Monads以上摘自 Bartosz Milewski Category Theory 10.1: Monads

mb >>= g producing mc    That looks like a functor

mb >>= b->mc     variable substitution: mc -> c'
mb >>= b->c'     now this is a functor!
fmap b->c' mb     fmap goes under the hood
m (b->c' b)      back substitution c' -> mc
m (b->mc b)
m (mc)    is not mc, so  another try!

join fmap g mb
join m(mc)     Monoid
mc       is mc   OK!

mc = mb >>= g
mc = join $ fmap g mb

g is b -> mc, so >>= and join-fmap use it the same way. g 是 b -> mc,所以 >>= 和 join-fmap 以同样的方式使用它。 If it's not available, you can build it with return.如果它不可用,您可以通过返回来构建它。

return
If we have b->c instead of b->mc

mb >>= (b->c) -> (b->mc)
         f
mb >>= \b -> let c = f b ;
             mc = return c
             in  mc
return :: c -> mc

mc = mb >>= \b -> return $ f b
and
mc = join $ fmap (\b -> return $ f b) mb

f is b -> c, you can use it together with return instead of b -> mc (Your Question). f 是 b -> c,您可以将它与 return 一起使用,而不是 b -> mc(您的问题)。

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

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