[英]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.