仕事で使うf#

98
仕事で使う F # bleis-tift August 28, 2011

Upload: bleis-tift

Post on 30-Jun-2015

10.875 views

Category:

Technology


0 download

TRANSCRIPT

仕事で使うF#

bleis-tift

August 28, 2011

自己紹介

今日やること

F#のおさらい

仕事で F#

NParsecからFParsecへ

まとめ

F#のおさらい

F#って?

静的型付けの関数型言語オブジェクト指向もできる

OCamlベース

正格評価

.NET Framework上で動作

こんな感じ

F#の基本的で特徴的な機能

リスト

タプル

レコード

判別共用体

パターンマッチ

コンピュテーション式

順番に見ていきましょう

リスト

F#で一番手軽に使えるコレクション

consセル

イミュータブル

要素をセミコロンで区切る点に注意

リストの書き方

.

1~3を含むリストの表現例

.

.

.

[1; 2; 3]

[1..3]

1::2::3::[]

1::2::[3]

[ 1

2

3 ]

改行するとセミコロンは不要

consセル

consセル

consセル

consセル

consセル

consセル

イミュータブル

.

リストへの要素の追加

.

.

.

let xs = [2..10]

let ys = 1::xs

イミュータブルなのでリストに対する操作は常に新しいリストを作り出す

タプル

複数の値をまとめて、一つの値として扱うためのもの

型を組み合わせて型を作る

所謂コレクションとは違う

intと stringのタプルの型は、int * string

タプル

タプル

タプル

タプル

タプル

タプル

.

タプルの例1

.

.

.

let x, y = 10, 20

printfn "%A" (x, y) // => (10, 20)

.

タプルの例2

.

.

.

let (x, y) as tpl = 1, 2

printfn "%A" tpl // => (1, 2)

printfn "%A" (x, y) // => (1, 2)

printfn "(%d, %d)" x y // => (1, 2)

リストじゃ駄目なの?

.

リストでやってみる

.

.

.

let [x; y] as lst = [1; 2]

printfn "%A" lst // => [1; 2]

printfn "%A" [x; y] // => [1; 2]

printfn "[%d; %d]" x y // => [1; 2]

.

コンパイル結果

.

.

.

warning FS0025: この式のパターン一致が不完全ですたとえば、値 ’[_;_;_]’ はパターンに含まれないケースを示す可能性があります。

タプルとリストの違い1

タプルとリストの違い1

タプルとリストの違い1

型の違いは容易にコードに落とし込めるタプルは保持する要素の違いが型の違いとなるなので、タプルは型安全に要素をばらせる

変数の型は動的に変更できないリストは要素数の違いは型に影響しないなので、リストは要素の追加、削除が可能

タプルとリストの違い2

.

タプル

.

.

.

(2, "hoge") // int * string

.

リスト

.

.

.

// コンパイルエラー[2; "hoge"]

.

.

[box 1; box "hoge"] // obj list

タプルとリストの違い2

タプルは型を組み合わせるためにあるリストは同じ型の列を扱うためにある

異なる型を混ぜることはできない

タプルとリストの違い

色々比べてきましたが、比べるものじゃないです

レコード

各フィールドに名前の付いたタプルのようなもの

関連する値を分かりやすくまとめた新しい型を作る

クラスとか構造体とかそんな感じ

レコードとタプル

.

タプルの場合

.

.

.

// 名前, 年齢let p = "山田太郎", 21

.

レコードの場合

.

.

.

type Person = {

Name: string

Age: int

}

let p = { Name = "山田太郎"; Age = 21 }

レコードにすると、コードは長くなるけどコメント不要に!感覚的には 4要素のタプルは辛い

レコードとクラス

F#にはレコードのほかにクラスもある

.

クラス

.

.

.

type Person(name: string, age: int) =

member this.Name = name

member this.Age = age

let p = Person("山田太郎", 21)

.

レコード

.

.

.

type Person = { Name: string; Age: int }

let p = { Name = "山田太郎"; Age = 21 }

どう使い分ければいいの?

レコードとクラス

.

クラス

.

.

.

type Person(name: string, age: int) =

member x.Name = name

member x.Age = age

let f p = p.Name

.

コンパイル結果

.

.

.

error FS0072: このプログラムの場所の前方にある情報に基づく不確定の型のオブジェクトに対する参照です。場合によっては、オブジェクトの型を制約する型の注釈がこのプログラムの場所の前に必要です。この操作で参照が解決される可能性があります。

レコードとクラス

.

レコード

.

.

.

type Person = { Name: string; Age: int }

let f p = p.Name

.

コンパイル結果

.

.

.

type Person =

{Name: string;

Age: int;}

val f : Person -> string

推論できた!

レコードとクラス

こんな風に、レコードの方が型推論と相性がいい

とりあえずレコードを使っておきましょうでもクラスも要らない子ではない

独自のコンピュテーション式 (後述)を作るとか

判別共用体

「どれかのうちひとつ」を表す新しい型を作る

ケース毎に値が持てるので、列挙型 (enum)よりも強力

例えば・・・

レベルを持った見出し

単なるテキスト

水平線

のどれかを表す型HtmlElemが欲しい

C#でやるとしたら

.

クラス階層で

.

.

.

public abstract class HtmlElem {}

public class Heading : HtmlElem {

public int Level { get; private set; }

public string Text { get; private set; }

public Heading(int level, string txt) {

Level = level; Text = txt; } }

public class Text : HtmlElem {

public string Text { get; private set; }

public Text(string txt) {

Text = txt; } }

public class HorizontalLine : HtmlElem {}

F#でやるとしたら

.

判別共用体で

.

.

.

type HtmlElem =

| Text of string

| Heading of int * string

| HorizontalLine

クラス階層の欠点

参照値の比較しかできない内容の比較を行うためには、Equalsメソッドをオーバーライドする必要があるGetHashCodeも実装する必要があるMake static analyzers happy==演算子と!=演算子もオーバーロードしておくと便利

文字列化できないテスト失敗時のメッセージがへぼいToStringメソッドをオーバーライドする必要がある

なのに長い

判別共用体は?

構造的な比較を行ってくれる!

sprintf ”%A”で文字列化も簡単!

短い!

他にも利点はあるけど、それは後で

F#の特徴

F#は、タプルやレコード、判別共用体があるため、C#よりもクラスを使う機会が少ない

パターンマッチ

リストやタプル、レコード、判別共用体と言った構造的な型を分解する

ネストした構造から一発で望みの値を手に入れることができる

不足しているケースを検出してくれる

絶対にマッチしないケースを検出してくれる

判別共用体に対するマッチ

.

HtmlElemをHTML文字列に変換する

.

.

.

type HtmlElem =

| Heading of int * string

| Text of string

| HorizontalLine

let toHtmlStr elem =

let h lv s =

sprintf "<h%d>%s</h%d>" lv s lv

match elem with

| Heading(level, txt) -> h level txt

| Text txt -> "<p>" + txt + "</p>"

| HorizontalLine -> "<hr/>"

そのものずばりの、match式を使いました

タプルに対するマッチ

.

渡された整数を足す

.

.

.

let add = function

| 0, y -> y

| x, 0 -> x

| x, y -> x + y

最後の引数に対してマッチさせる、function式を使いました

match式と function式

.

match式

.

.

.

let add tpl =

match tpl with

| x, y -> x + y

.

function式

.

.

.

let add = function

| x, y -> x + y

レコードに対するマッチ

.

特定のフィールドを取り出す

.

.

.

type t = { Tag: int; Value: int }

let value { Value = v } = v

引数に対してマッチさせました

match式と引数に対するマッチ

.

match式

.

.

.

let add tpl =

match tpl with

| x, y -> x + y

.

function式

.

.

.

let add = function

| x, y -> x + y

.

引数に対するマッチ

.

.

.

let add (x, y) = x + y

ネストしていても大丈夫

.

Personから名前を取り出す

.

.

.

type Name = {

FirstName: string

LastName: string

}

type Person = { Name: Name; Age: int }

let f x =

let { Name = { FirstName = fn } } = x

fn

コンピュテーション式

F#の文法と関数呼び出しを対応付ける規則

モナド用の構文としても力を発揮

例えば

.

Maybeモナド

.

.

.

type MaybeBuilder() =

member this.Bind(x, f) =

x |> Option.bind f

member this.Return(x) = Some x

let maybe = MaybeBuilder()

let plus db = maybe {

let! x = db |> Map.tryFind "x"

let! y = db |> Map.tryFind "y"

return x + y

}

コンピュテーション式は・・・

語りだすと長くなりすぎるので省略。詳しく知りたい人はこの後のディスカッションとかでどうぞ!

Map-Reduce的な何か (違)

foldと reduceの違い

foldで色々書く

foldと reduceの違い

foldと reduceってどう違うの?→言語にもよる

F#の場合はどうよ?

.

foldと reduceの型

.

.

.

> List.fold;;

val it : ((’a -> ’b -> ’a) -> ’a -> ’b list -> ’a)

= <fun:clo@1>

> List.reduce;;

val it : ((’a -> ’a -> ’a) -> ’a list -> ’a)

= <fun:clo@2-1>

引数の数と、型引数の数が違うfoldは初期値を受け取り、結果の型は初期値と同じ型reduceは初期値として listの先頭要素を使う。結果の型は必然的に listの要素の型

foldを追ってみる

foldを追ってみる

foldを追ってみる

foldを追ってみる

foldで色々と書く

では foldを使って色々と書いてみましょう!

sum

forall

map

filter

foldで色々と書く

では foldを使って色々と書いてみましょう!

sum・・・全部足す

forall・・・全ての要素が条件を満たすかどうか

map・・・一つ一つの要素を変換する

filter・・・条件を満たす要素のみを集める

foldで sum

.

sum

.

.

.

let sum xs =

List.fold (+) 0 xs

さっきの文字列連結と似たような感じ

foldで forall

.

forall

.

.

.

let forall p xs =

xs

|> List.fold (fun acc x -> acc && (p x)) true

今までとは違い、結果の型がリストの要素の型と一致しない例existsとか書いてみるといいかも

foldでmap

.

map

.

.

.

let map f xs =

([], xs)

||> List.fold (fun acc x ->

acc @ [f x]

)

初期値を空リストにするのがミソ

foldでfilter

.

filter

.

.

.

let filter p xs =

([], xs)

||> List.fold begin acc x ->

if p x then acc @ [x]

else acc

end

ラムダ式の中で分岐

fold

foldは・・・

抽象度が高い

fold(と unfold)で何でもできちゃう

何でもかんでも foldで書いちゃうと読みにくくなる

最後の手段として使う

仕事でF#

どうやれば仕事で使えるの?

F#向きな案件

F#の特長の売り込み

他の.NET言語との連携

F#向きな案件

というより、F#を押し込みやすい案件

.NET Frameworkのバージョンが古い

短納期

品質要求が高い

なんかある案件だと押し込みやすい

.NET Frameworkのバージョン

サーバにインストールされているのが 2.0で、下手にいじれないから最新の.NET Frameworkなんてインストールできないよ・・・

そんなあなたにF#!

.NET Frameworkのバージョン

F#は.NET Framework2.0が入っていればRuntimeを入れるだけで動く

.NET Framework2.0では LINQが使えないけど、F#なら Listなどに対する高階関数が色々と用意されている

非同期ワークフローも使える

.NET Frameworkのバージョンが 2.0だと、F#を採用することのメリットが絶大!

.NET Frameworkのバージョン

ちなみにバージョンが 4.0だとしても F#は素敵。例えば、TDDBC Tokyo 1.6の課題を見ると・・・

C#の場合 (実装コード)KeyValueTime.cs 42行SystemClock.cs 30行TddbcKeyValueStore.cs 67行

F#の場合 (実装コード)KeyValueStore.fs 48行

C#が 139行に対してF#は 48行!

.NET Frameworkのバージョン

ちなみにバージョンが 4.0だとしても F#は素敵。例えば、TDDBC Tokyo 1.6の課題を見ると・・・

C#の場合 (テストコード)KeyValueTimeTest.cs 141行SystemClockTest.cs 34行TddbcKeyValueStoreTest.cs 346行 (!)

F#の場合 (テストコード)KeyValueStore.fs 170行

C#が 521行に対してF#は 170行!

.NET Frameworkのバージョン

.NET Frameworkのバージョンにかかわらず、F#

が使える人がいるなら魅力的。少なくとも、2.0でC#とかやってられないので、そこは F#で。

F#の特長の売り込み

他の.NET言語ではなく、F#を選択する理由より安全

optionとか

よりシンプルさっき見たとおり

より便利各種高階関数型を作る心理的ハードルが低い→型が軽いパターンマッチと網羅性のチェック

大体こんな感じ

とはいっても・・・

他の人員との兼ね合いもあるし導入できないよ・・・自分の部分はF#を使って、他の人の部分はC#なりVBなりで書いてもらう。

他の.NET言語との連携

C#やVBといった言語と連携させる必要がある場合の注意点

リスト

判別共用体

関数

リスト

F#の listを外に見せてしまうと、F#の dllを参照に追加する必要がある色々面倒・・・

コレクションを外に出すなら arrayか seqで!

判別共用体

判別共用体を外に見せてしまうと・・・

継承階層に置き換わる各ケース識別子

値を持たないケースは staticフィールド、値を持つケースは入れ子になったクラスのNewHogeメソッド

どのケース識別子かを整数で表すTagプロパティと、あるケースかどうかを返す IsHogeメソッド

こんなの使ってられっか!状態

判別共用体は F#外に見せないようにしましょう

関数

普通の関数なら変換してくれるんだけど・・・

.

.

module Hoge

let plus10 = ((+)10)

これだとHoge.plus10プロパティとして見える・・・しかも FSharp.Core.FSharpFuncクラス→カリー化されている上にFSharp.Core.dllを参照に追加する必要

変数に関数を入れているときは注意!

NParsecからFParsecへ

モチベーション

ミニ言語的なものを実装しなければいけない

手軽に実現できた方がいい

外部のツールとか使いたくない

パーサジェネレータは?

yaccとか lexとかでは駄目なの?

yaccや lexはホスト言語とは別言語

コンパイル一発で動かない

汎用言語ではないので、柔軟性が低い

部品の共通化とか悲しい

テストがつらい

そこでパーサコンビネータですよ!

パーサコンビネータ

小さい単純なパーサを組み合わせて大きい複雑なパーサを作る

各パーサは関数として実装される

BNFとまではいかないけど、それなりに読みやすい

各パーサのテストが書ける

パーサコンビネータの例

parsec

JParsec

NParsec←今日やる

FParsec←今日やる

scala.util.parsing←標準ライブラリ

Boost.Spirit

NParsec

parsecの.NET実装

C#で実装されている

クラス名がどうやっても省略できないC#では非常につらい

ただし、JavaのコメントやらなんやらのScannerが標準装備

FParsec

parsecの.NET実装

F#で実装されている

今のところトークン列を扱うパーサが無い・・・

ドキュメントが至れり尽くせり

NParsecに対するFParsecの利点

再帰的なパーサの作りやすさ

データ構造の作りやすさ

記号が使える

コンピュテーション式が使える

読み書きできる

再帰的なパーサの作りやすさ

NParsecでは再帰的なパーサを作るために配列を使っていて最初見たときにわけがわからない。

.

こんな感じ

.

.

.

var lazyexpr = new Parser<double>[1];

var plazyexpr = Parsers.Lazy<double>(() =>

lazyexpr[0]

);

var pterm =

plazyexpr.Between(popen, pclose) | pnum;

var pexpr = ...

lazyexpr[0] = pexpr;

再帰的なパーサの作りやすさ

FParsecの場合は

.

こんな感じ

.

.

.

let pexpr, exprref =

createParserForwardedToRef()

let pterm =

pexpr |> between popen pclose

<|> pnum

do exprref := ...

マジックナンバー不要!

データ構造の作りやすさ

パーサコンビネータと判別共用体の相性は抜群。F#だと 10行のところが、C#では 54行・・・

さらにC#ではパターンマッチが無い→いろいろやろうと思ったらVisitorパターン実装・・・となって更に膨らむ

記号が使える

C#では使える記号が限られている・・・F#では割と自由に使えるので、より短くシンプルに記述できる!

慣れれば便利他のパーサコンビネータでも似たような記号の使い方になっている

コンピュテーション式が使える

NParsecは直接Bindメソッド呼び出したり、return Parsers.Return(...)とか、カオスFParsecはコンピュテーション式が使えるのでシンプルに記述できる!

これは後で見てみます

読み書きできる

NParsec力が足りない・・・

コードを見てみましょう

実案件での例

まとめ

まとめ

F#は・・・

静的型付けの関数型言語!

仕事でも使える!

FParsecなどのライブラリもある!

みんなでF#やりましょう!