試行錯誤が大切だという話
あ、どうも、最近残業続きで体力的にアレな感じなちゅーんさんです。 そしてですね、実際のとこ、やることいっっっっぱいあるのですが、うふふふ。
Aの処理に失敗したらBをやり、それにも失敗したらCをやり、そこで失敗したら例外をスローする。どの処理でも成功したらDの処理を実行する。こういったことやりたいのかなりあるんだけど、どうしよっかなー。
— ざっきー dev@呪術系エンジニア (@zakky_dev) 2014, 3月 26
こんなツイートを見かけてしまって、考えていたら楽しくなってきて酷い時間になってしまいました。 つまるところ、プログラムに「試行錯誤」させる仕組みを作りたいわけですね。
ではでは、そろそろ寝ないとヤバイのでちゃっちゃと纏めます。 なに、簡単簡単・・・
public class Main { private static void procces(boolean success, String procName){ if(success){ System.out.println("処理 " + procName + " 成功"); throw new RuntimeException(); } System.out.println("処理 " + procName + " 失敗。。。"); } public static void run(){ try{ procces(false, "A"); procces(true, "B"); procces(false, "C"); }catch(Exception ex){ System.out.println("処理 D 実行"); return; } throw new RuntimeException("全部失敗したよ!"); } public static void main(String[] args) { run(); } }
うわあああ!ごめんなさいごめんなさい!嘘です、冗談です!ちょっと思いついちゃったんです出来心です。
はい
@yubaさんのjava8での解答がエレガントなので貼っておきます。ラムダ式が使えれば良いのでC#でもOK。
@its_out_of_tune https://t.co/N8iDJEsYfb こんなのいかがでしょうかね。
— ゆば大好き (@yuba) 2014, 3月 26
で、Haskellの場合です。
実際の所、例外機構ってEither
モナドなので、Left
とRight
をひっくり返して使えば良いのですが(というか上のJavaの例はEither
から思いついた)、それではちょっと解りづらいので、モナド変換子のトレーニングも兼ねて、簡単に再実装してみましょーか。
まず、Control.Monad
とControl.Monad.Trans
をインポートしときます。
そしたら、ベースとなるTryモナドを作りましょう。ぶっちゃけEither
そのまんまです。
data Try a b = Success a | Failed b deriving Show instance Monad (Try a) where return x = Failed x m >>= k = case m of Success x -> Success x Failed x -> k x
んで、モナド変換子。
bindは最終的にTryT
型を返せば良いので、データコンストラクタにdo構文を食わせてやります。
今まで1から作ったこと無かったので、ほぼMaybeT
の写経プログラミングなのはないしょです。
newtype TryT a m b = TryT { runTryT :: m (Try a b) } instance MonadTrans (TryT a) where lift = TryT . liftM Failed instance Monad m => Monad (TryT a m) where return = lift . return x >>= f = TryT $ do v <- runTryT x case v of Success y -> return $ Success y Failed y -> runTryT (f y)
モナド変換子の手続き中で成功や失敗を表す関数を書いてやります。
failure
って実質return
なんですけどね。
success :: Monad m => a -> TryT a m b success x = TryT (return $ Success x) failure :: Monad m => b -> TryT a m b failure x = TryT (return $ Failed x)
で、入出力を行いたいので、型変数m
をIO
にした型を宣言・・・
type TryIO a b = TryT a IO b runTryIO :: TryIO a b -> IO (Try a b) runTryIO = runTryT
あっ、ここはMonadIO
にすべきだったorz
http://hackage.haskell.org/package/transformers-0.2.2.0/docs/Control-Monad-IO-Class.html
で、テストー
process :: Bool -> String -> TryIO () () process True s = do lift . putStrLn $ "Process " ++ s ++ " success!" success () process False s = do lift . putStrLn $ "Process " ++ s ++ " failed!" failure () main :: IO () main = do v <- runTryIO $ do process False "A" process True "B" process False "C" case v of Success x -> putStrLn "Process D to be run!" Failed x -> putStrLn "All process failed!"
こんだけ書くのに約1時間。
うええ、まだまだHaskell力が全然足りませぬー。。。
全体はこっち:https://gist.github.com/tokiwoousaka/9786835
あ、あと、Haskellでこういう事したい時は、素直にEitherT
使うのが良いかと思いました。まる。