- 手軽なスクリプト言語としてのF#
- 手軽なスクリプト言語としてのF# その2
- 手軽なスクリプト言語としてのF# その3
- 手軽なスクリプト言語としてのF# その4
- 手軽なスクリプト言語としてのF# その5
- 手軽なスクリプト言語としてのF# その6
- 手軽なスクリプト言語としてのF# その7
- 手軽なスクリプト言語としてのF# その8「レコード」
- 手軽なスクリプト言語としてのF# その9「クラス」
- 手軽なスクリプト言語としてのF# その10「継承・アブストラクトクラス」
そろそろ、関数言語っぽいことしたいけど、まだもうちょっとかかります。
インターフェース
インターフェースです!インターフェースは、全てabstractなメンバーで構成される型だとインターフェースになるみたいです。
type IFooable = abstract Foo : (string * string) -> string abstract Bar : unit -> unit
[
type Foo() = interface IFooable with member this.Foo ((s1, s2)) = "hogehoge" member this.Bar() = printfn "sample"
F#のインターフェースの実装は、C#でいう明示的な実装なので以下のように書いてもメソッドを呼ぶことは出来ません。
let f = Foo() f.Bar() // コンパイルエラー!
なのでインターフェースにキャストするか、インターフェースのメソッドと同じメソッドを作ってあげる必要があります。因みにアップキャストは、:>でやってダウンキャストは:?>で出来ます。
let f = Foo() (f :> IFooable).Bar() // キャストしてメソッドを呼ぶ
あとは、Fooクラスにインターフェースと同名のメソッドを定義してやる方法もあります。
type IFooable = abstract Foo : (string * string) -> string abstract Bar : unit -> unit type Foo() = interface IFooable with member this.Foo ((s1, s2)) = "hogehoge" member this.Bar() = printfn "sample" // インターフェースのメソッドと同名のメソッドを定義する member this.Bar() = (this :> IFooable).Bar() let f = Foo() f.Bar()
演算子のオーバーロード
さて、F#では演算子も好きなように定義出来ます。
演算子のオーバーロードはstaticメソッドの定義と同じ要領で出来ます。staticメソッドの定義はやってませんが、staticつけるだけな上にクラス名.メソッド名でアクセスするのもC#と同じです。例としてint型の値を保持するHolderクラスを定義して、足し算が出来るようにしてみます。
// 値を保持するだけのクラス type Holder(value) = let value : int = value member this.Value with get() = value // メソッド名は括弧で演算子を囲ったもの static member (+) (lhs : Holder, rhs : Holder) = // 足した結果を返すよ Holder(lhs.Value + rhs.Value) // 足し算してみる let a = Holder(10) let b = Holder(3) let c = a + b // 結果出力 printfn "%d" c.Value
結果は13と表示されます。演算子は、かなり自由に定義できるようになってます。一覧はMSDNを参照してください。
最後にちょっと長いサンプル
さて、今日はインターフェースだけの説明にしようと思ってたのですが何故か急に演算子のオーバーロードもやってしまいました。理由は、インターフェースを使ったサンプルプログラムを書こうと思ってて何かC#でいい題材が無いかと思ってC# インターフェースでBingって一番上に出たものにしようと思ったらこれだったわけですよ。長い上に演算子のオーバーロードまでやってはる…orz
ということで演算子のオーバーロードもやったので、上記のページ(ufcppさんのページね)のサンプルをF#で焼き直してみようと思います。
とりあえず、今までやった範囲内の機能を使って実装してみるので、関数型のうまみは出てないはずです。(サンプルでは構造体使ってますが、構造体は、ここで作ったサンプルでは使ってないです)
open System // 二次元の点を表す type Point(x : float, y : float) = let mutable x = x let mutable y = y // X座標 member this.X with get() = x and set(v) = x <- v // Y座標 member this.Y with get() = y and set(v) = y <- v // ベクトル和 static member (+) (a : Point, b : Point) = Point(a.X + b.X, a.Y + b.Y) // ベクトル差 static member (-) (a : Point, b : Point) = Point(a.X - b.X, a.Y - b.Y) // A-B間の距離を求める static member GetDistance(a : Point, b : Point) = let x = a.X - b.X let y = a.Y - b.Y Math.Sqrt(x ** 2. + y ** 2.) override this.ToString() = sprintf "(%f, %f)" x y // 2次元空間上の図形 type Shape = abstract GetArea : unit -> float abstract GetPerimeter : unit -> float // 2次元空間上の円をあらわすクラス type Circle(center : Point, r : float) = let mutable center = center let mutable radius = r // 円の中心 member this.Center with get() = center and set(v) = center <- v // 円の半径 member this.Radius with get() = radius and set(v) = radius <- v // インターフェースの実装 interface Shape with member this.GetArea() = Math.PI * radius ** 2. member this.GetPerimeter() = 2. * Math.PI * radius override this.ToString() = sprintf "Circle (c = %A, r = %f" center radius // 2次元上の三角形を表すクラス type Triangle(a : Point, b : Point, c : Point) = let mutable a = a let mutable b = b let mutable c = c // 頂点A member this.A with get() = a and set(v) = a <- v // 頂点B member this.B with get() = b and set(v) = b <- v // 頂点C member this.C with get() = c and set(v) = c <- v // インターフェースの実装 interface Shape with member this.GetArea() = let ab = b - a let ac = c - a 0.5 * Math.Abs(ab.X * ac.Y - ac.X * ab.Y) member this.GetPerimeter() = let l = Point.GetDistance(a, b) let l = l + Point.GetDistance(a, c) l + Point.GetDistance(b, c) override this.ToString() = sprintf "Circle (a = %A, b = %A, c = %A)" a b c // 自由多角形を表すクラス type Polygon(verteces : Point array) = let mutable verteces = verteces // 頂点の集合 member this.Verteces with get() = verteces and set(v) = verteces <- v // インターフェースの実装 interface Shape with member this.GetArea() = let mutable area = 0. let mutable p = verteces.[verteces.Length - 1] for q in verteces do area <- area + p.X * q.Y - q.X * p.Y p <- q 0.5 * Math.Abs(area) member this.GetPerimeter() = let mutable perimeter = 0. let mutable p = verteces.[verteces.Length - 1] for q in verteces do perimeter <- perimeter + Point.GetDistance(p, q) p <- q perimeter override this.ToString() = let sb = System.Text.StringBuilder() sb.AppendFormat("Polygon({0}", verteces.[0]) |> ignore for p in verteces do sb.AppendFormat(", {0}", p) |> ignore sb.Append(")") |> ignore sb.ToString() let Show (f : Shape) = printfn "図形 %A" f printfn "面積/周 = %f" (f.GetArea() / f.GetPerimeter()) let t = Triangle(Point(0., 0.), Point(3., 4.), Point(4., 3.)) let c = Circle(Point(0., 0.), 3.) let p1 = Polygon([|Point(0., 0.); Point(3., 4.); Point(4., 3.)|]) let p2 = Polygon([|Point(0., 0.); Point(0., 2.); Point(2., 2.); Point(0., 2.); Point(2., 2.)|]) // 多態!! Show(t) Show(c) Show(p1) Show(p2)
実行結果が正しいのかどうかわかりませんが、実行してみました。
図形 Circle (a = (0.000000, 0.000000), b = (3.000000, 4.000000), c = (4.000000, 3.000000)) 面積/周 = 0.306635 図形 Circle (c = (0.000000, 0.000000), r = 3.000000 面積/周 = 1.500000 図形 Polygon((0.000000, 0.000000), (0.000000, 0.000000), (3.000000, 4.000000), ( 4.000000, 3.000000)) 面積/周 = 0.306635 図形 Polygon((0.000000, 0.000000), (0.000000, 0.000000), (0.000000, 2.000000), ( 2.000000, 2.000000), (0.000000, 2.000000), (2.000000, 2.000000)) 面積/周 = 0.184699
まとめ
ちょっと長いプログラムを書きましたが、基本的にC#とほぼ1対1に対応する感じになっています。なので、とりあえずC#っぽい言語としてF#を使えるようになってきたころかな?