2017-03-28 30 views
4

我正在苦苦掙扎着榆樹缺乏monad。一個實現Elm狀態monad的庫(http://package.elm-lang.org/packages/folkertdev/elm-state/latest/State)對我有很大的幫助。如何在Elm中結合結果和狀態?

問題是,現在我遇到了交替嵌套Result和狀態類型的情況,當時我只想每個都有一個。

我試着用下面的簽名寫一個函數,但似乎不可能,因爲只有在評估外部狀態時纔會知道內部結果。

join : Result a (State s (Result a (State s x))) -> Result a (State s x) 

也許如果我把結果國家內部的返回值,將工作,但會產生的情況下,一個虛擬的國家外的結果是Err

我認爲正確的想法是做出既是結果又是狀態的東西。熟悉Haskell monad變形金剛的人可以解釋他們如何解決這類問題或提出替代解決方案嗎?

這裏是一個粗略的版本,一個地方的問題出在哪裏出現了:

generateConstraints environment value 
    |> Result.map (State.map (\(value, valueC) -> 
    Result.map 
     (State.map2 (\this (body, bodyC) -> 
     (this 
     , valueC++ bodyC++ [(this, body)] 
     )) 
     freshTypevar) 
     (generateConstraints (extend environment name value) body)) 
) 
+1

當你混合使用相同類型的變壓器時,Monad變壓器是一種痛苦。我通常使用monad類型類,但是當類型出現多次時會失敗。對此的解決方案是新建您所需的特定環境。 –

+0

我並不需要多次擁有相同的monad。我包含的代碼片段是'generateConstraints'的一種情況,它返回'Result String(State Int(Type,List Constraint))'。遞歸工作正常,但是約束生成有一些不同之處,因爲綁定會混淆monad。 – Joonazan

+0

我還是不知道如何將狀態和可能的錯誤結合起來,但是我給出的具體例子奇蹟般地解決了它自己,因爲我能夠將所有特殊的結果排除在外。 – Joonazan

回答

2

我最終寫了一個monad。我必須犧牲在狀態被觸摸之前失敗的能力,因爲我需要能夠在失敗後失敗。

type alias Infer a = State Int (Result String a) 

infer : a -> Infer a 
infer x = 
    State.state (Ok x) 

map : (a -> value) -> Infer a -> Infer value 
map f x = 
    State.map (Result.map f) x 

andThen : (a -> Infer b) -> Infer a -> Infer b 
andThen f x = 
    State.andThen 
    (\r -> case r of 
     Ok v -> f v 
     Err e -> State.state <| Err e 
    ) 
    x 

andMap : Infer y -> Infer (y -> z) -> Infer z 
andMap y = 
    andThen (\g -> map g y) 

map2 : (a -> b -> c) -> Infer a -> Infer b -> Infer c 
map2 f x y = 
    map f x 
    |> andMap y 

map3 : (a -> b -> c -> d) -> Infer a -> Infer b -> Infer c -> Infer d 
map3 f a b c = 
    map2 f a b 
    |> andMap c 

map4 : (a -> b -> c -> d -> e) -> Infer a -> Infer b -> Infer c -> Infer d -> Infer e 
map4 f a b c d = 
    map3 f a b c 
    |> andMap d 
3

人誰是熟悉Haskell的單子變壓器能解釋他們是如何解決這樣的問題或建議的替代解決方案?

那麼,我至少可以試試。這是怎樣的類型看起來直接翻譯成哈斯克爾:

type EffM a s x = Either a (State s x) 

一個相當明顯的觀察是,它不是一個單子轉換。 這是一個變壓器會是什麼樣子:

type TransM a s x = EitherT a (State s) x 

正如你看到的,唯一的變化是T和事實x是括號外面。後一部分對理解變壓器方法至關重要。

其核心思想是State是結果生產的一部分,不管Either是「成功」還是「失敗」,而在您的情況下生成「失敗」意味着狀態操作從未被觸摸。在實踐中,我需要更加努力地思考這意味着什麼,但直觀地說,變換器方法就是您在使用典型的命令式代碼時要記住的內容。

現在,當您使用這種變壓器時,join實際上是免費提供的,因爲它適合於Monad接口。

import Control.Monad.State 
import Control.Monad.Trans.Either 
import Control.Monad 

type Eff e s a = EitherT e (State s) a 

-- type the following in REPL 

λ :t join 
join :: Monad m => m (m a) -> m a 

λ :t join :: Eff e s (Eff e s a) -> Eff e s a 
join :: Eff e s (Eff e s a) -> Eff e s a 
    :: Eff e s (Eff e s a) -> Eff e s a 

-- the slightly cryptic output above means that it typechecks correctly 

所以這是哈斯克爾如何解決它。現在,用這種方式編寫專門的EitherState類型顯然是可能的(儘管我個人將所有例子中的這兩個翻譯爲StateEither - 感覺更自然),這反映了各個變壓器的實現join會做什麼。我不知道在Elm中是否可以寫EitherT


一個可行的方法。還有其他的遞歸方案/免費可能是在未來幾年中要注意的方案。效果疊加的內在排序原本比起初看起來更成問題。

這也是不是一個Monad,至少在這個意義上,x不能在單子實例的直下式(因爲Either可以作爲一個在特殊情況下,顯然行事)。

+1

你有點太慢了;我已經自己解決了這個問題。 EitherT國家會對我的情況起作用嗎?至少我寫它的方式,它只能反過來。 – Joonazan

+0

'join'是免費的,因爲它幾乎與'>> ='相同,對吧?此外,在我和然後有非常醜陋的'Err e - > State.state <| Err e'。我認爲至少在Elm中必須這樣做,因爲'Err e - > x'不會檢查。 – Joonazan

+1

@Joonazan我看到了你的評論,但我想我可能會回答。我認爲這會對你的情況起作用;這些變壓器的順序應該沒有關係。是的,'join'只是'join x = x >> = id'。你的實現的「醜陋」主要來自於你必須同時拆解兩個圖層的事實,而變形金剛可以拆分它並允許稍後組合。 –