書いて覚えるSwift入門

第8回 Swift 2を待ちながら

この記事を読むのに必要な時間:およそ 3.5 分

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で実行します。

注1)
この連載は,Software Design 2015年10月号に掲載されたものです。

実行しないを実行する―遅延評価

で,何を実行するかというと,実行しないことを実行します。なんだか禅問答のようですが,これのないプログラミングはあり得ません。次のコードを見てみましょう。

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 2016年12月号

2016年11月18日発売
B5判/192ページ
定価(本体1,220円+税)

  • 第1特集
    適材適所で活用していますか?
    NoSQLの教科書
  • 第2特集
    文字コード攻略マニュアル
    HTML・Java・Ruby・MySQLのハマりどころ
  • 第3特集
    年末特別企画
    温故知新 ITむかしばなしスペシャル
  • 一般記事
    [次世代言語]Elixirの実力を知る――Phoenixで高機能Webアプリ開発(後編)
    ElixirにおけるプロセスとPhoenixによるアプリ開発

著者プロフィール

小飼弾(こがいだん)

1969年生まれ,東京都出身。元ライブドア取締役の肩書きよりも,最近はPokemon GOのガチトレーナーのほうが有名になりつつある……かもしれない永遠のエンジニアオヤジ。

活躍の場はIT業界だけでなく,サブカルからアカデミックまで多方面にわたり,ネットからの情報発信は気の向くまま毎日毎秒! https://twitter.com/dankogai,ニコニコチャンネルは,http://ch.nicovideo.jp/dankogai,blogはhttp://blog.livedoor.jp/dankogai/

当社刊行書籍は『小飼弾のアルファギークに逢ってきた』『小飼弾のコードなエッセイ』など。他にも著書多数。

コメント

コメントの記入