なんとな~くしあわせ?の日記

JavaとかAWSの設定とかをメモする技術ブログ

すごいHaskell たのしく学ぼう 読解1

動機

Java, C++Scalaとやってきて、気づいたことはJavaからScalaに受け継がれたのはJVMとしての便利さだけであり、関数型言語的要素はむしろHaskellから受け継がれているということだ。さっそく調査班はYodobashiで以下を購入した。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

プログラミング言語における学習では新しい概念を知ることが大事だと思っているので(…もちろんアルゴリズムも重要だが、アルゴリズムは概念によりかなり変化する)、新しい考え方だと感じたことを中心にメモする。

読み進めるときには、以下を見ながらだと面白い
Scalaコレクションメソッドメモ(Hishidama's Scala collection method Memo)

第1章 はじめの第一歩

まずはhaskellによる四則演算、否定、リストとか。

1.3 リスト入門

ここの部分、まるっきりScalaのリスト操作です。

head, tail, init, last

1~5までの整数を含んだリストを作った時、上記のメソッドは以下のような動きをします。数値だと実用性が感じられないかもしれないですが、例えばリストの中身がテキストの1行を表すStringだとしたら便利な気がしてきます。

scala> val list = List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)
scala> list.head
res0: Int = 1
scala> list.tail
res1: List[Int] = List(2, 3, 4, 5)
scala> list.init
res2: List[Int] = List(1, 2, 3, 4)
scala> list.last
res3: Int = 5
1.4 レンジでチン!

・リストを [0..20] のような表記で作成できるレンジの話
無限リストの話

無限リストは知らないけど、Scalaでもそのようなことはできるし、D言語でも出来た気がする。
Scala Seqメモ(Hishidama's Scala Seq Memo)

1.5 リスト内包表記

・Pythonista(Python使い)からネット上で百万回聞かされたリスト内包表記です、何が嬉しいんでしょうね???

1.6 タプル

タプルです。タプルはパターンマッチングと組み合わせると条件分岐をかなり美しく書けることを最近教わりました。

// Javaなら
if (isHoge && isFuga) {
  // うぐぐ…
} else if (isHoge && !isFuga)
  // バグる、絶対にバグる!
} else {
  // ここはこれでいいんだっけ
}
// Scalaで
(isHoge, isFuga) match {
  case (true, true) =>
    // いえい
  case (true, false) =>
    //  おー
  case (_, _) =>
    // 後は知らん!
}

2要素、3要素と増えていく内にありがたみが倍になる(当社比)。返り値にもタプルとか使える気がする。

第2章 型を信じろ!

タイトルそのままです

2.4 型クラス 初級講座

Scala勉強会で気になっていた概念、型クラスが出てきます。引用…

型クラスは、何らかの振る舞いを定義するインターフェイスです。ある型クラスのインスタンスである型は、その型クラスが記述する振る舞いを実装します。

型クラス自体の概念は簡単に見えます。それはなんかのインターフェースを決めておくので、実装する側が実際の処理を書くのです。Javaのabstract classとかinterface、C++のvirtualに近いような気がします。

第3章 関数の構文

Haskell特有の構文について

3.1 パターンマッチ

死ぬほど実用的なパターンマッチが出てきます。
Scala matchメモ(Hishidama's Scala match Memo)

3.3 where?!

Haskellは純粋関数型言語なので計算結果を一時的な結果として保存するためにwhereを使うらしいです

3.4 let It Be

letも同じようなもんらしいですが、こちらは式らしい

第4章 Hello 再帰

再帰関数を実装しまくる気持ち悪い章です

4.4 再帰的に考える

再帰関数の書き方についての指南があります。

再帰を書くには定跡があります。まず、再帰に頼らない自明な解を持つ基底部から始めます。…云々

別に再帰関数自体はがんばらなくても書ける気がするので、がんばって再帰関数にしたい時にこれに従えば良いのでしょうか。つまり、末尾再帰にしたい場合のお話だと思うのです。ちなみにScalaだと末尾再帰であるかどうかを判別するアノテーションがあるらしいですよ… Scala @tailrecメモ(Hishidama's Scala @tailrec Memo)

再帰するメソッド(自分自身を呼び出すメソッド)は、呼び出す回数が多くなるとその分スタックフレームを消費してしまう(最悪はOutOfMemoryErrorが発生する)が、末尾再帰だと効率の良いループに変換してくれる。

第5章 高階関数

ネット上で難しそうに書かれている概念群が、優しい筆致で書かれた解説により脳内のイメージと結びつく章です。

高階関数
Haskellの関数は、引数として関数を取ったり返り値として関数を返したりできます。

5.1 カリー化関数

カリー化関数は、複数の引数を取る代わりに、常にちょうど一つの引数を取る関数です。

5.3 関数プログラマの道具箱

いよいよあのScalaと同じコレクション操作関数群が現れます。ソースコードの例は、以下を見たほうが楽しいでしょう。
Scalaコレクションメソッドメモ(Hishidama's Scala collection method Memo)

だいたいこのへんのコレクション操作関数は関数を引数に取るので、高階関数というわけです。
すごいですね、もうみんな関数型プログラマですよ!*1

関数 引数
map 関数とリスト
filter 述語*2とリスト
5.4 ラムダ式

はい、ラムダ式です。Java8でも実装されたので、もはやあまり目新しい概念でもないですね。
今一度昔の日記でC++などでどう書くか振り返ると、nantonaku-shiawase.hatenablog.com

そう、こう書いていたのでした

// C++
[] (type arguments) -> type { 式 }

これが、Haskellだとこう書ける

ghci> map (\x -> x + 3) [1, 6, 3, 2]
[4, 9, 6, 5]

Scalaだと

scala> List(1, 6, 3, 2).map { x => x + 3 }
res0: List[Int] = List(4, 9, 6, 5)
5.5 畳み込み、見込みアリ!

foldl, foldrScalaにおけるfoldLeft, foldRight。
これらは単純なデータ処理の場合あまり役に立たない気もするが、アキュムレータという名前で後で再度紹介されてる。

5.6 $を使った関数適用

Haskellでの省略表現。

5.7 関数合成

この辺の話に相当scalamanual.blog.fc2.com

ポイントフリースタイル
関数合成をすることで、関数の書き方が変わる。

// これが
fn x = ceiling (negate (tan (cos (max 50 x)))

// こうなる
fn = ceiling . negate . tan . cos . max 50

5章まではこれでおしまい!

*1:Scala使っててコレクション関数使わないプログラマはいないので、関数型プログラマなんてたくさんいるということです。

*2:結局これも関数