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

OCaml

# (*) 8 6;;
Warning 1: this is the start of a comment.

おお、けど大人しくOCamlに合わるのでいいんじゃないかな。

# ( * ) 8 6;;
- : int = 48

ビット演算子

OCaml

# 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]

C#

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章は頭がおいついてない感じなので改めて挑戦する。