12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

P&D - Planning and Development -Advent Calendar 2018

Day 24

Monad入門以前: (Functor,Applicative)

Last updated at Posted at 2018-12-23

Haskellを学ぶに当たって避けて通れないのがMonadですね(?)
しかしいきなりMonadを理解するのは難しいかと思います.

なので, まずFunctorとApplicativeを理解してみようというお話です.

FunctorとApplicativeがどうMonadと繋がるのか

モナドの定義をみてみましょう

class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  (>>) :: m a -> m b -> m b
  return :: a -> m a
  fail :: String -> m a

またApplicativeの定義をみてみましょう

class (Functor f) => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

となっています.
Monad構造を持つものはApplicative構造を持っています.
またApplicative構造を持つものはFunctor構造を持っています.
Functor => Applicative => Monad
よってMonadを学ぶにはFunctorとApplicativeを理解する必要があると思います.

Functor

Functorとは?

Functorとは全体を写せる(map over)ものの型クラスです.
よくわかりませんね.
一つ例をあげるとリストはFunctorに属します.
リストのメソッドにmapというメソッドがあります. よく使われますね.
Swiftでの例をみてみましょう.

func double(x: Int) -> Int {
    return x * 2
}
let x = [1,2,3]
let y = x.map(double) // [2,4,6]

mapはどのような動きをしているかというと, xの要素である1,2,3に対してdoubleを適用し, リストに包んで返しています.
つまり, この例では
全体: リストに包む
写す: doubleを適用
と対応が取れます.
これらからFunctorは元の構造を保ったまま, 関数が適用できるような構造のことを指します.

Functorの実装を見る

HaskellのFunctor型クラスを見てみましょう.実装は以下のようになっています

class Functor f where
  fmap :: (a -> b) -> f a -> f b

これだけです.それではfmapを詳しくみてみましょう.
fmapの第一引数は(a型の値をとってb型の値を返す関数)です.特に問題はないですね.
第二引数は(f a)となっています.
これはfという構造を持ったa型の値を表しています. リストで言えばf a = [] a = [a]です.

結局のところ, fmapは何をするのかというと
fという構造を持ったもの(fはa型の値を持つ)に対して何か関数を適用して, fという構造を保ったまま返す
ということをしています.

Optional(Maybe)を実装してみる

さて、上で説明したことをOptional(Maybe)を使って実装してみます.
HaskellとSwiftの二つの言語で実装しました.

Haskell

まずデータ型を定義します.
値を持っていればSomeで値をラップし, 値がない場合はNoneとします.

data Optional a = Some a | None deriving (Eq, Ord, Read, Show)

OptionalはFunctorを準拠します.
Optionalが値を持っている(Some)ならば, Someの中の値に関数を適用して, 再度Someでラップします.
値を持っていない(None)ならば, Noneを返します. (何もないものに関数を適用しても何も起こりませんよね)

instance Functor Optional where
  fmap f (Some x) = Some (f x)
  fmap f None     = None

さて確かめてみましょう.

*Main> x = Some 10
*Main> y = None
*Main> fmap (*2) x
Some 20
*Main> fmap (*2) y
None

うまく動いていますね.

Swift

SwiftではFunctor自体を定義します.

protocol Functor {
    associatedtype A
    associatedtype B
    associatedtype F
    func fmap(f: ((A) -> B)) -> F
}

次にOptionalを定義します.(名前が衝突するのでMyOptionalとしています)
また, MyOptional(T)に対してMyOptional(U)を返す方法がわからなかったので今回は同じ型を返すようにしています(MyOptional(T)に対してMyOptional(T)を返すようにしています)

enum MyOptional<T>: Functor {
    typealias A = T
    typealias B = T
    typealias F = MyOptional
    
    case some(value: T)
    case none
    
    func fmap(f: ((T) -> T)) -> F {
        switch self {
        case .some(let value):
            return .some(value: f(value))
        case .none:
            return .none
        }
    }
}

さて確かめてみましょう.

let x = MyOptional<Int>.some(value: 1)
let y = MyOptional<Int>.none
let z = x.fmap(f: { x in return x * 2 })
let w = y.fmap(f: { x in return x * 2 })
print(x)  // some(value: 1)
print(y)  // none
print(z)  // some(value: 2)
print(w)  // none

うまく動いています.

Applicative

Applicativeとは

先ほどfmap (*2) (Some 1)をFunctorの利用例としてあげました.
実はfmap (*) (Some 2)もできます.
この型はfmap (*) (Some 2) :: Num a => Optional (a -> a)となっています.
書き方を変えるとSome (2 *)です. つまり, Functor値に関数を与えることもできます.

ではこれを踏まえて, Some (2 *)と Some 3があったとして, Some (2 *)Some 3に適用できるでしょうか. 答えはNoです.
残念ながらFunctorだけの世界では, Some (2 *)は使い物になりません.

ここでApplicativeが登場します.
Applicativeな世界ではSome (2 *)Some 3に適用することができます.
つまり, (Some (2 *)) (Some 3) = Some 6を得ることができる世界になるわけです.

Applicativeの実装をみる

HaskellのApplicative型クラスを見てみましょう.実装は以下のようになっています

class (Functor f) => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

詳しくみていきましょう.
まず1行目class (Functor f) => Applicative fについて,「ApplicativeなfはFunctor型クラスを準拠している必要がある.」という制約をつけています.
二行目以降は「Applicativeなfpure<*>の実装を持つ必要がある」と言っています.
pureは「ある値をfで包む, つまりApplicative値に変換する関数」,
<*>は「fで包まれた(a->b)な関数を,fに包まれたa型の値に対して適用してfに包まれたbを返す」
となっています.

Optional(Maybe)を実装する

Functorの説明で実装したOptionalにApplicativeを準拠させます.

haskell

instance Applicative Optional where
  pure = Some
  None <*> _ = None
  (Some f) <*> x = fmap f x

pureはSomeで包みます. pure (*2)とすると, (Some (*2))が得られます.
<*>は (Optional 関数) <*> (Optional 値)のように使います.
左引数(関数)がNoneの場合, 問答無用でNoneを返します.何かにを適用してもです.
左引数(関数)がSomeの場合, Some内の関数を取り出し, fmapを適用します. つまり両辺のSomeから値を取り出し, 適用しSomeに入れて返すということをしています.

確認してみましょう

*Main> Some (+3) <*> Some 2
Some 5
*Main> pure (*2) <*> Some 10
Some 20
*Main> pure (*2) <*> None
None

-- アプリカティブスタイル <*> を連続して使う
*Main> pure (+) <*> Some 3 <*> Some 5
Some 8
*Main> pure (+) <*> Some 3 <*> None
None
*Main> pure (+) <*> None <*> Some 5
None

Swift

Swiftでの実装は以下のようになります

Applicative型クラスを実装したいところでしたが, どうしたらいいかわからなかったのでMyOptionalをextensionしてpureと<*>を実装します. <*>はapplyとして実装します

extension MyOptional {
    static func pure<U>(_ a: U) -> MyOptional<U> {
        return .some(value: a)
    }
    
    func apply(_ f: MyOptional<((T) -> T)>) -> MyOptional<T> {
        switch f {
        case .none:
            return .none
        case .some(let fun):
            return self.fmap(f: fun)
        }
    }
}

確認してみましょう.

let f1 = MyOptional<Int>.pure({ x in x+2 })
let f2 = MyOptional<Int>.pure({ x in x*3 })
let x = MyOptional<Int>.some(value: 2)
let y = x.apply(f1).apply(f2)
print(x) // some(value: 2)
print(y) // some(value: 12)

うまくいっているように見えます.
しかし残念ながら, swiftでCurry化は普通には使えないのでアプリカティブスタイルはかけませんでした.
Curry化を実装すれば, できそうです.

それは辛いので今回は実装しません.

あとがき

Haskellを書き始めて二ヶ月程度なので, 変なことを言っている可能性があります.
そういったところはマサカリを投げていただけると助かります.

参考文献

すごいHaskell楽しく学ぼう!

12
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?