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个参数的monadic函数?
答案 0 :(得分:5)
如果你真的想要,你可以用相当多的丑陋做这样的事情。
{-# 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
上操作功能链本身。
答案 1 :(得分:3)
简短的回答是否定的。稍微长一点的答案是你可以定义一个中缀运算符。
查看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
答案 2 :(得分:1)
每个可能N
的单一功能?并不是的。在Haskell中推广具有不同数量的参数的函数是很困难的,部分原因是&#34;参数的数量&#34;并不总是定义明确。以下是id
类型的所有有效专业化:
id :: a -> a
id :: (a -> a) -> a -> a
id :: (a -> a -> a) -> a -> a -> a
...
我们需要一些方式在类型级别获取N
,然后根据N
的内容执行不同的操作。
现有&#34;可变参数&#34;像printf
这样的函数用类型类来做这个。他们通过N
上的归纳来确定->
是什么:他们有一个&#34;基本案例&#34;像String
这样的非函数类型的实例和函数的递归实例:
instance PrintfType String ...
instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) ...
我们可以(经过很多思考:P)在这里使用相同的方法,但有一点需要注意:我们的基本情况有点难看。我们想从正常join
开始,它产生类型为m a
的结果;问题是,要支持任何 m a
,我们必须与普通函数重叠。这意味着我们需要启用一些可怕的扩展,并且当我们实际使用joinN
时,我们可能会混淆类型推断系统。但是,如果有正确的类型签名,我相信它应该可以正常工作。
首先,这是辅助类:
class Monad m => JoinN m ma where
joinN :: m ma -> ma
ma
将采用相关的函数类型,如a -> m b
,a -> b -> m c
等。我无法弄清楚如何将m
排除在课程定义之外,所以我们需要启用MultiParamTypeClasses
。
接下来,我们的基本情况,这是正常的join
:
instance Monad m => JoinN m (m a) where
joinN = join
最后,我们有递归案例。我们需要做的是&#34;剥离&#34;一个参数,然后用较小的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 (m a)
)完全由变量组成,所以它实际上与一堆其他合理的类型重叠。要实际完成这项工作,我们必须启用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