この記事は Crowdworks Advent Calendar 2015 16日目の記事になります.
こんにちは、どうでしょう藩士の飯田です。
DVD第24弾「ユーコン川160キロ~地獄の6日間~」が本日から予約が始まりました!
ユーコンは名脇役(女性現地ガイド熊谷さん)あり、名言ありなので今から楽しみです!!
ちなみに個人的には「ひとり不思議発見」が好きですw
はじめに
前置きはこれくらいにして、本題に入ります。
Scalaで関数型言語を初めて触り、関数型言語に興味を持ちました。
関数型言語とは?
ちなみに関数型言語とはどんな言語のことを言うのでしょうか?
関数型言語とは?(Wikipediaからの引用)
関数型言語(かんすうがたげんご、英: functional language)は、以下に述べる関数型プログラミングを基本スタイルとして推奨する機能を持つプログラミング言語、関数型プログラミング言語[1]の略称である。
つまり、関数型プログラミングとは?(Wikipediaからの引用)
何をもって関数型プログラミングとするか、関数型プログラミングを行っているコミュニティ内でも正確な定義や合意というものは存在しないが、一般的には、手続き型プログラミングがコマンド実行の列としてプログラムを記述していくのに対し、関数型プログラミングは複数の式を関数の適用によって組み合わせていくプログラミングスタイルである、ということは広く認められている。
と、いうことなんですね。
確かにScalaで実装するとき関数を組み合わせてコーディングしてました。
ここが手続き型プログラミングとの違いで、とっつきにくい部分かもしれないです。
関数型に慣れてくるとこの組み合わせる感覚がビシっとハマった時気持ちいいんですよねー
メリットは?
あと、関数型言語のメリットです。(お約束的な)
- 第一級関数・高階関数
- 副作用がない
副作用がない ことによって、テストがしやすかったり、部分プログラム化しやすかったりなどの恩恵を受けることが出来ます。
代表的な関数型言語
そういえば、Scala以外にどんな関数型言語があるのでしょうか?
調べたら結構いろんな言語がありました、結構ね。
| 言語 | 純粋さ | 型付け |
|---|---|---|
| Clean | 純粋 | 強い、静的 |
| Erlang | 非純粋 | 動的 |
| F# | 非純粋 | 強い、静的 |
| Haskell | 純粋 | 強い、静的 |
| Idris | 純粋 | 強い、静的 |
| Lazy K | 純粋 | 型なし |
| LISP | 非純粋 | 動的 |
| Miranda | 純粋 | 強い、静的 |
| ML | 非純粋 | 強い、静的 |
| SML# | 非純粋 | 強い、静的 |
| Standard ML | 非純粋 | 強い、静的 |
| OCaml | 非純粋 | 強い、静的 |
| Scala | 非純粋 | 強い、静的 |
| Scheme | 非純粋 | 動的 |
| Unlambda | 非純粋 | 型なし |
| ※Wikipedia引用 |
非純粋と純粋がある
また、関数型って非純粋、純粋の2パターンあるんですね!
純粋な関数型どんなのか気になる!
という単純な興味本位から今回、純粋関数型言語Haskellを触ってみました!
ちなみに、純粋関数型言語とは?(Wikipediaより引用)
純粋関数型言語では、参照透過性が常に保たれるという意味において、全ての式や関数の評価時に副作用を生まない。純粋関数型言語であるHaskellやCleanは非正格な評価を基本としており、引数はデフォルトで遅延評価される。一方、Idrisは純粋だが正格評価を採用している。入出力などを参照透過性を保ったまま実現するために、たとえば Haskell ではモナド、Clean では一意型(英語版)という特殊な型を通して一貫性のある表現を提供する。
つまり、副作用が全くないない関数型言語ということです。
なるほど、確かにScalaは再代入できる(varなど)ので非純粋とうことで納得です![]()
- 変数定義の時に値を設定し、その後値を再代入できなければ副作用は起きない
なぜ今回、純粋関数言語の中でHaskellを選んだかは、関数型言語で調べた時にLISPやHaskellがよく検索でひっかかり、その中で静的型付けがHaskellだったからです。
Haskellのインストール
まずはHaskellが実行できるようにするため、Macにインストールしていきます。
※ Windowsはちょっとわかりません・・・
brew でサクッとインストールできるんですが、バージョンが古くなるということで
今回はパッケージからインストールします。
ghc をイントールする
- http://ghcformacosx.github.io/ からzipファイルをダウンロードして、インストールする
- zipファイルを解凍し、実行ファイルを Applicationフォルダに移動します
- ファイルを実行し
Append to ~/.bash_profileをクリックし、Modifyを選択する
以上でインストールは終わりです、簡単ですね!
ghcが無事インストールされたか確認します
$ ghci
GHCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help
Prelude> print "Hello World"
"Hello World"
Prelude>:quit
Leaving GHCi.
はい、お決まりのHello Worldが出力されました。
めでたしめでたし。
Haskellを使ってみた
フィボナッチ数列
関数型言語は再帰が得意ということなので、フィボナッチ数列をやってみました(正確には調べました)。
fibonacci = 1:1:zipWith (+) fibonacci (tail fibonacci)
ホント1行でかけるんですね、でもかなりキモいです。
※このページを参考にするとわかりやすいです。
そして、実行するとこんな感じ。
$ ghci
HCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help
Prelude> :l fibonacci.hs
[1 of 1] Compiling Main ( fibonacci.hs, interpreted )
Ok, modules loaded: Main.
*Main> fibonacci
[1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,・・・
と、メモリを食い尽くすまで無限に実行されますw
ちなみにこれをJavaで書くとこんな感じです。
Haskellと違いfor文を使い、再代入(i++やa[i] = a[i-1] + a[i-2]など)が発生してます。
import java.io.*;
public class Fibonacci
{
public static void main(String[] args) throws Exception{
int num = Integer.parseInt(args[0]);
int[] a;
a = new int [num];
for(int i = 1; i < num; i++){
if(i == 1){
a[i] = 1;
}
else{
a[i] = a[i-1] + a[i-2];
}
System.out.println(a[i]);
}
}
}
Haskellに比べるとはるかにコード量が多いですよね。
関数型は再起処理が書きやすいのが見て分かります。
ただフィボナッチ数列だけでは、非純粋 でも同じような恩恵を受けられるので、
まだ純粋のこんなところが良いというのは無いですね。
ちなみにScalaでもHaskellと同じく1行でかけます。
HaskellよりScalaのが読みやすい(慣れの問題ですかね?)のでScalaで十分かなって感じです。
val fibonacchi: Stream[Int] = 1 #:: fibonacchi.scanLeft(1)(_ + _)
最後に
純粋は非純粋に比べて◯◯的なことを書こうと思ってたのですが、
関数型言語の話になってしまいました。。。すみません。
次回はHaskellで何かアプリを作って、こんな時副作用がなくてよかった、副作用が欲しかったなどを書こうかと思います。
エンジニア絶賛募集中!
弊社では自らの技術で「21世紀の新しいワークスタイル」を実現したいエンジニア募集しています!
https://www.wantedly.com/projects/34041