だとしたら、Swiftではカリー化は不要ということになってしまう。
もちろん、そんなことはない。
求めているのは、「n-tupleを引数とする関数を、1-tupleづつ引数を受け取り、n個受け取った時点ではじめて値を返す関数にすること」である。
例えばこんな風に。
import Darwin // for log(Double)
func logWithBase(base:Double)->Double->Double {
return { x in log(x) / log(base) }
}
let log10 = logWithBase(10)
log10(100) // 2.0
そうすること、つまり
func f<T0,T1,...Tn,R>(a0:T0,a1:T1,...,an:Tn)->R
を
func f<T0,T1,...Tn,R>(a0:T0)->T1->T2->...->Tn->R
にするのが、カリー化(currying)というわけである。言葉にするとまだるっこしいが、関数の宣言として書くと直感的にわかる。
任意の関数をcurry化するには?
それでは、任意の関数を受け取ってそれをカリー化する関数は書けるのだろうか?
2-tupleの関数であれば、あっさり書ける。
func curry<A,B,R>(f:(A,B)->R)->A->B->R {
return {a in {b in f(a,b)}}
}
自分で定義した関数のみならず、ビルトインはおろか演算子関数さえ受け付ける。
let plusplus = curry(+)(1)
plusplus(41) // 42
3-tupleでも
func curry<A,B,C,R>(f:(A,B,C)->R)->A->B->C->R {
return {a in {b in {c in f(a,b,c)}}}
}
4-tupleでも
func curry<A,B,C,D,R>(f:(A,B,C,D)->R)->A->B->C->D->R {
return {a in {b in {c in {d in f(a,b,c,d)}}}}
}
しかし、任意のtuple長の関数を、たった一つのcurry()
で賄えるのか?
長さ変われば型変わる
ここでやっと本題に入れる。
できない、のである。
なぜか。
長さが変わると、型も変わってしまうから。
[0,1]
と[0,1,2]
はどちらも同じ[Int]
型だが、(0,1)
と(0,1,2)
は前者が(Int,Int)
型で後者が(Int,Int,Int)
型。任意の型の関数を受け取るところまでは、「Swiftの関数の引数は、常に一つ」で示した通りA->R
で行けるのだが、任意の型を返すのはジェネリクスをもってしても無理なのである。
これは、Pythonのtuple()のような、任意のSequence
から同じ長さのtupleを生成するような関数はSwiftでは実装できないことを意味する。
もっとも実行時も変数が自分の型を覚えている動的型のPythonのtupleと、コンパイルされたら(どうしても必要な場合を除いて)型が外される、静的型のSwiftやScalaなどのtupleは、ずいぶんと意味が異なる。むしろ静的型の言語から見れば、Pythonのそれはtupleというより単なるimmutable listに過ぎず、実際各要素へのアクセスは、
(0,0.0,"zero")[2] // "zero"
とlistとまるで変わらないのだし。
AnyObject
であれば、Pythonと同様のことをObjective-C的に実装することも出来なくはなさそうであるが、SwiftにおいてAnyObjectへのキャストは意味がまるで異なるので、「静的型の要素からなる任意の長さのSequenceをtuple化すること」は「出来ない」といい切ってもよいだろう。なお、Any
とAnyObject
の違いについては、記事を改めてまた書く予定だ。
[0,1] == [0,1] が出来て (0,1) == (0,1) が出来ないなんて
Tupleをサポートする他の静的言語を使い慣れた人が、一番びっくりするのはこれかも知れない。
なんと、tupleどおしの等しさを確認できないのだ。
(要素がEquatable
な)配列どおしの等しさなら確認できるのに。
もちろん、これはすっぴん状態で、ということであり、以下のようにすれば 2-tuple (Doubleと呼びたいところだが倍精度浮動点小数にもう抑えられている)が即座に比較可能になる。
@infix func ==<A:Equatable,B:Equatable>(lhs:(A,B),rhs:(A,B))->Bool {
return lhs.0 == rhs.0 && lhs.1 == rhs.1
}
(0,1) == (0,1) // true as it should be
これをtupleの長さごとに書くのは正直めんどくさすぎる。
せつなさの理由
とはいうものの、そうなっている理由を察せないわけでもない。
組み合わせ爆発だ。
Tupleの要素となりうる型がm種類あるとすると、要素数nまでのtupleの型の種類は、tupleのネストを認めなかった場合でさえ(m+1) ** nも存在することになる。
実際にはtupleの入れ子はSwiftでも認められている。そして(T,T,T)
と(T,(T,T))
と(((T,T),T)
はそれぞれ別の型なのである。
型の増大を防ぐことができるのがメリットだと思うよ - http://qiita.com/f81@github/items/a8419532c316d190782d
ずいぶんな誤解である。「型定義の増大」であれば話はわかるのであるが。
Tupleの最大長は(C#は8?)、Scalaが22、Haskellが64。ずいぶんと短いのは、これが原因ではないか。
ちなみに現時点においてSwiftではtupleの最大長は無制限なようである。無制限な代わりに、すっぴんでは.0
,.1
...と、各要素のgetterしか持たない(ラベル付きのtupleではそのラベルも使えるが)。extensibleでもないので、
extension (Int, Int) : Equatable {}
のようなことも出来ない。
そういうことをしたければ、
struct Point2d: Equatable {
var x = 0.0, y = 0.0
}
@infix func ==(lhs:Point2d, rhs:Point2d)->Bool {
return lhs.x == rhs.y && lhs.y == rhs.y
}
といった具合にきちんと型を作りなさいというメッセージなのだろうか。そして「任意の長さのtuple」であれば、代わりに配列を使えというメッセージなのだろうか…
(Dan, Swift, Newbie)