書いて覚えるSwift入門

第8回Swift 2を待ちながら

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
図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「何もしない」関数です。何もしないのであれば、tfも0のままのはずですが、tは1に、fは-1になってしまっています。(t += 1)と(f -= 1)が実行されてしまっているのです。

これを防ぐためにはどうしたらよいでしょう?

(){}に変えてみましょう。つまりnoop((t += 1), (f -= 1))noop({t += 1}, {f -= 1})にしてみるのです。今度はどうなったでしょうか? tfも元のままです。(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文の使い方
図2 IF文の使い方

実は同様のコードは連載第1回でも紹介したのですが、連載第1回の時とは異なり、総称関数としてIFを実装しています。つまり三項演算子? :と完全に互換です。さらにカスタム演算子の機能を使って任意の三項演算子を定義でき……ればいいのですが、Swiftにカスタム三項演算子を定義する機能はありません。? :しか存在しないのでわざわざカスタム演算子を定義する需要もなさそうなので、さほど残念ではありませんが。

実際Swiftには@autoclosureという属性(attribute)も用意されていて、これで宣言した引数は自動的に{}でくくられていることになります。assert()などはそのように実現されているのです。

λはつらいよ

というわけで、⁠実行しないを実行する」は、⁠実装しといて実行しない」という形で実現できるわけです。これを突き詰めるとどうなるでしょうか?

それが関数しかない世界、ラムダ演算です。

この世界において、01はどう表現すればよいでしょうか? 結論から言うとこうなります。

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))}
}

つまりzerofをゼロ回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 ラムダ演算の実行例
図3 ラムダ演算の実行例

まだSwiftが1になる前に書いたものですが今でもきちんと動いていますし、Xcode 7 betaでも動きました。

次回は……

次回はいよいよSwift 2に触れていきたいと思います。そのときまでに仕様が固まっているといいのですが……。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」「使いたい」「発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧