[英](How) Can you curry compose monadic functions?
我有以下功能:
f: a -> m[b]
g: (b,c) -> m[d]
h: (a,c) -> m[d]
h
如何表示為f
和g
的組合?
使用do/for
表示法,我們可以像這樣輕松實現h
:
h: (a,c) => {
for {
b <- f(a)
d <- g(b,c)
} yield (d)
}
但是,我很好奇我們是否可以這樣表達: h = f andThen g
其中andThen
被用作單子組合運算符。 例如:
f: a -> m[b]
g: b -> m[c]
h: a -> m[c] = f andThen g
我假設在 Haskell (例如,Kliesli >=>
)等語言中可以創建這樣的andThen
function 。 在 Scala 中,我們可以這樣寫:(在 Scala 命名中的示例andThenE
因為andThen
已經在Function1
的實例上定義)。
implicit class AndThenEither[A,B](val e: Function1[A,Either[_,B]]) {
def andThenE[C](f:Function1[B, Either[_,C]]): Function1[A, Either[_,C]] = {
(v1: A) => e.apply(v1).flatMap(b => f.apply(b))
}
}
鑒於此,如果我們對函數進行 curry,我們似乎可以實現這樣的組合(或者至少看起來是可能的):
f: a -> m[b]
g: b -> c -> m[d]
h: a -> c -> m[d] = f andThen g
理論上這可以工作,但我不知道這是否可能或如何在 Scala (或 Haskell,盡管我更熟悉前者)中實現類似的東西。
假設我們有以下功能:
case class Error(e:String)
case class Output(i: Int, f: Float, s: String)
case class IntermediateOutput(i:Int, f:Float)
def f(i:Int): Either[Error, IntermediateOutput] = Right(IntermediateOutput(i+1, i*0.33)
def g(io: IntermediateOutput, s: String): Either[Error, Output] = Right(Output(io.i, io.f, "hello "+s))
val h: (Int, String) => Either[Error, Output] = f andThen g
val result = h(1, "world!") //Right(Output(2, 0.33, "hello world!")
這甚至可能/可以實現嗎? 如果不是 Scala,我們怎么能 go 關於curry 在 Haskell 或一般情況下組成一元函數?
這是已知的事情,還是我們明確區分適用於非單子函數的柯里化和為單子函數保留andThen
like 運算符,但避免將兩者混合? 如果是這樣,我可以看到do/for
符號的有力案例。 但是,我並不完全相信這是不可能的,並且想進一步了解這一點。 也許代碼會很混亂,沒關系 - 我只是好奇。 由於處理現有問題,我偶然發現了這種情況,但我不能這樣投。
在 Haskell 中有一些標准(即在base
庫中)運算符。
首先,您的andThen
function 是眾所周知的Kleisli 組合:
>=> :: (a -> m b) -> (b -> m c) -> a -> m c
a -> m b
b -> m c
-----------------
a -> m c
由於g
在元組中操作並且f
不返回元組,因此此運算符與您的類型不完全匹配。 這可以通過do/for
符號輕松克服
h :: Monad m => (a -> m b) -> ( (b,c) -> m d ) -> (a,c) -> m d
h f g (a, c) = do
b <- f a
g (b, c)
對於上面的解決方案,我會使用 go,但為了好奇,這個問題已經遇到過,Haskell 的base
庫引入了一個面向類別理論的模塊,稱為Control.Arrow
。 在這里,您可以找到大量運算符來實現您的目標:
import Control.Arrow
hKleisli :: Monad m => (a -> m b) -> ( (b,c) -> m d ) -> (a,c) -> m d
hKleisli f g = runKleisli $
first (Kleisli f) >>> Kleisli g
--| | |- this is just boilerplate
--| |- This composes Categories
--|- this converts f into a function operating in tuples
{--
Kleisli f :: Kleisli m a b -- a -> m b
---------------------------------------------
first (Kleisli f) :: Kleisli m (a,c) (b,c) -- (a,c) -> m (b,c)
Kleisli g :: Kleisli m (b,c) d -- (b,c) -> m d
---------------------------------------------
first (Kleisli f)
>>> Kleisli g :: Kleisli m (a,c) d -- (a,c) -> m d
--}
關於您的評論:最初的問題是:我們如何在柯里化g
后組成f
和g
? 而且我的解決方案看起來更像是讓我們 uncurry f
與g
一起工作,所以我同意這不是一個完整的解決方案。 好的,讓我們解決您的問題,但首先,一些注意事項:
h:: a -> c -> md
應該很清楚,我們想要一些行為類似於m
但將c
納入考慮范圍的 monad。f:: a -> mb
的類型我們知道f
無法訪問c
並且應該以某種方式將它帶入 scope。 否則, f
和h
永遠不可能是同一個單子。const. f:: a -> c -> mb
const. f:: a -> c -> mb
到目前為止,我們有
{--
The name of the type variables are chosen to match the ones used in this post, but are different in ghci
f :: a -> m b
g :: (b,c) -> m d
const . f :: a -> c -> m b
curry g :: b -> c -> m d
--}
現在很明顯,我們需要使用一些帶有const. f
const. f
和curry g
但問題是我們需要保留 monad m
並且除非我們將結果包裝成一些新的數據類型,否則無法實現,否則,我們將引用的 monad 是 function monad (->)
(這是 haskell 特定的嗎?我認為不是)。 顯而易見的選擇是使用Kleisli
monad ( ghc >= 8.10
)。 所以現在我們有:
{--
The name of the type variables are chosen to match the ones used in this post, but are different in ghci
f :: a -> m b
g :: (b,c) -> m d
const . f :: a -> c -> m b
curry g :: b -> c -> m d
|- This result lives in the -> monad
Kleisli . const . f :: a -> Kleisli m c b
Kleisli . curry g :: b -> Kleisli m c b
--}
import Control.Monad
import Control.Arrow
f :: Monad m => a -> m b
f = undefined
g :: Monad m => (b, c) -> m d
g = undefined
-- And now, We have curryed and composed g
h :: Monad m => a -> c -> m b
h = runKleisli . (f' >=> g')
where
f' :: Monad m => a -> Kleisli m c b
f' = Kleisli . const . f
g' :: Monad m => b -> Kleisli m c d
g' = Kleisli . curry g
請注意,這可以使用與Kleisli
不同的 monad 來完成。 可能所有解決方案都是同構的 curry / uncurry。 只要您可以將c
帶到f
的 scope 並找到一個保留m
行為的 monad,您就可以應用它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.