DT日記

家を離れた自宅警備員の日記

F#がすげーかっこいい話(判別共用体)

すっごく簡単なクラス図を考へてみる。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でひっかけることができます。