書いて覚えるSwift入門

第4回 遺産の継承

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

今回も前回の予告どおり,SwiftからCやObjective-Cの遺産を活用します。

巨人の肩への乗り方

本題に入る前に,今までの言語はどうやって過去の――おもにCの――遺産を活用してきたのかを振り返ってみましょう。「以前の言語でできたことを,新しい言語でもできるようにする」にあたっては,次の4とおりの方法が考えられます。

  1. 新しい言語で一から書き直す
  2. 今までどおり過去の言語で書く
  3. 過去の言語の仕様を拡張した言語で書く
  4. 過去の資産へのインターフェースを設ける

1.「新しい言語で一から書き直す」というのは,言語デザイナが最も犯しやすい過(あやま)ちなのかもしれません。確かに新しい言語であれば,今まで書きにくかったプログラムが書きやすくなるかもしれません。しかし,そのために今まで培ってきたプログラムを書き直すには,プログラム資産があまりに膨大です。趣味グラマーなら車輪の再発明もまた楽しいものですが,そうでないほとんどのプログラマにとって,すでにあるプログラムまで一から書き直せというのは,全財産を捨てて出家せよというのに等しい暴言であり,「じゃあ来世で」という返事が返ってくること請け合いです。この例で成功と呼べる例は,Javaぐらいでしょうか。しかしうまくいった最大の理由がどう見ても「Cとクリソツな構文」ということ自体,このやり方がいかにうまくいかないかの証左だと筆者は感じます。

2.「今までどおり過去の言語で書く」というのはある意味で新言語の否定でもあるのですが,1.よりははるかに人気があるオプションです。過去の遺産に関して失うものは何もないのですから。C言語はその代表格で,数多(あまた)のOS,そして数多の新言語もCで実装されているのは皆さんご存じのとおりです。しかし過去の遺産に関して失うものがないというのは,負の遺産に関しても同様であり,新言語であれば1行で書けるものを丸々1ページ使って書かねばならないのもなんとも痛い話です。

3.「過去の言語の仕様を拡張した言語で書く」というのは,冷戦とバブルが崩壊する前までは最も人気があったオプションかもしれません。Objective-CもC++もこれに該当します(どちらも1983年生まれ)。2.同様,失うものは何もなく,それでいて新機能を得るという点でこの方法は一見理想的にも思えますが,しかし負の遺産をも継承してしまうというのは,実は2.と共通しています。JavaをはじめとするC非互換のオブジェクト指向言語であればobject.method()と直感的に書けるところを[object method]と書かねばならなかったり,演算子オーバーロードはできても新演算子を定義したり優先順位を変更できなかったりするのは,そうしてしまうとCでなくなってしまうから。そのC自体,まだ進化がゆっくりではあっても止まっておらず,C99が登場した結果,C++がもはやC上位互換とは言えなくなってしまったことを考えれば,一見理想的なこの方法が2.と同様の困難を抱えていることも理解できます。

4.「過去の資産へのインターフェースを設ける」というのは,今までの試行錯誤を繰り返してたどり着いた境地とも言えます。先鞭を付けたのはPerl 5でしょうか。XSというインターフェースを通して,Perl自体をC言語で拡張できるようにしたのです。このしくみは,新言語の仕様を旧言語にしばられることなく旧言語の資産を活用できるという点で画期的であり,PythonやRubyをはじめ,その後(普及という意味で)成功した言語のほとんどがこの手法を採用しています。そのPerl 5は一方でPerl 4という旧言語に対しては3.のアプローチをとっていて,おかげで$object.method()注1ではなく$object->method()とC++と同様の「1文字余計な」表記を強いられたのは歴史の皮肉ではありますが。

Swiftもまた,4.のアプローチを採用することで,Objective-C without C”,つまりCの負の遺産の解消に成功しました。

注1)
.は文字列連結演算子として使用済みだった。

import Framework

前口上はこれくらいにして,実際に過去の遺産を活用してみましょう。というか読者の皆さんは,すでに知らない間に活用しているのです。

たとえば,新規のPlaygroundを作成すると,空ではなく次のようなコードがあらかじめ挿入されています。

// Playground - noun: a place where people can play

import Cocoa

var str = "Hello, playground"

このコードの尻に,sqrt(2.0)と入れてみましょう。期待どおり1.4142135623731と右に答えが表示されたはずです。

この状態で,import Cocoaの行注2をコメントアウトしてみましょう。どうなりましたか?

図1のように,Use of unresolved identifier 'sqrt'sqrtなんて識別子なんて知らん」というエラーが出ました。そうなのです。「生の」Swiftには,平方根すら定義されていないのです。

図1 Swiftには平方根の定義がない?

図1 Swiftには平方根の定義がない?

どうやら,ソフトウェア資産はObjective-Cの場合と同様importするべきもののようです。

注2)
PlatformにOS Xを指定した場合。iOSを指定した場合はimport UIKitになる。

俺のSwiftにポインタがあるはずがない

importを使うことで,CやObjective-Cで書かれたライブラリの関数にアクセスできそうだということはこれでわかりました。

しかしそれらの関数の多くは,引数や戻り値がポインタです。Javaなどと同様,ポインタがないはずのSwiftでこれらにアクセスするにはどうしたらよいのでしょうか?

Swiftでは,一種の「詭弁」でこの問題をクリアしています図2)。「ポインタがなければ,構造体を食えばいいじゃない!」。

図2 『Using Swift with Cocoa and Objective-Cより抜粋

図2 『Using Swift with Cocoa and Objective-C』より抜粋

ポインタそのものではなく,ポインタにアクセスするための構造体を用意する。このアイデアはRust言語から拝借されたようです。しかしT *でなくてUnsafePointer<T>とはなんとも長ったらしい。これはむしろ改悪なのではないか?

そうならないことは,実際にCでポインタを駆使したプログラムを移植してみればわかります。たとえば,標準入力をそのまま標準出力に垂れ流すプログラムを考えてみましょう。cat未満なのでkittenといったところでしょうか。Cで書くとこんな感じですか。

#include <stdio.h>
#define BUFSIZE 4096
int main() {
    char buf[BUFSIZE];
    while(fgets(buf, BUFSIZE - 1, stdin) != NULL){
        fputs(buf, stdout);
    }
    return 0;
}

これをSwiftで書き直すとどうなるか?

import Darwin // or Foundation
let bufsize = 4096
var buf = [Int8]()
buf.reserveCapacity(bufsize)
while fgets(&buf, Int32(bufsize - 1), stdin) != nil
{
    fputs(&buf, stdout)
}

ほとんど変わりません。変わったのは,

  • Swiftの配列は動的なので,初期化してから拡張している
  • Swiftでは中身が書き換わる引数には必ず&を付けることになっているので,bufではなく&buf
  • SwiftはCより型にうるさいので,Int32()を付けている

ぐらいで,あとはそのまま。int main(){ /*...*/ }が不要な分,むしろコンパクトになってさえいます。UnsafePointer<T>のような型宣言はいっさい出てきません。そして型を知りたければ,識別子を[Option]+クリックすれば図3のようにXcodeが教えてくれますし,型の不整合はその場でXcodeが指摘してくれます。

図3 [Option]+クリックでXcodeが教えてくれる

図3 [Option]+クリックでXcodeが教えてくれる

さらにありがたいのは,Cの構造体がそのままSwiftの構造体として使えること。次はARGV[1]で指定したファイルの最終更新日(time)を表示するSwiftコードの例ですが,statに注目してください。構造体そのもののみならず,構造体の中の構造体にもそのままアクセスできています。

import Darwin
if C_ARGC > 1 {
   let cfilename = C_ARGV[1]
   var st = stat()
   if lstat(cfilename, &st) == 0 {
      var mtime = st.st_mtimespec.tv_sec
      let filename = String.fromCString(cfilename)!
      let when = String.fromCString(ctime(&mtime))!
      print("\(filename): \(when)")
   } else {
      perror(cfilename)
   }
}

続きは次号

SwiftによるCライブラリへのアクセスを紹介したところで今回は紙幅が尽きてしまいました。次回はObjective-Cへの連携を見ていくことにしましょう。

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/

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

コメント

コメントの記入