View
6.139
Download
4
Category
Preview:
DESCRIPTION
高専カンファ in 三重2の発表資料です。高専生向けなので、入門と言いつつアレな感じですので注意してください。
Citation preview
F#によるFunctional Programming入門
bleis-tift
December 22 2012
自己紹介
id:bleis-tift / @bleis
鈴鹿高専電子情報工学科卒業生
Microsoft MVP for Visual F#
いきなりですが質問です
Q1. プログラミングしたことありますか?
Q2. どんな言語を使いましたか?
C, C++, BASIC, Java, C#, PHP, Perl, Python,Ruby, JavaScript, ...授業だと大体こんなところでしょうか?
今日話すこと
授業ではあまり取り上げられないであろうタイプの言語である、
関数型プログラミング言語
を取り上げ、関数プログラミングについて紹介します。
その前に・・・
手続型言語ってなんでしょうか?手続きを記述する
処理のまとまりを「手続き」としてまとめる手続きを構成するのは「文」が基本
状態を書き換える基本的に変数は書き換え可能単純なループは変数の書き換えがほぼ必須
手続型言語は文中心かつ、書き換え可能な変数が必須
手続型言語に対し・・・
関数型言語はざっくりこんな感じです。関数を記述する
処理のまとまりを「関数」としてまとめる関数を構成するのは「式」
状態の書き換えは局所化する基本的に変数は書き換え不可能変数の書き換えによらない繰り返し
関数型言語は式中心
式と文の違い
式と文の違い、分かりますか?
式は評価することで値になる
文そのものは値を持たない
値が書けるところには式が書ける!
式と文の違い (文)
.
Java
.
.
.
int[] f(int x) {
int[] res = new int[x];
for (int i = 0; i < x; i++)
res[i] = i * 2;
return res;
}
int sum(int[] xs) { ... }
// これはもちろん可能int total = sum(f(10));
// これは出来ないint total = sum(
int[] res = new int[x];
for (int i = 0; i < x; i++)
res[i] = i * 2;
return res;
);
式と文の違い (式)
.
F#
.
.
.
let f x =
let res = Array.zeroCreate x
for i in 0..(x - 1) do
res.[i] <- i * 2
res
let sum xs = ...
// これはもちろん可能let total = sum(f 10)
// これも可能!let total =
sum (
let res = Array.zeroCreate 10
for i in 0..(10 - 1) do
res.[i] <- i * 2
res
)
式中心であることの利点
式を組み合わせてより大きく複雑な式を構築できる!
F#について
ちょっと休憩して、F#について。
Visual Studioに標準搭載されている、マルチパラダイム言語(コアとなるパラダイムは関数型)
.NET Framework上で動作するため、
.NETの資産を活かせる
強い静的型付き言語
インデントを文法に含む (軽量構文の場合)
オープンソース
さっきのコードをもう一回見てみる
.
.
let f x =
let res = Array.zeroCreate x
for i in 0..(x - 1) do
res.[i] <- i * 2 // 状態書き換えてる!res
.
状態書き換えない版
.
.
.
let f x =
Array.init x ((*)2)
ループを自分で書く必要すらない!→バグの抑制につながる
F#って静的型付き言語って言ったよね・・・?
今までのコード、型書いてません。でも、型推論によってコンパイル時に型が付いていたのです!
.
.
// intを受け取って int[]を返す関数// 「int -> int[]」と記述するlet f x =
Array.init x ((*)2)
この関数にどうやって型が付いたのか見ていきます。
その前に
((*)2)って何!
((*)2)について
F#では、中置演算子を関数として扱える
.
.
2 * 4 // 8
(*) 2 4 // 8
そして、複数引数の関数に引数を「一部」与えることができる
.
.
let f x y = x * y
f 2 4 // 8
let g = f 2
g 4 // 8
つまり、((*)2)は「何かを 2倍する関数」
型推論のステップ
.
.
let f x =
Array.init x ((*)2)
.
..
1 Array.initは、int → (int → α) → α[]
.
.
.
2 xはArray.initの第一引数として渡しているxの型は int
.
.
.
3 戻り値の型はArray.initの戻り値の型と同じfの戻り値の型はα[]
.
.
.
4 掛け算「*」の型は int → int → int((*)2)は引数を一つ与えているので、int → intこれを Array.initの第二引数として渡しているので、(int → α)のαは int
全体として、fは「int → int[]」
型推論の利点
型を書かなくても型が付く→型チェックの恩恵が受けられる
ちょっと休憩
ここまでで何か質問ありますか?
ここからは型について
AかBかCのどれかを表す型が欲しくなったときどうしますか?列挙型を使う?
.
Javaの例
.
.
.
enum T { A, B, C }
これでどうや!
列挙型でダメな場合
列挙型は「Aの時のみ必要な情報」を保持できない例えば、
アンケートの項目「このイベントを何で知りましたか?」
Webサイト / Twitter / Facebook / その他
その他の場合は理由が必要
これは列挙型ではうまくあらわせない・・・
どうしよう
.
列挙型とクラスでやる?
.
.
.
enum InfoSource {
WebSite, Twitter, Facebook, Etc
}
class Answer {
InfoSource source;
String etc;
...
}
Etc以外の時に etcが使われてしまうかもしれない
どうしよう・・・クラス階層を作ってみる?
.
こうや!
.
.
.
interface InfoSource { }
enum WebSite implements InfoSource { Instance }
enum Twitter implements InfoSource { Instance }
enum Facebook implements InfoSource { Instance }
class Etc implements InfoSource {
String etc;
}
Etc以外の時は etcが使えない!
でも InfoSourceをEtcとして使うためにはキャストが必要
結局コンパイル時に解決できていない
そこでVisitorですよ!
Visitor
.
Visitorの導入
.
.
.
interface Visitor {
void webSite(WebSite w);
void twitter(Twitter t);
void facebook(Facebook f);
void etc(Etc e);
}
interface InfoSource { void accept(Visitor v); }
enum WebSite implements InfoSource {
Instance;
public void accept(Visitor v) { return v.webSite(this); }
}
enum Twitter implements InfoSource {
Instance;
public void accept(Visitor v) { return v.twitter(this); }
}
enum Facebook implements InfoSource {
Instance;
public void accept(Visitor v) { return v.facebook(this); }
}
class Etc implements InfoSource {
String etc;
public void accept(Visitor v) { return v.etc(this); }
}
やってられるかー!
そこで直和型ですよ!
.
F#の直和型 (判別共用体)を使えばこの通り
.
.
.
type InfoSource =
| WebSite
| Etc of string
すっきり!
使い方も簡単
.
match式を使う
.
.
.
let toStr src =
match src with
| WebSite -> "Webサイト"
| Twitter -> "Twitter"
| Facebook -> "Facebook"
| Etc etc -> "その他 (" + etc + ")"
.
function式でも可
.
.
.
let toStr = function
| WebSite -> "Webサイト"
| Twitter -> "Twitter"
| Facebook -> "Facebook"
| Etc etc -> "その他 (" + etc + ")"
判別共用体のさらなる用途
ここまでは、列挙型に毛の生えたようなもの
ここまででも便利だが・・・
判別共用体は、自分自身を参照することで更に便利に!
情報系ではおそらく一度は実装する
.
二分木
.
.
.
これを実装してみましょう
二分木の実装
.
Javaの場合
.
.
.
public class BinTree {
final int value;
BinTree left = null;
BinTree right = null;
public BinTree(int value) {
this.value = value;
}
}
.
F#の場合
.
.
.
type BinTree =
| Leaf
| Node of BinTree * int * BinTree
// ↑ ↑
二分探索木の探索
.
Javaの場合
.
.
.
public boolean contains(int value) {
if (value == this.value)
return true;
if (value < this.value && lelft != null)
return left.contains(value);
if (this.value < value && right != null)
return right.contains(value);
return false;
}
.
F#の場合
.
.
.
let rec contains value = function
| Leaf -> false
| Node (l, v, r) when v = value -> true
| Node (l, v, r) when value < v -> contains value l
| Node (l, v, r) when v < value -> contains value r
両者の違い
F#の実装の方が短い (Javaよりも宣言的)
Java版は nullを使うため、NullPointerExceptionが起こりうるのに対し、F#版は nullを使わない
Java版は二分木の操作は参照をやりくりする必要があるのに対し、F#版はパターンマッチが使える
判別共用体は
再帰的なデータ構造を上手に扱える再帰的なデータ構造は思いのほか多いリスト、木、ファイルシステム、などなど
簡単に記述できる型を定義するハードルが低い (型が軽い)
取りこぼし
リストとリストの処理
高階関数 (関数が第一級の値)
少ないルールを組み合わせる
モナドとか
まとめ
文より式!
直和型 (判別共用体)!
F#! F#!
Recommended