functional



functional

0 0


functional


On Github unisys-tani / functional

関数型言語勉強会

第1回

関数型言語とは

本勉強会の目的

  • 関数型言語という新しいパラダイムの習得​
  • 本勉強会で得られた知見を業務で活用する。​

本勉強会の予定(は未定)

  • 第1回 --- 関数型言語とは​
  • 第2回 --- 関数型言語の考え方を手続き型言語で活かすには​
  • 第3回 --- Elmの環境構築方法、基本文法​
  • 第4回 --- Elmのリスト処理​
  • 第5回 --- Elm のイベント処理

関数型言語を勉強する意味

  • 手続き型言語に関数型言語のパラダイムが流入する傾向​
    • C#であればLINQ、ラムダ、Rx.Net​
    • JavaであればストリームAPI​
    • JavaScriptであればアロー関数、RxJS​
  • その傾向は今後も続くはず。​

手続き型に流入してくる理由

  • 手続き型言語に比べて抽象度が高いため​
  • ちなみに関数型言語に手続き型の機能が流入することはない。

関数型言語とは

永続データプログラミング

  • 永続データ​
    • 破壊できないデータ、再代入できないデータ​
  • 非永続データ
    • 破壊できるデータ、再代入できるデータ

永続データの例

  • コンストラクタでしか値の設定ができないクラス
    public class Person {​
      public string Name { get; private set; }​
      public int Age { get; private set; }​
      public Person(string name, int age) {​
          this.Name = name;​
          this.Age = age;​
      }​
    }
    

手続き型のイメージ

  • 鍋で作るカレー
    • 家族全員の分を鍋で作る。味付けの変更が可能。但し全員に影響が出る。

関数型のイメージ

  • レトルトのカレー
    • 各人が作る。味付けの変更は不可。味を変えたければ別のカレーを買う。

手続き型

$$ X = X + 1 $$

関数型

$$ X_{n+1} = X_n + 1 $$

手続き型とは

  • メモリを確保し、そのメモリ領域を共有し内部を破壊しつつ、プログラムを動作させるというパラダイム
  • よってアクセス指定子などが必要

関数型とは

  • メモリを共有せずに、内部の破壊を起こさないようにプログラムを動作させるというパラダイム

副作用

  • 確保したメモリの内部を破壊する操作
  • 環境に依存した処理

純粋関数型言語

  • メモリの破壊ができない関数型言語を純粋関数型言語と呼びます。
  • ループが書けないので、再帰もしくは高階関数(後述)を使用。
  • 環境に依存した処理などは特定の機構を通じて行います。

関数とは?

  • 同じ引数であれば同じ戻り値を返すもの
  • 上記の性質のことを参照透明性と呼びます。
  • 引数が同じでも同じ結果を返さないものは関数ではない。(手続き)
  • 例えばテキストファイルのパスを渡してその中身を読み込み返す「関数」
  • 上記は関数ではなく手続き、戻り値はファイルの内容に依存するため。

高階関数

  • 関数を引数にしたり、関数を戻り値にしたりする関数のこと
  • JavaScript、C#、Luaなどはこの機能をサポート
  • 「関数がファーストクラスオブジェクト」などと言ったりします。
  • 関数型言語では関数を外から差し込むことで、多態性を実現します。

高階関数(実装例)

// JavaScriptでの実装例
binOp(10, 2, function(x, y){ return x + y }) // -> 12
binOp(10, 2, function(x, y){ return x - y }) // -> 8
binOp(10, 2, function(x, y){ return x * y }) // -> 20
binOp(10, 2, function(x, y){ return x / y }) // -> 5

function binOp(x, y, op)
{
    return op(x, y)
}

高階関数(実装例)

// ES6での実装例
binOp(10, 2, (x, y) => x + y) // -> 12
binOp(10, 2, (x, y) => x - y) // -> 8
binOp(10, 2, (x, y) => x * y) // -> 20
binOp(10, 2, (x, y) => x / y) // -> 5

function binOp(x, y, op)
{
    return op(x, y)
}

関数型言語の影響を受けたライブラリ

  • .Net
    • Rx.NET、WPF、LINQ
  • JavaScript
    • RxJS、Cycle.js、knockout.js、Underscore.js、Elm

第2回

関数型の考え方を活かすには

今回のテーマ

  • 関数型言語の考え方をオブジェクト指向に活かしてみる。

関数?手続き?

  • 自分が書いているものが関数なのか手続きなのかを意識する。
  • それが全ての出発点。

SOLID

  • 単一責務の原則(SRR)
  • オープン・クローズドの原則(OCP)
  • リスコフの置換原則(LSP)
  • インタフェース分離の原則(ISP)
  • 依存関係逆転の原則(DIP)

単一責務の原則

  • クラスには複数の責務を持たせてはいけない。

凝集度と結合度

  • 一般に凝集度が高く、結合度が低いプログラムが良いとされている。
  • 凝集度
    • あるクラスのメソッドがどれだけメンバ変数に対してアクセスしているかという指標
  • 結合度
    • クラスとクラスの間の結びつきの強さの指標

凝集度の算出方法

$$ LCOM = \frac{\frac{1}{a}\sum_{j}^a μ(Aj)-m}{1-m} $$

  • Ajは、着目しているクラスのj番目のメンバ変数
  • aは、着目しているクラスのメンバ変数の個数
  • mは、着目しているクラスのメソッドの個数
  • μ(Aj)は、メンバ変数Ajにアクセスしているメソッドの個数
  • -->つまり、、、

凝集度の算出方法

  • メンバ変数1つ当たりへアクセスするメソッドの数が少ないとLCOMは1へ近づき、多ければLCOMは0へ近づく。
  • LCOMは小さいほど凝集度が高いとみなすことができる。
  • 例えばメンバ変数に全然アクセスしていないメソッドがあれば凝集度が下がる。(そのメソッドがそのクラスにある必要性がない)

良いクラスとは

  • 自分が持っているメンバ変数に対してのみ仕事をしている。
  • ほぼ副作用の塊になるはず。
  • 結果、単一責務の原則を守るような小さなクラスになっていくはず。

それ以外のメソッドは?

  • 多くは副作用がないはず。
  • したがってヘルパクラスのstaticメソッドとして切り出せる。
  • 副作用がないためpublicにしてユニットテストを書いてしまえばOK

関数?手続き?

  • 自分が書いているものが関数なのか手続きなのかを意識する。
  • 手続きであっても関数として切り出せる部分がないか確認する。
  • 関数ならばpublic staticとして切り出してユニットテストを書く。
  • 手続きならば当該クラスに閉じ込め、状態の変更が外部から観察可能であればユニットテストを書く。
  • 外部から観察不可であれば別のテスト手法などを考慮する。

関数?手続き?

  • 前スライドのようなことをやっていると、凝集度が高く単一責務の原則を満たす小さなクラスになっていきます。
  • また、関数にしてしまうということは副作用がない、つまり状態が変わらないことを意味します。なのでアクセス指定子は全く気にする必要がなくなります。

関数?手続き?

  • 関数と手続きを分ける。関数はヘルパクラスへ。手続きはさらにユニットテストで確認できるものとそうでないものに分ける。ユニットテストで確認できるものはユニットテストを書く。書けないものは知恵を絞る。
  • 手続き(副作用)を局所化するという意識が重要。

関数型のアプローチ

  • 前スライドのようなアプローチであればオブジェクト指向における開発でも関数型の考え方を組み込むことが可能。
  • 今日話した内容が何かのヒントになれば幸いです。
関数型言語勉強会