読者です 読者をやめる 読者になる 読者になる

Creatable a => a -> IO b

Haskellと数学とちょびっと音楽

試行錯誤が大切だという話

あ、どうも、最近残業続きで体力的にアレな感じなちゅーんさんです。 そしてですね、実際のとこ、やることいっっっっぱいあるのですが、うふふふ。

こんなツイートを見かけてしまって、考えていたら楽しくなってきて酷い時間になってしまいました。 つまるところ、プログラムに「試行錯誤」させる仕組みを作りたいわけですね。

ではでは、そろそろ寝ないとヤバイのでちゃっちゃと纏めます。 なに、簡単簡単・・・

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。

で、Haskellの場合です。

実際の所、例外機構ってEitherモナドなので、LeftRightをひっくり返して使えば良いのですが(というか上のJavaの例はEitherから思いついた)、それではちょっと解りづらいので、モナド変換子のトレーニングも兼ねて、簡単に再実装してみましょーか。

まず、Control.MonadControl.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)

で、入出力を行いたいので、型変数mIOにした型を宣言・・・

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使うのが良いかと思いました。まる。