繁体   English   中英

从monad中取出monadic函数

[英]Taking monadic functions out of a monad

Haskell有函数join ,它“运行”monadic中的monadic动作:

join :: Monad m => m (m a) -> m a
join m = m >>= \f -> f

我们可以用一个参数为monadic函数编写一个类似的函数:

join1 :: Monad m => m (a -> m b) -> (a -> m b)
join1 m arg1 = m >>= \f -> f arg1

并且有两个论点:

join2 :: Monad m => m (a -> b -> m c) -> (a -> b -> m c)
join2 m arg1 arg2 = m >>= \f -> f arg1 arg2

是否可以编写一个通用函数joinN ,它可以处理带有N个参数的joinN函数?

如果你真的想要,你可以用相当多的丑陋做这样的事情。

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}

import Control.Monad (join, liftM)

class Joinable m z | z -> m where
  joins :: m z -> z

instance Monad m => Joinable m (m a) where
  joins = join

instance (Monad m, Joinable m z) => Joinable m (r -> z) where
  joins m r = joins (liftM ($ r) m)

但是,正如你所看到的,这依赖于一些不稳定的类型类魔术(尤其是不可饶恕的UndecidableInstances )。 这很可能更好 - 如果看起来丑陋 - 写下所有实例join1 ... join10并直接导出它们。 这也是在base库中建立的模式。

值得注意的是,在这种制度下,推论不会太顺利。 例如

λ> joins (return (\a b -> return (a + b))) 1 2
Overlapping instances for Joinable ((->) t0) (t0 -> t0 -> IO t0)
  arising from a use of ‘joins’
Matching instances:
  instance Monad m => Joinable m (m a)
    -- Defined at /Users/tel/tmp/ASD.hs:11:10
  instance (Monad m, Joinable m z) => Joinable m (r -> z)
    -- Defined at /Users/tel/tmp/ASD.hs:14:10

但是如果我们给我们的论证一个明确的类型

λ> let {q :: IO (Int -> Int -> IO Int); q = return (\a b -> return (a + b))}

然后它仍然可以像我们希望的那样工作

λ> joins q 1 2
3

之所以出现这种情况是因为仅使用类型类就很难指出你是想在函数链的最终返回类型中使用monad m 还是在函数链本身的monad (->) r操作

最简洁的答案是不。 稍微长一点的答案是您可以定义一个中缀运算符。

看一下liftM的实现: http//hackage.haskell.org/package/base-4.7.0.2/docs/src/Control-Monad.html#liftM

它定义了liftM5。 这是因为无法定义liftMN,就像你的joinN不可能一样。

但是我们可以从Appicative <$><*>吸取教训并定义我们自己的中缀运算符:

> let infixr 1 <~> ; x <~> f = fmap ($ x) f
> :t (<~>)
(<~>) :: Functor f => a -> f (a -> b) -> f b
> let foo x y = Just (x + y)
> let foobar = Just foo
> join $ 1 <~> 2 <~> foobar
Just 3

这很容易让人联想到一个常见的应用模式:

f <$> a1 <*> a2 .. <*> an
join $ a1 <~> a2 .. <~> an <~> f

每个可能的N单一功能? 并不是的。 在Haskell中泛化具有不同数量的参数的函数是很困难的,部分原因是“参数数量”并不总是很明确。 以下是id类型的所有有效特化:

id :: a -> a
id :: (a -> a) -> a -> a
id :: (a -> a -> a) -> a -> a -> a
...

我们需要一些方法来获得类型级别的N ,然后根据N是做不同的事情。

printf这样的现有“可变参数”函数可以使用类型类来执行此操作。 它们通过归纳来确定N是什么-> :它们为非函数类型(如String和函数的递归实例具有“基本案例”实例:

instance PrintfType String ...
instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) ...

我们可以(在经过大量思考之后:P)在这里使用相同的方法,但有一点需要注意:我们的基本情况有点难看。 我们想从普通join开始,它产生类型为ma的结果; 问题是,为了支持任何 ma ,我们必须与正常功能重叠。 这意味着我们需要启用一些可怕的扩展,并且当我们实际使用joinN时,我们可能会混淆类型推断系统。 但是,如果有正确的类型签名,我相信它应该正常工作。

首先,这是辅助类:

class Monad m => JoinN m ma where
  joinN :: m ma -> ma

ma将采用相关的函数类型,如a -> mba -> b -> mc等。 我无法弄清楚如何将m留在类定义之外,所以我们需要启用MultiParamTypeClasses

接下来,我们的基本情况,这只是正常的join

instance Monad m => JoinN m (m a) where
  joinN = join

最后,我们有递归案例。 我们需要做的是“剥离”一个参数,然后根据较小的joinN实现该函数。 我们用ap来做这个,它是专门用于monad的: <*>

instance (Monad m, JoinN m ma) => JoinN m (b -> ma) where
  joinN m arg = joinN (m `ap` return arg)

我们可以读取=>在实例作为寓意:如果我们知道如何joinNma ,我们也知道该怎么做了b -> ma

这个实例有点奇怪,因此需要FlexibleInstances才能工作。 更令人不安的是,因为我们的基本情况( m (ma) )完全由变量组成,它实际上与一堆其他合理的类型重叠。 要实际完成这项工作,我们必须启用OverlappingInstancesIncoherentInstances ,它们相对棘手且容易出错。

经过一些粗略的测试,它似乎工作:

λ> let foo' = do getLine >>= \ x -> return $ \ a b c d -> putStrLn $ a ++ x ++ b ++ x ++ c ++ x ++ d
λ> let join4 m a b c d = m >>= \ f -> f a b c d
λ> join4 foo' "a" "b" "c" "d"

a  b  c  d
λ> joinN foo' "a" "b" "c" "d"

a  b  c  d

暂无
暂无

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

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