F#メモ
実践F# 関数型プログラミング入門をいい加減に読もうということで写経しながらの読書メモ。
# ocaml OCaml version 4.01.0 # fsharpi F# Interactive for F# 3.1 (Open Source Edition)
でたまに比較する。
メッセージは多少手を加える事もある。
OCamlをほんのちょびっと学んでいる前提があるので軽く流す程度のところもある。
1~3章は特にアレする事でもないので4章から。
F#コンパイラは可能な限り気の利いた解釈をしてくれるので、リスト4-42のようなコードでは問題が表面化しません。
F#
(*) 8 6 // ->46
# (*) 8 6;; Warning 1: this is the start of a comment.
おお、けど大人しくOCamlに合わるのでいいんじゃないかな。
# ( * ) 8 6;; - : int = 48
ビット演算子
# 2 land 4;; - : int = 0 # 2 lor 4;; - : int = 6 # 2 lxor 4;; - : int = 6 # lnot 3;; - : int = -4
F#
2 &&& 4 2 ||| 4 2 ^^^ 4 ~~~ 3
まぁ同じのが良ければ自分で定義すればいいしたいした問題ではない。はず。
let (&&&) x y = x land y let (land) x y = x &&& y
ハイライトがアレですね。
F#といえばパイプライン演算子?よくわからないけどわかりやすい
> [1; 2; 3; 4; 5] - |> List.map (fun x -> x * 2) ;; val it : int list = [2; 4; 6; 8; 10]
タプル用もあるよ!
> (4, 5) ||> (fun x y -> x + y) ;; val it : int = 9
後置さんをうまく混ぜていくとのーみそこねこねな雰囲気を醸し出せる気がしてくる。
functionとの相性もよくなる
関数合成演算子
> (>>) ;; val it : (('a -> 'b) -> ('b -> 'c) -> 'a -> 'c) = <fun:it@18-1> > (<<) ;; val it : (('a -> 'b) -> ('c -> 'a) -> 'c -> 'b) = <fun:it@19-2> # let (>>) f g x = g (f x) ;; val ( >> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c = <fun> # let (<<) f g x = f (g x) ;; val ( << ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
>>と<<の例は答えが変わってきちゃうよね・・・?と思ったらサポートページに記載が。
ちゃんと動きが分かっているから気付けたのかなと思うと嬉しい。
カリー化や高階関数、関数合成って見た目がアレで頭がくねくねするけど、実際にコード書いて答え見るの繰り返すと納得したような満足感が得られますね。
適用可能な引数型の内特定の1つをデフォルトの型にしてしまって、適用する引数の型がわからない場合はすべてそのデフォルトの型にすればよい、
という身も蓋もない基準です。
F#
> let add x y = x + y;; val add : x:int -> y:int -> int > add 1. 2. ;; add 1. 2. ----^^ error FS0001: This expression was expected to have type int but here has type float (* 特定の型に推論させる場合はどこかしらに型を明示する *) > let add x y :float = x + y ;; val add : x:float -> y:float -> float (* OCamlが忘れられないなら中置き演算子を定義する? *) > let (+.) : float -> float -> float = (+) ;; val ( +. ) : (float -> float -> float) > 2 +. 3 ;; 2 +. 3 ;; ^ error FS0001: This expression was expected to have type float but here has type int > 2. +. 3. ;; val it : float = 5.0
リテラルパターン
[<Literal>] let Paranoia = "F#" "F#" |> function Paranoia -> "STAY GOLD" | _ -> "STAY AWAY" val Paranoia : string = "F#" val it : string = "STAY GOLD" let Hoge = "hoge" "hoge" |> function Hoge -> "hogehoge" | _ -> "fugafuga" : warning FS0049: Uppercase variable identifiers should not generally be used in patterns, and may indicate a misspelt pattern name. : warning FS0026: This rule will never be matched > val Hoge : string = "hoge" > val it : string = "hogehoge"
出力結果的には正しくなってるけど識別子パターンの命名規則との判別がアレで警告出ちゃってる。
他の.NETの命名規則とのギャップ埋めにはCompiledName属性を使う
[<CompiledName("Hoge")>] let hoge = ...
C#や他の.NET Framework対応の言語から見たときの識別子名はCompiledName属性で指定したFunctionNameに変更されます。
型略称で別名を与えることができる。
let longlonglonglonglonglonglongfunctionname x = x let shortname = longlonglonglonglonglonglongfunctionname longlonglonglonglonglonglongfunctionname 10 > val it : int = 10 shortname "hoge" > val it : string = "hoge"
多相性持たせる時には.NETの書き方もできる。
> type 'a hoge = Hoge | Fuga of 'a ;; type 'a hoge = | Hoge | Fuga of 'a > type piyo<'T> = Piyo | Puyo of 'T ;; type piyo<'T> = | Piyo | Puyo of 'T
F#でやるならOCaml互換の方が慣れている感はある。
型パラメーターの制約
(* equal制約はそれぞれの引数の型が同じ型でかつ等価性の比較ができなければいけない *) > let eqf f x y = if x = y then f x y else x ;; (* 誰得な関数。同じもの同士でないと関数適用がされない *) > eqf (fun x y -> x * y) 10 20 ;; val it : int = 10 > eqf (fun x y -> x * y) 20 20 ;; val it : int = 400
ボクシングとアンボクシング
> unbox<float> (box 10.) ;; val it : float = 10.0 > unbox<float> (box 10) ;; System.InvalidCastException: Cannot cast from source type to destination type. ** let is
:?演算子と共に型チェックをする際に使用する事が多い?
> let isString a = (box a) :? string;; val isString : a:'a -> bool > isString 10;; val it : bool = false > isString "hoge";; val it : bool = true
ループ
let addRangeWhile n m = let n, m = if n <= m then n, m else m, n let mutable sum, i = 0, n while i <= m do sum <- sum + i i <- i + 1 sum let addRangeForTo n m = let n, m = if n <= m then n, m else m, n let mutable sum = 0 for i = n to m do sum <- sum + i sum let addRangeForIn n m = let n, m = if n <= m then n, m else m, n let mutable sum = 0 for i in n..m do sum <- sum + i sum let addRangeRec n m = let n, m = if n <= m then n, m else m, n let rec fsum sum i = if i = m then sum else fsum (sum + i) (i + 1) fsum m n > addRangeWhile 1 10;; val it : int = 55 > addRangeForTo 1 10;; val it : int = 55 > addRangeForIn 1 10;; val it : int = 55 > addRangeRec 1 10;; val it : int = 55
クロージャはサイ本に出てきた時になんとかなりそうな雰囲気出て、
今回も理解できる感はある。
リファレンススコープを閉鎖
let f () = let mutable x = 0 x <- 100 x <- 100 + 100 let x = x
なるほど面白い。
関数の内側(実装)を確認した結果がもし副作用があったとしても、
それが外側に漏れ出さないものであるならば、
その関数は副作用がないものとして使用して差し支えありません。
Lazy<'T>
> let lz = lazy (1 + 1) - - lz.Force() ;; val lz : Lazy<int> = 2 val it : int = 2 > lz.Force() ;; (* 一度評価されたらそれ以降はその値が返される *) val it : int = 2
今の頭では用途が思いつかない。
コレクション!コレクション!コレクション!
type Tree<'T> = | Empty | Node of 'T * Tree<'T> * Tree<'T> let rec size = function | Node (_, l, r) -> 1 + size l + size r | Empty -> 0 let rec depth = function | Node(_, l, r) -> 1 + max (depth l) (depth r) | Empty -> 0
3つのソート
> List.sort;; val it : ('a list -> 'a list) when 'a : comparison = <fun:clo@183-1> > List.sortBy;; val it : (('a -> 'b) -> 'a list -> 'a list) when 'b : comparison = <fun:clo@184-2> > List.sortWith;; val it : (('a -> 'a -> int) -> 'a list -> 'a list) = <fun:clo@185-3>
List.sortが普通のソートでList.sortByがソートの基準となるアレコレを指定してList.sortWithがcompareToな感じ。
public static void Main (string[] args) { foreach (var n in Enumerable.Range(0, 10).OrderBy(n => - n)) { Console.WriteLine (n); } var gs = new List<Gengo> { new Gengo{ Year = 1868, Name = "Meiji" }, new Gengo{ Year = 1912, Name = "Taisho" }, new Gengo{ Year = 1926, Name = "Syowa" }, new Gengo{ Year = 1989, Name = "Heisei" } }; foreach (var item in gs.OrderBy(g => g.Name)) { Console.WriteLine (item.ToString()); } } class Gengo { public int Year { get; set; } public string Name { get; set; } public override string ToString () { return string.Format ("[Gengo: Year={0}, Name={1}]", Year, Name); } }
sortByとsortWithをC#でやるとこんなんなるよね。
借りてきた文法のお陰でだいぶ楽だしIComparable実装はあんまり楽しくないし。
内包的記法
すごいH本をちら見した時にちょっと触れた記憶があります。
for ... inとyieldだけを含む場合はyieldの代わりに->を使える
> [ for n in 1..9 -> n * n];; val it : int list = [1; 4; 9; 16; 25; 36; 49; 64; 81]
Enumerable.Range (1, 9).Select (n => n * n)
reduce
let myReduce f list = let rec reduce l t = match l with | [] -> t | x::xs -> reduce xs (f t x) reduce (List.tail list) (List.head list) val myReduce : f:('a -> 'a -> 'a) -> list:'a list -> 'a > List.reduce (+) [1..10] ;; val it : int = 55 > List.reduce (+) [] ;; System.ArgumentException: The input list was empty. > [1..10] |> myReduce (+) ;; val it : int = 55 > [] |> myReduce (+) ;; System.ArgumentException: The input list was empty.
こんな感じかなぁ。
mapi,mpa2
let myMapi f list = let rec mapi n = function | [] -> [] | x::xs -> (f n x)::(mapi (n+1) xs) mapi 0 list ["Foo"; "Bar"; "Baz"] |> List.mapi (fun i s -> sprintf "%d. %s" (i + 1) s) let myMap2 f list1 list2 = let rec map2 l1 l2 = match l1, l2 with | x::xs, y::ys -> (f x y)::map2 xs ys | _, _ -> [] map2 list1 list2 List.map2 (sprintf "%d-%c") [1..3] ['A'..'C']
末尾再帰考えなければそこまでたいしたことない気がする。
pick
> let rec pick f = function - | x::xs -> - match f x with - | Some(y) -> y - | None -> pick f xs - | [] -> failwith "KeyNotFoundException" - ;; val pick : f:('a -> 'b option) -> _arg1:'a list -> 'b > [1..10] |> pick (fun x -> if x = 5 then Some(x*x) else None);; val it : int = 25 > [1..10] |> pick (fun x -> if x = 12 then Some(x*x) else None);; System.Exception: KeyNotFoundException
scan
> let scan f init list = - let rec sc f init t = function - | x::xs -> - let calced = f init x - (sc f (calced) (calced::t) xs) - | [] -> List.rev t - sc f init [init] list > List.scan (+) 0 [1..4];; val it : int list = [0; 1; 3; 6; 10] > scan (+) 0 [1..4];; val it : int list = [0; 1; 3; 6; 10]
Q.4次元って存在するの?
A.F#で見た
Array4D.zeroCreate<int> 3 3 5 5 val it : int [,,,] = [|rank=4|]
ArrayXD.iteriは元の配列を書き換え、ArrayXD.mapiは新しい配列を生成する
val arr2A : int [,] = [bound1=0 bound2=1 [0; 0; 0; 0] [0; 0; 0; 0]] > let arr2B = Array2D.mapi (fun i j _ -> 10 * i + j) arr2;; val arr2B : int [,] = [bound1=0 bound2=1 [1; 2; 3; 4] [11; 12; 13; 14]] > arr2B;; val it : int [,] = [bound1=0 bound2=1 [1; 2; 3; 4] [11; 12; 13; 14]] > arr2A;; val it : int [,] = [bound1=0 bound2=1 [0; 0; 0; 0] [0; 0; 0; 0]]
シーケンス
ジェネリックなコレクションを抽象化した奴でジェネレーター
> seq;; val it : (seq<'a> -> seq<'a>) = <fun:clo@290-10> > seq { for n in 0..9 do - let m = n * n - if m >= 10 then yield m };; val it : seq<int> = seq [16; 25; 36; 49; ...]
遅延実行される奴で無限リストを扱えるけど停止性に注意
OCamlだとラクダの本の第6章にありますね
# type intseq = Cons of int * (int -> intseq);; type intseq = Cons of int * (int -> intseq) # let rec f x = Cons(x+1, f);; val f : int -> intseq = <fun> # f;; - : int -> intseq = <fun> # f 30;; - : intseq = Cons (31, <fun>) # let rec step n x = Cons(x+n, step (n+1));; val step : int -> int -> intseq = <fun> # let rec nthseq n (Cons(x, f)) = if n = 1 then x else nthseq (n-1) (f x);; val nthseq : int -> intseq -> int = <fun> # #trace nthseq;; nthseq is now traced. # nthseq 6 (step 1 0);; nthseq <-- 6 nthseq --> <fun> nthseq* <-- Cons (1, <fun>) nthseq <-- 5 nthseq --> <fun> nthseq* <-- Cons (3, <fun>) nthseq <-- 4 nthseq --> <fun> nthseq* <-- Cons (6, <fun>) nthseq <-- 3 nthseq --> <fun> nthseq* <-- Cons (10, <fun>) nthseq <-- 2 nthseq --> <fun> nthseq* <-- Cons (15, <fun>) nthseq <-- 1 nthseq --> <fun> nthseq* <-- Cons (21, <fun>) nthseq* --> 21 nthseq* --> 21 nthseq* --> 21 nthseq* --> 21 nthseq* --> 21 nthseq* --> 21 - : int = 21
ちょっとしたものなら#traceがかなり便利です。
> Seq.empty;; val it : seq<'a> = seq [] > Seq.singleton "F#" ;; val it : seq<string> = seq ["F#"]
パターンのシングルトンではないシングルトンはなにかで見た記憶があったなと思ったけど、C#でググれの人の過去の記事でした。
http://ufcpp.wordpress.com/2014/04/13/singleton/
そして、リストや配列は、seq型として束縛した時からシーケンスとして扱われるのであり、遅延評価性を帯びるようになります
> Seq.map id [0; 1; 2; 3; 4; 5; 6; 7; 8; 9];; val it : seq<int> = seq [0; 1; 2; 3; ...]
takeの例はちょうどこの性質を利用していますね。
他のSeqモジュール内の例はC#と動き的には大差ない感じ。
pairwise
> Seq.pairwise [];; Seq.pairwise [];; ^^^^^^^^^^^^^^^ error FS0030: Value restriction. The value 'it' has been inferred to have generic type val it : seq<'_a * '_a> > Seq.pairwise [100];; val it : seq<int * int> = seq [] > Seq.pairwise [100; 200];; val it : seq<int * int> = seq [(100, 200)] > Seq.pairwise [100; 200; 300];; val it : seq<int * int> = seq [(100, 200); (200, 300)]
1要素の時は[]は忘れそう
unfold
シーケンスを生成するための関数
> Seq.unfold (fun (n:int) -> - let t = 1. / (4. * (float n) + 1.) - 1. / (4. * (float n) + 3.) - Some ((t, n + 1))) 0 - |> Seq.take 400 - |> Seq.fold (fun a b -> a + b) 0. - |> ( * ) 4.;; val it : float = 3.140342654
arctan(1)をやってみた
といっても数式に適用するだけですが
集合
常に整列済みで重複のない要素を持つimmutableなコレクションで、内部的なデータ構造は二分木によって実装されています。
集合演算用の関数はSQLと同じ感じですね。
add,removeはその名の通り
辞書
各種関数はわりとおなじみのものだけど
dictで作られるのは、要素の追加と削除が許されない読み取り専用の辞書です。
要素の追加と削除が可能なmutableな辞書が必要なら、Dictionary型を使用します。
> dict (seq { for n in 0..3 -> n, n * n } );; val it : System.Collections.Generic.IDictionary<int,int> = seq [[0, 0] {Key = 0; Value = 0;}; [1, 1] {Key = 1; Value = 1;}; [2, 4] {Key = 2; Value = 4;}; [3, 9] {Key = 3; Value = 9;}]
型はIDictionaryで中身はseq?なんですかね
OOP Mix
type Parttimer(name:string) = ... (* getのみ *) member prt.Name = name (* get&set *) member prt.Job with get() = job and set(v) = job <- v (* setのみ *) member prt.Grade with set(v) = grade <- v (* 付加的コンストラクター *) new(name, job, grade) as this = Parttimer(name) then this.Job <- job this.Grade <- grade
プライマリコンストラクターはScalaと似たような書き方で、時期C#でも使えるようなのですね。
付加的コンストラクターはScalaの補助コンストラクタな感じ
ただし、プライマリコンストラクターは必ず呼び出されなければなりません。
呼び出さないで定義してみる
type Hoge(name:string) = let mutable age = 0 let mutable name = name member this.Name with get() = name and set(v) = name <- v member this.Age with get() = age and set(v) = age <- v new(name:string, age:int) as this = this.Name <- name this.Age <- age ;; error FS0001: This expression was expected to have type Hoge but here has type unit type Hoge(name:string) = let mutable age = 0 let mutable name = name member this.Name with get() = name and set(v) = name <- v member this.Age with get() = age and set(v) = age <- v new(name:string, age:int) as this = Hoge(name) then this.Age <- age ;; type Hoge = class new : name:string -> Hoge new : name:string * age:int -> Hoge member Age : int member Name : string member Age : int with set member Name : string with set end
Hoge型期待してるのにUnit型だよ!型エラーだよ!とちょっとわかりにくいメッセージですね
付加的コンストラクターのthen節では、他のコンストラクターを呼び出してインスタンスを作成した後に、続けて行いたい副作用処理を記述することができます。
を見るとmutableへの代入はUnitを返す事が分かっているのでメッセージ的には自然なメッセージとも取れます
パイプラインとともに
オブジェクトとしての変数には型推論が効かないと言うことです。
> (fun s -> s.Length);; (fun s -> s.Length);; ----------^^^^^^^^ error FS0072: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved. > (fun (s:string) -> s.Length);; val it : s:string -> int = <fun:clo@3> > "hoge" |> (fun s -> s.Length) ;; val it : int = 4
sの型がわからんからLengthってプロパティもあるか分からんってな事でエラーが出ている
A type annotation may be neededなのでsの型を指定した真ん中の場合には、sがstringでstringならLengthというプロパティを持っている事は知っているのでエラーは出ないと。
さらに、型の決定は左から右に行われる事を利用して、前方パイプを使う事で型推論を働かせる
名前付き引数とオプション引数
type Foo = static member Add(x, y) = x + y static member Add(x, y, ?z) = let z = defaultArg z 0 x + y + z type Foo = class static member Add : x:int * y:int -> int static member Add : x:int * y:int * ?z:int -> int end Foo.Add(1, 10) Foo.Add(x=1, y=10) Foo.Add(y=10, x=1) Foo.Add(1, 20, 30) Foo.Add(x=1, y=2, ?z=Some 3) Foo.Add(x=1, y=2, ?z=None)
宣言にも?zとオプションと分かる印が出力に現れていますね。
インターフェース
インターフェースとは、実装のないクラスのことです。
abstractなメンバーのみを含むtype定義がインターフェースの定義になります。
明示的なキーワードはないと。
type Human = abstract FirstName : string with get abstract SecondName : string with get abstract FullName : string with get > type Human = interface abstract member FirstName : string abstract member FullName : string abstract member SecondName : string end type Avatar = abstract Handle : string with get > type Avatar = interface abstract member Handle : string end
interfaceとして宣言されているのが分かります。
実装クラス
type Person(fname, sname, hdl) = interface Human with member h.FirstName = fname member h.SecondName = sname member h.FullName = fname + "_" + sname interface Avatar with member a.Handle = hdl override p.ToString() = let human = p :> Human let avatar = p :> Avatar avatar.Handle + "@" + human.FullName > type Person = class interface Avatar interface Human new : fname:string * sname:string * hdl:string -> Person override ToString : unit -> string end
C#で同じような事やるとしたらこんな感じになっちゃうかなぁ?
public interface Human { string FirstName { get; } string SecondName { get; } string FullName { get; } } public interface Avatar { string Handle { get; } } public class Person : Human, Avatar { private string _firstName; private string _secondName; private string _handle; public string FirstName { get { return _firstName; } } public string SecondName { get { return _secondName; } } public string FullName { get { return FirstName + "_" + SecondName; } } public string Handle { get { return _handle; } } public Person(string fname, string sname, string hdl) { _firstName = fname; _secondName = sname; _handle = hdl; } public override string ToString () { return this.Handle + "@" + this.FullName; } }
F#はインターフェースとして振る舞うときにアップキャストが必要になるけど、C#はprivateの変数宣言したりで助長な感じが。
同じ名前のプロパティを持ったインターフェースを同時に実装する場合に、どちらかを明示的に実装するかは無理なのかな?
ちょっと調べきれていない
オブジェクト式
無名クラスを生成して、生成した無名クラスを即インスタンス化して、指定された規定型にアップキャストする機能です。
アクティブパターン
さっきのPersonにプロパティを追加してお遊び
type Person(fname, sname, hdl) = (* 略 *) member this.FirstName = (this :> Human).FirstName member this.SecondName = (this :> Human).SecondName member this.Handle = (this :> Avatar).Handle (* 略 *) (* Personが侵略者か判定するのアクティブパターン *) let (|RealPerson|Invader|) (p:Person) = if p.FirstName = "taro" then Invader else RealPerson (* オブジェクト式を使って化けの皮を剥がしてみる *) let fact (p:Person) = match p with | RealPerson -> p | Invader -> {new Person(p.FirstName, p.SecondName, "Invader") with override t.ToString() = "<Invader>" } p.Handle > val it : string = "AC" (fact p).Handle > val it : string = "Invader"
taroの化けの皮を剥がすことに成功しました。
はい。
.NET Frameworkのライブラリを使用する
読み込んでいるアセンブリ確認してみた
# mono -V Mono JIT compiler version 3.4.0 ((no/954ed3c Wed Jun 11 14:51:12 EDT 2014) Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com TLS: normal SIGSEGV: altstack Notification: kqueue Architecture: x86 Disabled: none Misc: softdebug LLVM: yes(3.4svn-mono-(no/e656cac) GC: sgen
> System.AppDomain.CurrentDomain - |> fun x -> x.GetAssemblies() - |> Array.map(fun x -> x.GetName().Name);; val it : string [] = [|"mscorlib"; "fsi"; "FSharp.Core"; "FSharp.Compiler"; "System"; "FSharp.Compiler.Interactive.Settings"; "System.Core"; "System.Windows.Forms"; "FSI-ASSEMBLY"; "Mono.CompilerServices.SymbolWriter"; "Mono.Posix"|]
Mono.CompilerServices.SymbolWriterとMono.Posixがいい感じにアレコレ頑張ってくれているものみたいですね
Systemをインタラクティブで読み込んでみる
> #r "System";; --> Referenced '/Library/Frameworks/Mono.framework/Versions/3.4.0/lib/mono/4.5/System.dll'
掲載されれているのと同じモジュール作成
# fsharpc --target:library FSharpFun.fs F# Compiler for F# 3.1 (Open Source Edition) Freely distributed under the Apache 2.0 Open Source License
できたdllをXamarinStudioのアセンブリブラウザで開いてみたら
[CompilationMapping (SourceConstructFlags.Module)] public static class FSharpToCSharp { // // Static Methods // [CompilationArgumentCounts (new int[] { 1, 1 })] public static int add (int a, int b) { return a + b; } [CompilationArgumentCounts (new int[] { 1, 1 })] public static int add2args (int a, int b) { return a + b; } public static int addTuple (int a, int b) { return a + b; } public static int inc (int a) { return a + 1; } }
ここまで逆アセンブルされるのがちょっと驚きですが、F#のmodule定義とほぼ同じですね。
11章は頭がおいついてない感じなので改めて挑戦する。