LoginSignup
37
36

More than 5 years have passed since last update.

Swiftの関数を===出来るようにする

Last updated at Posted at 2014-08-04

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)とでもして、hiloを比べればいいのではないかと思われるが、そうも行かない、試してみればわかるが、

let g = f

とした時にgの中身はfのそれとは一致しない。「Swiftのfuncは、ポインターを二つ含む構造体である」というところまでは推察できたが、それではそのポインターが何を指しているかはSwiftだけではわからない。

そんなさなか、以下のページを見つけた。

ここまでわかれば、こちらのものだ。上記ページの著者はCを使ってfuncに内包された関数ポインターを取り出しているが、上に示した通り、これはSwiftだけで達成できる。さらにclosureの同一性まで確認するためには、関数ポインターのみならずencloseされた環境へのポインターも必要となるが、これは関数ポインターのお隣さんだ。methodの場合、それがselfへのポインターとなっている。

ちなみにこの問題、stackoverflowで懸賞がかけられていた。ちょっとした難問だったのだが、Cに頼らず解けたのは我ながらちょっと驚いた。

何がうれしいの?

これが出来ることで、メタプログラミングの幅が広がる。funcをキーとした辞書やセットもこれで実装できるようになるし、動的に関数の実装を切り替えることすら可能となるのだ。

Enjoy!

func dankogai(q:Q)->A

37
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
36