248
247

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swiftの関数はファーストクラスである--とは限らない

Last updated at Posted at 2014-06-27

ここQiitaには初投稿のです。

Swiftにおいて、関数はファーストクラス、つまり関数は第一級関数であることはすでに皆さんご存知のとおりです。つまり、

func f(x:XType)->RType {
    var result:RType
// ....
    return result
}

let f =  { (x:XType)->RType in
    var result:RType
// ....
    return result
}

の構文糖衣に過ぎないということです。このことを強調するためかどうかはわかりませんが、Swiftでは{}は一意にブロックであり、 JavaScript の Object リテラルや Perl や Ruby の Hash リテラルを示すこともあるという曖昧さが一切ありません。試しに以下のコードをPlaygroundなりでお試し下さい。

{ println("Hello, \($0)") }("Swift")

ところが、次のコードはエラーになります。

let hello = { println("Hello, \($0)") }

なぜでしょう?

それがわかれば、本記事の答えも見えてきます。

答えは、$0の型が不明だから。前者では引数の"Swift"から型推論できますが、後者には型推論の「材料」に欠けています。

よって、次のように型を明示してやればうまく行きます。

let hello = { (s:String)->() in println("Hello, \(s)") }

ジェネリック関数 = 型も引数化しちゃえ

しかし型を明示すると、今度はこういう時に困ってしまいます。

hello(941)

IntStringでないので当然コンパイルエラーなのですが、だとしたら必要な型の数だけ関数も定義しなければならないのでしょうか?こんな風に。

func hello(s:String)->() { println("Hello, \(s)") }
func hello(i:Int)->() { println("Hello, \(i)") }
func hello(d:Double)->() { println("Hello, \(d)") }
// ....

そうそう言い忘れましたが、Swiftの関数はこの通り多重定義が可能で、実際に呼び出す関数は関数の型を見て判別されます。なぜかこのことは「アマツバメ本」は明記されていないようなのですが、使っているうちに自然と気がつきます。

でもいくら多重定義が可能でも、こんなの繰り返し書くのはうざいですよね。

そこで登場するのがジェネリック関数です。

func hello<T>(x:T)->() { 
    println("Hello, \(x)")
}

これ一つで

hello("Swift");
hello(941);
hello(1.08)
hello([1,1,2,3,5,8,13]);
hello(["Swift":0]);

全部行けます。

引数だけではなく型も「引数」にしておくと。C++ や Java でおなじみの型、もとい方も少なくないでしょう。さらに Swift の場合、 Haskell 同様、引数から型推論してくれるので、関数の定義で指定したTは使うときには不要です。

Genericというのは「汎用」という意味なので、「汎関数」と訳したい気持ちがありますが今のところはまだ根付いていないようで、そのままカタカナで呼ばれているようです。

「我輩はジェネリック関数である。名前はまだない」は許されない

ここで、最初に立ち戻ります。「func f(){}というのはlet f = {}の構文糖衣に過ぎない」と私は言いました。それでは

func f<XType, RType>(x:XType)->Rtype {
    var result:Rtype
// ....
    return result
}

に対応するlet文は存在するのでしょうか?

let f =  { <XType,RType>(x:XType)->RType in
    var result:RType
// ....
    return result
}

だめです。

let f =  <XType,RType>{ (x:XType)->RType in
    var result:RType
// ....
    return result
}

だめでした。

どうやら、現時点では無名のジェネリック関数は書けないようです。C++やJavaに慣れた方なら当然と思われるでしょうが、Haskellersの皆さんはちょっと失望したかも知れません。Haskellであれば

hello x = "Hello " ++ show(x)

hello = \x -> "Hello " ++ show(x)

と同意であるからです。型は一切書いてません。Show (a0 -> [Char])であることが適切に推論されます。

要するに

Swiftの型推論はジェネリックではない

ということです。実際の型まで落とし込むか、それが出来なければコンパイラーがさえずるかのどちらかです。Haskellのようにジェネリックに推論しておいて、使用時に型を代入してくれるわけではありません。

型推論というのは意外と重い作業でもあるので、Swiftが主に利用されるであろう用途を考えればこれは適切なトレードオフだとは思うのですが、その一方、こういうものを書く時にあったらよかったのになあとも願わずにいられないのもまた事実です。

見てのとおり、ghciに丸投げした型推論をそのまま写経しております。

//
//  lambda.swift
//  church
//
//  Created by Dan Kogai on 6/15/14.
//  Copyright (c) 2014 Dan Kogai. All rights reserved.
//

/* Church Booleans and Operations */
func t<T1,T0>(x:T1)->T0->T1 {
    return {y in x}
}
func f<T1,T0>(x:T1)->T0->T0 {
    return {y in y}
}
// (t10 -> (t20 -> t30 -> t30) -> t0) -> t10 -> t0)
func and<T10,T20,T30,T0>(p:T10->(T20->T30->T30)->T0)->T10->T0 {
    return {q in p(q)(f)}
}
// ((t30 -> t20 -> t30) -> t10 -> t0) -> t10 -> t0
func or<T10,T20,T30,T0>(p:(T30->T20->T30)->T10->T0)->T10->T0 {
    return {q in p(t)(q)}
}
// (t10 -> t10 -> t0) -> ((t20 -> t20 -> t20) -> (t30 -> t30 -> t30) -> t10) -> t0)
func xor<T10,T20,T30,T0>(p:T10->T10->T0)->((T20->T20->T20)->(T30-> T30->T30)->T10)->T0 {
    return {q in p(q(f)(t))(q(t)(f))}
}
// ((t10 -> t20 -> t20) -> (t40 -> t30 -> t40) -> t0) -> t0
func not<T10,T20,T30,T40,T0>(p:(T10->T20->T20)->(T40->T30->T40)->T0)->T0{
    return p(f)(t)
}
// (t10 -> t20 -> t0) -> t10 -> t20 -> t0
func cif<T10,T20,T0>(p:T10->T20->T0)->T10->T20->T0 {
    return {tn in {el in p(tn)(el)}}
}

/* Church Numerals and Operations */

// SUCC := λnfx.f (n f x)
// ((t1 -> t) -> t2 -> t1) -> (t1 -> t) -> t2 -> t
func succ<T1,T,T2>(n:(T1->T)->T2->T1)->(T1->T)->T2->T {
    return {f in {x in f(n(f)(x))}}
}
// ADD := λm n f x. m f (n f x)
// (t2 -> t1 -> t) -> (t2 -> t3 -> t1) -> t2 -> t3 -> t
func add<T2,T1,T,T3>(m:T2->T1->T)->(T2->T3->T1)->T2->T3->T {
    return {n in {f in {x in m(f)(n(f)(x))}}}
}
// MUL := λm n f. m (n f)
// (t1 -> t) -> (t2 -> t1) -> t2 -> t
func mul<T1,T,T2>(m:T1->T)->(T2->T1)->T2->T {
    return {n in {f in m(n(f))}}
}
// POW
// t1 -> (t1 -> t) -> t
func pow<T1,T>(m:T1)->(T1->T)->T {
    return {n in n(m)}
}
// 0
func c0<T>(f:T->T)->T->T {
    return {x in x}
}
// 1
func c1<T>(f:T->T)->T->T {
    // return {(x:T)->T in f(x)}
    return {x in succ(c0)(f)(x)}
}
// 2
func c2<T>(f:T->T)->T->T {
    // return {(x:T)->T in f(f(x))}
    return {x in succ(c1)(f)(x)}
}
// 3
func c3<T>(f:T->T)->T->T {
    // return {(x:T)->T in f(f(f(x)))}
    return {x in add(c1)(c2)(f)(x)}
}
// ISZERO
// ((t10 -> t20 -> t30 -> t30) -> (t50 -> t40 -> t50) -> t0) -> t0
func iszero<T50,T40,T30,T20,T10,T0>(n:(T10->T20->T30->T30)->(T50-> T40->T50)->T0)->T0 {
    return n({x in f})(t)
}
// PRED
// (((t30 -> t20) -> (t20 -> t10) -> t10)-> (t40 -> t50) -> (t60 -> t60) -> t0)-> t30 -> t50 -> t0
func pred<T60,T50,T40,T30,T20,T10,T0>(n:((T30->T20)->(T20->T10)-> T10)->(T40->T50)->(T60->T60)->T0)->T30->T50->T0{
    return {f in {x in n({g in {h in h(g(f))}})({u in x})({u in u})}}
}
// fix operator is a cheat for good reason.
// cf. http://blog.livedoor.jp/dankogai/archives/50463152.html
func fix<T>(f:(T->T)->T->T)->T->T {
    return { x in f(fix(f))(x) }
}

以上はじめてのQiitaでした。

Dan the Generic Swift Newbie

248
247
3

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
248
247

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?