すっごく簡単なクラス図を考へてみる。Aが親のクラスみたいなので、B・C・Dがその下にある、と。で、ある函数に値を渡したときに、BかCかDかによって、それぞれ別の値が返ってくるやうな動きをするやうに期待したい。つまり、その値がBなら"BB"、Cなら"CCC"、Dなら"DDDD"って値が返ってくることにしたい。
飽くまで「クラス」は例であって、いまはAとB・C・Dの間に継承は考へないものとする。
それで、F#の場合。結果からコードで表してみると、こんな感じのが欲しい。
> let b, c, d = B, C, D ;; val d : A = D val c : A = C val b : A = B > func b ;; val it : string = "BB" > func c ;; val it : string = "CCC" > func d ;; val it : string = "DDDD"
これは昨日の札幌F#勉強会で復習として出された問題(を噛み砕いて勝手に改変したもの)でした。もしかしたらそんな意図の問題ではなかったかもしれない。
この問題を出されるまですっかり忘れてたのだけれど、なるほど、これは判別共用体とパターンマッチの典型的な使ひかたらしい。
type A = | B | C | D let func x = match x with | B -> "BB" | C -> "CCC" | D -> "DDDD" > func b ;; val it : string = "BB" > func c ;; val it : string = "CCC" > func d ;; val it : string = "DDDD"
ちゃんと動いてますね。これはかっこいい。
type A = B | C | D
は判別共用体といって、Cの列挙体と共用体を組合せたみたいな感じの型らしいです。ふむふむふむ。typeの後についてるのが「型名」で、そのあとに列挙されてるのが「ケース識別子」といふらしいです。ほむほむ、名前を覚えられなくても、これはすぐにおぼえて活用できる便利機能ですな。
このままだと列挙体と変はらないのだけれど、値が付随できるのがまたかっこいい。
これはあまりにアレな例で、といふよりもクラスの「たとへ」はあまり適切ではない気がして、この場合は単にパターンマッチを使ってポリモーフィズムっぽいことを実現したいだけなので、もっとふつうにはこんな感じ。
type Job = | Teacher | Engineer | Designer | Other of string ;; > let bob = Engineer ;; val bob : Job = Engineer > let alice = Teacher ;; val alice : Job = Teacher > let john = Designer ;; val john : Job = Designer > let tadsan = Other "NEET" ;; val tadsan : Job = Other "NEET"
この場合はOtherが自由記入欄みたいになってますが、もちろんどのケースにも値を持てて、値が要るケースには函数適用みたいにして渡してやるのですね。
この判別共用体と似た(同じ?)仕組みにオプションがあります。これまたかっこいいのだけれど、自分で良い例がぱっと思ひ浮かばなかったので、この場は「実践F#」から拝借して紹介するに留めておきます。
> let tryDivision x y = if y <> 0 then Some (x / y) else None ;; val tryDivision : int -> int -> int option > tryDivision 6 3 ;; val it : int option = Some 2 > tryDivision 6 0 ;; val it : int option = None
Rubyならnil
を返しておくかー、みたいなデザインにするところですね。それを静的型で同じやうなことができるのが素敵。
せっかくなので一番最初の問題をRubyで書いたらこんな感じになりますね!
class A; end class B < A; end class C < A; end class D < A; end b = B.new c = C.new d = D.new func = ->(x){ case x when B; "BB" when C; "CCC" when D; "DDDD" end } func[b] func[c] func[d]
F#のパターンマッチの方がずっと柔軟なんだけど、この例に限ってはRubyでも似た感じに書けるよ! ってお話でした。
ついでにですね、
func2 = ->(x){ case x when A; "AAAAA" when B; "BB" when C; "CCC" when D; "DDDD" else; "Other 他人です" end }
この例だと、さっきのAを継承したB・C・Dクラスのインスタンスであるところの b, c, d はすべてwhen A
でひっかけることができます。