なにを言ってるんだお前という話だが、仕事でScalaのcatsというライブラリを使っている。が、全く持ってサッパリわからなくて困っている。

これはつまるところ要するに関数型プログラミングのエッセンスがサッパリわからないのが原因だと思っていて、例えば モナド, 代数的データ型, サンク (thunk)などなど…これらでググってもだいたいHaskellがでてくる。どうしたものかと思っていたところ、Haskellをサラッとさらえそうなサイトが見つかったので一通り読んでみることにした。

ウォークスルーHaskell

なお、Haskellを学ぶのが目的ではないので写経とかは(ほとんど)やってない。

関数の型の定義

Ocamlを少しやったときにも思ったが、このHaskellの関数の型の定義がやっぱりわかりにくい…わかりにくいなぁ。と思っていたけど、後述するカリー化がわかれば理解しやすい。

1
2
3
4
5
6
-- Haskell
f :: Int -> Int -> Int
f x y => x * y

-- lambda
f = \x -> \y -> x * y

パターン

ウォークスルーHaskell: パターン

Scalaの_とか使い始めてしばらく知らなかったし@の as patternもついこないだまで知らなかったんですが、最初の方に書いてあった。意味も同じ。

1
2
3
4
5
6
7
// Scala
scala> val x@(a, b, c, d) = (1, "hoge", true, 99.99)
x: (Int, String, Boolean, Double) = (1,hoge,true,99.99) // as pattern
a: Int = 1
b: String = hoge
c: Boolean = true
d: Double = 99.99

部分適用とカリー化

ウォークスルーHaskell: 関数

  1. 関数の部分適用
    部分適用の可能な関数のことを,カリー化 された(curried)関数

もうカリー化は100回くらい調べて100回くらい忘れているのですが、こう一行で書いてもらえるとスゴくわかりやすい。以下、Scalaで書く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Scala
// メソッド: 部分適用できない
scala> def f (x: Int, y: Int): Int = x * y
f: (x: Int, y: Int) Int

// 無名関数(ラムダ): 部分適用できない
scala> val f = (x: Int, y: Int) => x * y
f: (Int, Int) => Int =

// カリー化: 部分適用できる
scala> val fcurried = (x: Int) => ((y: Int) => x * y)
fcurried: Int => (Int => Int) = // Int -> Int -> Int と同じ

scala> fcurried(2)
res0: Int => Int =

scala> res0(3)
res1: Int = 6

scala> fcurried(2)(4)
res2: Int = 8

カリー化を理解するとimplicit parameterはそういうことだったのかというのがわかる。101回目の忘却がないように頑張りたい。

代数的データ型

ウォークスルーHaskell: 代数的データ型だとわからなかったので調べた。ざっと調べた感じ下記の3記事がよさそう。

ソフトウェア技法: No.6 (直積型と代数的データ型)
代数的データ型と初等代数学
「ADT, 直和・直積, State Machine」

1番上の「3.1 単純な場合 (バリアント型)」をサッと読んだ感じTupleSetじゃね??というような感じがするんだけど、どうなんだろ…。特に3番目のやつは後でゆっくり読もうと思う。今日はもう厳しいので無理。

ファンクタとliftingと高階関数

ウォークスルーHaskell: モナドの項でいきなりファンクタというのが出てきてイミフになってしまった。うーん…下記の辺りが参考になりそう。

Cats: Functor

liftingは複数の意味合いがありそう。

What is “lifting” in Scala?
lift関数いいすね

What is “lifting” in Scala?を見る限り、とりあえずその中の一つはメソッドの後ろに_を書くと関数になるというのはわかった。

1
2
3
4
5
6
// Scala
scala> def f (x: Int, y: Int):Int = x * y
f: (x: Int, y: Int)Int

scala> val f2 = f _
hoge: (Int, Int) => Int =

これをやって(Scalaの)高階関数の引数にメソッドがとれるのはおかしくないか??と思った。(メソッドは第一級オブジェクトではないので)

高階関数の引数にメソッドを渡す時に別に前述のようなliftをしたことはない。ので、コンパイラが良い感じにやってくれてるのかな。と思って調べたらドキュメントにもその旨が書いてあった。コードを書くとこんな感じか。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// メソッド f
scala> def f (x: Int, y: Int):Int = x * y
f: (x: Int, y: Int)Int

// メソッド f を lift して関数 f2 にする
scala> val f2 = f _
f2: (Int, Int) => Int =

// 高階関数
scala> def f3(f: (Int, Int) => Int, x:Int, y: Int): Int = f(x, y) * f(x, y) + f(x, y)
f3: (f: (Int, Int) => Int, x: Int, y: Int)Int

// f はメソッドだが、引数で渡すときにコンパイラが関数に変換してくれる
scala> f3(f, 2, 3)
res0: Int = 42

// f2 はもともと関数
scala> f3(f2, 2, 3)
res1: Int = 42

モナド

ウォークスルーHaskell: モナド
ファンクタで見たさっきの図の矢印の向きを少し変えると,モナドを説明した図ができあがります

もうこれが全く持って意味が解らない。意味が解らないのでちょっと後回しにしようと思う。

所感


とりあえずモナドのとこ以外はサッと全部読んだ感じ。Haskell以前の問題が多い気がする…。代数的データ型とファンクタを抑えてから頑張ってモナドをもっかい読む方向にできればいいかな…。