[英]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 -> mb
, a -> 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)
我们可以读取=>
在实例作为寓意:如果我们知道如何joinN
的ma
,我们也知道该怎么做了b -> ma
。
这个实例有点奇怪,因此需要FlexibleInstances
才能工作。 更令人不安的是,因为我们的基本情况( m (ma)
)完全由变量组成,它实际上与一堆其他合理的类型重叠。 要实际完成这项工作,我们必须启用OverlappingInstances
和IncoherentInstances
,它们相对棘手且容易出错。
经过一些粗略的测试,它似乎工作:
λ> 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.