2016-03-21 7 views
0

私はいくつかのPythonコードをHaskellに変換しています。ビジネスロジックはかなり複雑で、私のHaskellコードは醜いものになっています。アクションを収集し、必要に応じて早く分岐する

私のPython関数:

def f(some_state): 
    doAction1() # e.g. send_message("Hi there!") 
    if not userIsAuthenticated: 
     doAction2() # e.g. send_message("Please login") 
     return 
    if not userHasPermission: 
     doAction3() # e.g. send_message("Please sudo") 
     return 
    if somePredicate3: 
     doAction4() 
     if somePredicate4: 
      doAction5() 
    return 

私はHaskellのバージョンのための2つの制約があります。可能な限り純粋な私のコードを保つために

  1. を、私は内のすべての私の行動を収集したいと思いますリスト(または私が気に入っているならFree Monad)して(関数外で)それを実行します。 Pythonでは、私の関数の冒頭にtmp = []を定義し、tmpに(呼び出しを行わずに)すべてのアクションを追加し、tmpを返します。それから、私はすべてのアクションを実行するためにリストを繰り返します(Pythonicではなく!)。
  2. ないこのようなプッシュ・ツー・右狂気で終わる:

- 基本的に、私は、必要に応じて早期に行動して終了のリストを収集する

if a 
then undefined 
else if b 
    then undefined 
    else if c 
      then undefined 
      else if d 
       then undefined 
       else undefined 

私のような何かを書くことができるように:

tell Action1 
when somePredicate1 $ tell doAction2 
when somePredicate2 $ tell doAction3 
... 

tellはライターモナドからのものではないが、それはfunctioです(これは存在しない)nは、これまでに "言われた"すべてのものを集めて(àla writer monad)、終了時に返されます。

これは、最初の左(そして左の値)の前に起こったすべてのアクションを保持し、最初の左から早く終了する必要があることを除いて、一般化されたモナドのように実際に感じます。私は、おそらくMonadPlusやAlternativeを使って解決策を作っていると思っていますが、何か素敵なことは分かりません。基本的には、ファンシーなguard

私はあまりにも多くの楽しみを持っていることは間違いありませんが、私は自分のコードが正しくドリフトすることを受け入れるべきです。しかしそれはエレガントではないでしょうか?

編集:わかりやすくするために、タイプf :: State -> [Action]の機能が必要です(data Action = Action1 | Action2 | Action3)。

答えて

2

これがそうのように、MTLのControl.Monad.Exceptから、throwErrorの理想的な使用のように聞こえる:

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE RecordWildCards #-} 

module Main where 

import Control.Monad.Except 

data UnlockError = 
    UnlockErrorNoLogin 
    | UnlockErrorNeedsSudo 

data State = State { 
    stateUserIsAuthenticated :: Bool 
    , stateUserHasPermission :: Bool 
    } 

unlock :: MonadError UnlockError m => State -> m String 
unlock State{..} = do 
    action1 
    unless stateUserIsAuthenticated 
     (throwError UnlockErrorNoLogin) 
    unless stateUserHasPermission 
     (throwError UnlockErrorNeedsSudo) 
    return "all" 
    where 
    action1 = return() 

main :: IO() 
main = do 
    state <- conjureState 
    case unlock state of 
    Left UnlockErrorNoLogin -> 
     p "please log in" 
    Left UnlockErrorNeedsSudo -> 
     p "please sudo" 
    Right count -> 
     p ("i love you with " ++ show count ++ " of my heart") 
    where 
    p = 
     putStrLn 
    conjureState = 
     return (State True True) 
+0

おかげで、それは素敵なソリューションです:)私は返すように 'unlock'を'たいと思いますが、[アクション1、 UnlockErrorNoLogin] 'または' [action1、UnlockErrorNeedsSudo] 'を実行します。つまり、 'data Actions = Action1 | ActionIfNoLogin | ActionIfNeedsSudo'私は、関数の実行に依存するアクションのリストを収集したいと思います。しかし、「行動1」はモナドではない。 – cpa

+0

@cpaモナドアクションが実際に上記のエラーの1つに到達するのに十分に実行された場合、本当に 'action1'アクションが実行されます。 –

+0

私のOPはあまり明確ではありませんでしたが(私はそれを更新しました)、アクションを実行するのではなく、実行するアクションのリストを返します。良い夜の眠りの後、私は実際には無料のモナドを再開発していると思う。 – cpa

関連する問題