Swiftではデフォルト状態だとtupleの比較が出来ないというのは以前書いた通りだが、関数どうしも===
できないので、出来るようにしてみた。
SYNOPSIS
func peekFunc<A,R>(f:A->R)->(fp:Int, ctx:Int) {
// let (hi, lo):(Int, Int) = reinterpretCast(f)
typealias IntInt = (Int, Int)
let (hi, lo) = unsafeBitCast(f, IntInt.self)
let offset = sizeof(Int) == 8 ? 16 : 12
let ptr = UnsafePointer<Int>(lo+offset)
return (ptr.memory, ptr.successor().memory)
}
@infix func === <A,R>(lhs:A->R,rhs:A->R)->Bool {
let (tl, tr) = (peekFunc(lhs), peekFunc(rhs))
return tl.0 == tr.0 && tl.1 == tr.1
}
// simple functions
func genericId<T>(t:T)->T { return t }
func incr(i:Int)->Int { return i + 1 }
var f:Int->Int = genericId
var g = f; println("(f === g) == \(f === g)")
f = genericId; println("(f === g) == \(f === g)")
f = g; println("(f === g) == \(f === g)")
// closures
func mkcounter()->()->Int {
var count = 0;
return { count++ }
}
var c0 = mkcounter()
var c1 = mkcounter()
var c2 = c0
println("peekFunc(c0) == \(peekFunc(c0))")
println("peekFunc(c1) == \(peekFunc(c1))")
println("peekFunc(c2) == \(peekFunc(c2))")
println("(c0() == c1()) == \(c0() == c1())") // true : both are called once
println("(c0() == c2()) == \(c0() == c2())") // false: because c0() means c2()
println("(c0 === c1) == \(c0 === c1)")
println("(c0 === c2) == \(c0 === c2)")
DESCRIPTION
アマツバメ本には、Closures Are Reference Typesと明記してある。
しかし(関数|メソッド|クロージャー)は、明らかにただの参照ではない。
class Point { var x = 0.0, y = 0.0 }
func genericID<A>(a:A)->A{ return a }
let p = Point()
let f:Int->Int = genericID
sizeofValue(p) // OS X では8
sizeofValue(f) // OS X では16
ただの参照であるclass
とは異なり、ポインター二つ分メモリーを食っている。
それであれば、単純にlet (hi, lo) = reinterpretCast(f)
とでもして、hi
とlo
を比べればいいのではないかと思われるが、そうも行かない、試してみればわかるが、
let g = f
とした時にg
の中身はf
のそれとは一致しない。「Swiftのfuncは、ポインターを二つ含む構造体である」というところまでは推察できたが、それではそのポインターが何を指しているかはSwiftだけではわからない。
そんなさなか、以下のページを見つけた。
ここまでわかれば、こちらのものだ。上記ページの著者はCを使ってfunc
に内包された関数ポインターを取り出しているが、上に示した通り、これはSwiftだけで達成できる。さらにclosureの同一性まで確認するためには、関数ポインターのみならずencloseされた環境へのポインターも必要となるが、これは関数ポインターのお隣さんだ。methodの場合、それがself
へのポインターとなっている。
ちなみにこの問題、stackoverflowで懸賞がかけられていた。ちょっとした難問だったのだが、Cに頼らず解けたのは我ながらちょっと驚いた。
何がうれしいの?
これが出来ることで、メタプログラミングの幅が広がる。funcをキーとした辞書やセットもこれで実装できるようになるし、動的に関数の実装を切り替えることすら可能となるのだ。
Enjoy!
func dankogai(q:Q)->A