Objective-Cを知らない子供たち
SwiftがWWDC2014で登場したのは2014年6月2日。OS X YosemiteとXcode 6でSwiftが1.0になったのは同年10月19日。本稿執筆時点で実はまだ1年も経っていません[1] 。にもかかわらず、すでにSwiftはiOS/OS Xプログラミングにおいて当たり前の存在となっています。どれくらい当たり前かというと、高校二年生と中学二年生の娘たちにせがまれてLife is Tech! というスクールに彼女たちを通わせているのですが、そこで教えているのがObjective-Cを飛ばしていきなりSwift。彼女たちは文字どおり「Objective-Cを知らない子供たち」であるわけです。
にもかかわらず、次のSwift 2は今のSwift 1とはかなり非互換で、前回で触れたとおりprint()
の仕様すら変わっています。正直執筆者泣かせもいいところなのですが、わずか1年でObjective-CからのエクソダスをやりとげたAppleにしてみれば、この程度はちょろいのかもしれません。
とはいえ、言語仕様が定まらないことには記事も書けないわけで、現時点におけるSwiftは1、正確には1.2で行くしかありません。本稿が読者の皆さんに届くころには、次のiPhoneがiOS 9とともに出ているかもしれませんが、Xcode 7とともに正式になるはずのSwift 2はまだのはずです。というわけで今回はSwift 1でもSwift 2でも同等に扱えそうな話題を取り上げます。前述の理由でprint()
すら避けたいので、すべてplaygroundで実行します。
実行しないを実行する―遅延評価
で、何を実行するかというと、実行しないことを実行します。なんだか禅問答のようですが、これのないプログラミングはあり得ません。次のコードを見てみましょう。
if (true) {
"真"
} else {
"偽"
}
なんの変哲もないif文ですが、これをplaygroundで実行してみましょう(図1 ) 。
図1 if then else
“ 真” だけが右に表示され、“ 偽” のところは“ Will never be executed” と出てきます。条件分岐はプログラミングにおいて必須の機能ですが、「 成立した場合に実行するコードを実行する」というのは「成立しない場合に実行するコードを実行しない」と同値なのです。
それでは、「 実行しないを実行する」を、そのために用意されたif
などの構文を使わずに実現できるでしょうか?
次のコードを見てみましょう。
func noop<T>(a:T, b:T){}
var t = 0
var f = 0
noop((t += 1), (f -= 1))
t
f
見てのとおり、noop
は「何もしない」関数です。何もしないのであれば、t
もf
も0のままのはずですが、t
は1に、f
は-1になってしまっています。(t += 1)と(f -= 1)
が実行されてしまっているのです。
これを防ぐためにはどうしたらよいでしょう?
()
を{}
に変えてみましょう。つまりnoop((t += 1), (f -= 1))
をnoop({t += 1}, {f -= 1})
にしてみるのです。今度はどうなったでしょうか? t
もf
も元のままです。(t += 1)
は「t += 1
を実行した結果」ですが、{t += 1}
は「t += 1
を実行する関数」 。実行「する」 、つまり、まだ実行されていないのです。
このことを利用すれば、「 実行しないを実行する」を実現できそうです。試しにif
文を構文ではなく関数として実装してみましょうか。関数として実装するので、rubyのf
のように値を返すようにします。
irb(main):001:0> result = if true then "T"
else "F" end
=> "T"
irb(main):002:0> result
=> "T"
つまり、三項演算子? :
と同じ機能を持つ関数を実装するのです。ただし、if
文も三項演算子も使わずに(リスト1、図2 ) 。
リスト1 IF文の使い方
import Cocoa
func IF<R>(
PRED:()->Bool, // 引数なしでBoolを返す関数
THEN:()->R, // 引数なし、戻り値Rの関数
ELSE:()->R // 引数なし、戻り値Rの関数
) -> R {
let dict = [ // [Bool:()->()] な辞書
true:THEN, // true には THEN を
false:ELSE // false には ELSE を紐付け
]
let which = dict[PRED()] // PRED()の結果で辞書引き
return which!() // それを実行した結果を返す
}
let truth = IF({true}, {"真"}, {"偽"})
truth
func fact(n:Int) -> Int {
return IF({n <= 1}, {n}, {n * fact(n-1)})
}
fact(10)
図2 IF文の使い方
実は同様のコードは連載第1回でも紹介したのですが、連載第1回の時とは異なり、総称関数としてIF
を実装しています。つまり三項演算子? :
と完全に互換です。さらにカスタム演算子の機能を使って任意の三項演算子を定義でき……ればいいのですが、Swiftにカスタム三項演算子を定義する機能はありません。? :
しか存在しないのでわざわざカスタム演算子を定義する需要もなさそうなので、さほど残念ではありませんが。
実際Swiftには@autoclosure
という属性(attribute)も用意されていて、これで宣言した引数は自動的に{}
でくくられていることになります。assert()
などはそのように実現されているのです。
λはつらいよ
というわけで、「 実行しないを実行する」は、「 実装しといて実行しない」という形で実現できるわけです。これを突き詰めるとどうなるでしょうか?
それが関数しかない世界、ラムダ演算 です。
この世界において、0
や1
はどう表現すればよいでしょうか? 結論から言うとこうなります。
func zero<T>(f:(T)->T)->(T)->T {
return {(x:T)->T in x}
}
func one<T> (f:(T)->T)->(T)->T{
return {(x:T)->T in f(x)}
}
func two<T> (f:(T)->T)->(T)->T{
return {(x:T)->T in f(f(x))}
}
つまりzero
はf
をゼロ回x
に適用する関数、one
は1回適用する関数、two
は2回適用する関数というわけです。動的型の関数であればそこで話は終わるのですが、Swiftの型は静的。((T->T)->(T)->T
、日本語で書くと「Tを受け取ってTを返す」関数を受け取り、Tを受けすべての「数値」が同じ型になっていて、整合性が確保されていることがわかります。
それではこれを普通の数値にするにはどうすればよいのでしょうか?
こんな感じにすればよいのです。
var n = two({x in x + 1})(0) // 2
中身を変えれ、たとえば数値ではなく文字の長さで表現することもできます。
var s = two({x in x + "*"})("") // "**"
これで「数」は実装できたのですが、その数同士で演算するにはどうしたらよいでしょう? その結果がリスト2 と図3 です。
リスト2 ラムダ演算の例
/* Operators */
// 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:T1->T)->T2->T in {(x:T2)->T 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:T2->T3->T1)->T2->T3->T in
{(f:T2)->T3->T in {(x:T3)->T 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:T2->T1)->T2->T in {(f:T2)->T in m(n(f))}}
}
// POW
// t1 -> (t1 -> t) -> t
func pow<T1,T>(m:T1)->(T1->T)->T {
return {(n:T1->T)->T in n(m)}
}
// 0
func c0<T>(f:(T)->T)->(T)->T {
return {(x:T)->T in x}
}
// 1
func c1<T>(f:(T)->T)->(T)->T {
// return {(x:T)->T in f(x)}
return {(x:T)->T in succ(c0)(f)(x)}
}
// 2
func c2<T>(f:(T)->T)->(T)->T {
// return {(x:T)->T in f(f(x))}
return {(x:T)->T in succ(c1)(f)(x)}
}
// 3
func c3<T>(f:(T)->T)->(T)->T {
// return {(x:T)->T in f(f(x))}
return {(x:T)->T in add(c1)(c2)(f)(x)}
}
// see if it works
let v = succ(succ(mul(add(c2)(c3))(pow(c2)(c3))))({x in x+1})(0)
v == 42
図3 ラムダ演算の実行例
まだSwiftが1になる前に書いたものですが 、今でもきちんと動いていますし、Xcode 7 betaでも動きました。
次回は……
次回はいよいよSwift 2に触れていきたいと思います。そのときまでに仕様が固まっているといいのですが……。
第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
第2特集
「知りたい」「使いたい」「発信したい」をかなえる
OSSソースコードリーディングのススメ
特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)