WWDC 2015のSwift関連の発表資料を眺めていたら,待望のRecursive EnumがSwift 2.0で追加されるようですね.同2.0で追加されたswitch以外でのenumパターンマッチと組み合わせると,Associated Enumの使い勝手がかなりよくなると思われます.(残念ながら現時点のXcode 7 betaでは,この機能はまだ実装されておらず実際に試すことができません.)
ちょっとフライング気味ですが,どんな感じになるのかだけでも見てみたいと思います.
<追記 (2015/07/22)>: Xcode 7 beta4 でサポートされました.ただし,パターンマッチをswitch
文以外で使うとコンパイラが落ちます.
再帰的Enum
関数型言語でよく使うリスト構造は再帰的enumで定義すると以下のように書ける(ようになるはず?).
enum List<T> {
case Nil
indirect case Cons(T, List<T>)
}
let xs: List<Int> = .Cons(1, .Cons(2, .Cons(3, .Cons(4, .Nil))))
indirect
というキーワードは,タプル内の要素を参照型と見なせという指示なのでしょうかね.f
コンパイラは今までも再帰を認識できてたので,別にこのキーワードがなくても良さそうな気がしますが,人間にとってのアノテーションの意味合いがあるのかもしれませんね.
パターンマッチを使いまくる
Swift 2.0ではswitch文以外,while, for, if文等でもenumのパターンマッチが使えるようになりました.
これらを使ってListに関数を追加していきましょう.
Haskellの定番パターンマッチ(x:xs
)に習って.Cons(x, xs)
をパターンとして使います.
extension List {
func toArray() -> [T] {
var list = self
var array: [T] = []
while case let .Cons(x, xs) = list {
array.append(x)
list = xs
}
return array
}
}
whileの中にswitchを書かずに条件の所でパターンマッチ一発で要素(xとxs)を取り出せるので,以前と比べて大分すっきりと記述できるようになったと思いませんか?
ちなみに,Swift 2.0では内部関数でも再帰が使えるようになったので,whileの代わりに末尾再帰な内部関数を定義してやる方法もあります.
続いて定番のfilterとmap関数は再帰関数として,
extension List {
func filter(pred: T -> Bool) -> List<T> {
if case let .Cons(x, xs) = self {
if pred(x) {
return .Cons(x, xs.filter(pred))
}
return xs.filter(pred)
}
return .Nil
}
func map<U>(f: T -> U) -> List<U> {
guard case let .Cons(x, xs) = self else {
return .Nil
}
return .Cons(f(x), xs.map(f))
}
}
とこんな感じになります.mapの方はguardを使って早期脱出にしてみました.guardを使うとネストが浅くなって良さげです.
<参考> Swift 1.2 でのやり方
比較のために,Swift 2.0以前の書き方を載せておきます.
といっても,参照型(class)だけでリストを定義するとパターンマッチが使えず全く面白くないので,enum+参照型の定義を使ったやり方です.
class Ref<T> {
let val: T
init(_ v: T) { val = v }
}
enum List<T> {
case Nil
case Cons(Ref<T>, Ref<List<T>>)
}
func cons<T>(x: T, xs: List<T>) -> List<T> {
return .Cons(Ref(x), Ref(xs))
}
今まではAssociated Enumの要素を参照型でラップしてやることで再帰的enumを定義していました.面倒くさいですね...
extension List {
func toArray() -> [T] {
var list = self
var array: [T] = []
while true {
switch list {
case .Nil:
return array
case let .Cons(x, xs):
array.append(x.val)
list = xs.val
}
}
}
func map<U>(f: T -> U) -> List<U> {
switch self {
case .Nil:
return .Nil
case let .Cons(x, xs):
return cons(f(x.val), xs.val.map(f))
}
}
func filter(pred: T -> Bool) -> List<T> {
switch self {
case .Nil:
return .Nil
case let .Cons(x, xs):
if pred(x.val) {
return cons(x.val, xs.val.filter(pred))
}
return xs.val.filter(pred)
}
}
}
switchだらけな上,パターンマッチ後に参照から値を取り出す(.val)必要があリ,見た目がいまいちすっきりしない気がします.