ttmsk's note to self

プログラミングのことや趣味のことに関して綴ります。

ギットクエストをやってみた

Gitについて教える機会があり、ギットクエストをちょっと試してみようと思いやってみた。

http://image.itmedia.co.jp/ait/articles/1601/29/si_gitquest-01.jpg

 

最初初めた際は、音がでかすぎて思わずびくってなってしまった。。

 

Gitの基本的な使い方のマニュアルがのっていて、モンスターを倒すために、適切なGitコマンドを選択しないと大きなダメージを与えられない。 よくできているなーと思いつつ、これよくやるなと思ったやつもあったりと。「git reset --hard」で追跡対象となっているファイルのみを元に戻して、「git clean」で追跡対象外のファイルも元に戻すことなど

 

もっと長いストーリーなのかなと思っていたら、5分くらいで終わってしまったので少し残念。

でも面白かったので、手始めの学習としてはありなのかな。

 

関数型プログラミングとは?

自分の中で、「カリー化ってどんなメリットがあるの?」や「モナドってどのようにつかうの?」と、しっかりと関数型プログラミングを理解できていない節があったので、自分の理解のために記録したものです。 
間違っている点や理解が不足している点があればご指摘をいただけると幸いですm(_ _)m

そもそも関数型プログラミングとは?

ざっくりと言えば、

「プログラムの構成に、副作用のない関数を利用し、組み立てていくプログラミングのスタイル」

のことである。 

暫し、オブジェクト指向のプログラミングのスタイルと比較されることがあるが、オブジェクト指向は、データ(状態)と操作を格納したコンテナを基本要素とすることで様々なメリットを享受することができるプログラミングのスタイルのこと。

オブジェクト指向関数型言語のプログラミングのスタイルの比較は、他で書いてありそうなので割愛。

関数型プログラミングの条件

関数を値として扱うこと(引数や返り値などに利用)で、操作を抽象化し、新しい制御構造を作る便利な手段にもなる。これにより、無名関数、高階関数、カリー化などのメリットが受けられる。

  • 参照透過性を確保できること

参照透過性とは、プログラムの構成要素が同じであれば同じ結果を返す性質のことで、メソッドは、引数をとり、結果値を返す方法だけで環境と通信すること(副作用がないともいう)

結局、関数型プログラミングって何のメリットがあるか?

  • コードのモジュール性が高まる

大規模なソフトウェアを構築するためには、コードが巨大化していく傾向がある。巨大に膨れ上がったコードを複雑にしないために、全体を分割し、小さなモジュールとして構成していく方法がある。

関数型プログラミングでは、データ、変数、関数を関数を使ってつなぎ合わせることでコードを簡潔にさせる。 

- モジュールの独立性

参照透過性は、コードがモジュールの独立性を保証してくれる。そのため、参照透過性を持つデータや変数は、周囲にどのようなコードがあってもその値は不変(イミュータブル)である。また、参照透過な関数の結果は、引数のみに依存し、それ以外のコードの影響も受けない。

- モジュールの汎用性

 どこでも使うことができるためには、関数を組み合わることが重要である。そのためには、関数同士を組み合わせる場面で、引数の数を一致させておくと組み合わせがしやすい。これを行う方法の一つとして、カリー化(引数の個数を一つに限定しておく)がある。

また、汎用性を高めるために、関数渡しも重要である。

  • コードのテストを容易にする

参照透過性のあるコードは依存関係が明確であり、テストを何度繰り返しても結果は不変であるため。

 

関数型プログラミングを支える技術

  • カリー化

カリー化は、複数の引数を持つ関数を一つの引数だけを持つ関数に変換することであり、カリー化関数は高階関数の一部である。
※ 普段は、scalaを書いてますが、javascriptで書いてみます。

/* カリー化されていない multipleOf関数 */
var multipleOf = (n,m) => {
if(m % n === 0) {
return true :
} else {
return false;
}
};
/* カリー化した multipleOf関数 */
var multipleOf = (n) => { // 外側の関数定義
return (m) => { // 内側の関数定義
if(m % n === 0) {
return true :
} else {
return false;
}
};

 

  • コールバック関数によるモジュール化

下記の2つは、コードの結合度が違う。doCall関数とsucc関数は密に結合している。(doCall関数の挙動を変更するにはdoCall関数そのものの定義を書き直す必要がある)

一方で、コールバック関数を間接的に呼び出す例では、コールバック関数を受け取るsetupCallback関数とコールバック関数であるsucc関数が分離されている。そのため、モジュールとして独立している。

/* 直接的な呼び出し例 */
var succ = (n) => {
return n + 1;
};

var doCall = (arg) => {
return succ(arg) // succ関数を直接呼び出す
};

/* 呼び出し */
doCall(2)
/* 単純なコールバックの例 */
var setupCallBack = (callback) => {
/* コールバック関数を実行する無名関数を返す */
return (arg) => {
return callback(arg);
};
};

var doCallback = setupCallback(succ);
/* 呼び出し */
doCallback(2)
  • 継続渡し

どちらかというと、反復処理をさせるものと条件分岐をさせるもので分離したほうが良い。条件分岐の時は、成功継続と失敗継続など分けることが多い。

 

/* 継続渡しのsucc関数 */ 
var succ = (n, continues) => {
return continues(n + 1);
};
/* 継続渡しのadd関数 */
var add = (n,m, continues) => {
return continues(n + m);
};
/* 継続渡しのsucc関数とadd関数を使って add(2, succ(3)) を計算する */
succ(3, (succResult) => {
return add(2, succResult, identity);
})

高階関数を利用したデザインパターンの一つで、値にコンテキストを付与し、コンテキストを付与したまま処理を合成することで実現。

 

モナドの基本構造
  • unit関数

モナドインスタンスを生成するための関数。T => M[T] という型情報を持つ。Mはモナドの型のこと。

  • flatMap関数

モナドから値を取り出して、何らかの処理をし施した後に、その結果を再びモナドに詰め込むための関数。

 

モナド

モナドインスタンスから値を取り出して、unit関数を適用した結果は、元のモナドインスタンスに等しい

  • 左単位元則

ある値から作られたモナドインスタンスに対してflatMap関数を介してf関数を適用した結果は、元の値に対してf関数を適用した結果に等しい

モナドの処理を結合することができる

 

- 恒等モナド

値にコンテキストを付加することなく、そのまま値として扱うモナド

var ID = {
/* #@range_begin(identity_monad) */
/* unit:: T => ID[T] */
unit: (value) => { // 単なる identity関数と同じ
return value;
},
/* flatMap:: ID[T] => FUN[T => ID[T]] => ID[T] */
flatMap: (instanceM) => {
return (transform) => {
return transform(instanceM); // 単なる関数適用と同じ
};
},
};

 

- Maybeモナド

値に正常か異常かのコンテキストを付与し、エラーチェックを不要にしたモナド

 

- IOモナド

副作用への対処(外部ディスプレイへの出力、ファイルへの書き出しなど)で利用される

 

IOモナドの型情報は、FUN[WORLD => PAIR[T, WORLD]]