アプリカティブとモナド

Posted on | 322 words | ~2 mins

がわからなかったが、わかった気がした人間のメモ

  • “わかる"とは
    • 定義がわかる
    • 作用のしくみがわかる
    • ありがたみがわかる

Context

わかっていること(たぶん)

  • ファンクター(関手)
    • 「文脈をもつ」値(以降、「箱」で表現する。個人的にイメージがしっくりくる為)
    • 文脈what
      • 「値があるかもしれないし、ないかもしれない」文脈
        • HaskellのMaybe
        • SwiftのOptional
      • 「成功しているかもしれないし、失敗しているかもしれない」文脈
        • HaskellのEither
        • SwiftのResult
    • fmap実装をもつ
      • fmap
        • Haskell
          class Functor f where
              fmap :: (a -> b) -> f a -> f b
          
        • Swift Optional
          func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
          
        • 通常の関数を受け取って、箱の中身に適用した結果で箱の中身を差し替える

わかった気がすること

a.) アプリカティブ

  • 「ある関数を複数の箱に対して適用」できるようにする関数
    • コード(swift)
      precedencegroup Applicative { associativity: left }
      infix operator <^>: Applicative
      infix operator <*>: Applicative
      
      func fmap<T, U, R>(f: @escaping ((T, U) -> R), x: T?) -> ((U) -> R)? {
        x.map { _x in { u in f(_x, u) } }
      }
      
      func <^><T, U, R>(f: @escaping ((T, U) -> R), x: T?) -> ((U) -> R)? {
        fmap(f: f, x: x)
      }
      
      func <*><U, R>(f: ((U) -> R)?, x: U?) -> R? {
        guard let f = f else { return nil }
        return x.map(f)
      }
      
      
      do {
        let x: Int? = 2
      
        // (+)のような、2引数関数を適用しようとすると、
        // 箱の中に関数((Int) -> Int)が入ってしまう
        let f: Optional<(Int) -> Int>
        f = x.map { lhs in { rhs in lhs + rhs }}
      
        let y: Int? = 3
        // mapではできない
        // Optional<Optional<Int>>になって箱が2重になってしまう
        let result = f.map { _f in y.map(_f)}
        print(result) // Optional(Optional(5))
      
        // Applicative(<*>)を定義してアプリカティブスタイル
        // 関数適用可能な形に(2引数 -> 1引数に)curry化するfmapと、
        // fmap結果の箱の中身が関数になったものと更に適用する箱を受け取って、
        // 2引数目の箱にfmap結果の関数を適用する<*>(アプリカティブ)を定義
        // fmap内の1引数目のmapでnilなら箱の関数はnilになり、<*>のguard節で結果がnilになる
        // 1引数目が.someで箱の関数が.someで返却された上で、2引数目の箱がnilなら、やはり結果がnilになる
        // つまり関数適用対象の「いずれかがnilなら結果がnilになる」ことを保証しつつ、<*>をつなぎあわせるだけで、
        // 箱として扱ったまま関数適用できる
        let result2 = fmap(f: (+), x: x) <*> y
        print(result2) // Optional(5)
      
        // fmapをinfix operator化したもの。Swiftでは`$`をoperatorとして使えないので、<^>を定義している
        let result3 = (+) <^> x <*> y
        print(result3) // Optional(5)
      }
      
  • なにがうれしいか
    • 箱を箱として扱いつつ(unwrap操作不要で)、関数適用できるのがうれしい

b.) モナド

  • 「flatMapできる箱」
    • flatMap
      • Haskell
        (>>=) :: m a -> (a -> m b) -> m b
        
      • Swift Optional
        flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
        
      • 「箱」と、「箱の中身を受け取って別の箱を返却する関数」を受け取って、別の箱を得る関数
  • なにがうれしいか
    • 箱の世界を保ったまま、箱の中身に対してふつうの関数適用(map)のみならず、箱自体を差し替えることも可能になるのがうれしい
  • コード(swift)
    func possibleFoo(_ x: Int) -> Int? {
      x
    }
    
    func possibleBar(_ x: Int) -> Int? {
      x
    }
    
    func possibleBaz(_ x: Int) -> Int? {
      nil
    }
    let x: Int? = 2
    x.flatMap(possibleFoo).flatMap(possibleBar) // Optional(2)
    x.flatMap(possibleFoo).flatMap(possibleBar).flatMap(possibleBaz) // nil
    
    • unwrapせず、箱(Optional)のまま引き回せる
    • 通しで成功したら値が得られて、どこかで失敗したらnilが返る

わかっていないこと

  • モノイドがよくわかっていない🤔

参考リンク