ここ数日Swiftを触ってみた印象など、良くなることを願いつつ雑にメモしておきます。 Xcode6.1の頃の話です。
代数的データ構造を彷彿とさせる魔改造を施されたenumですが、再帰的データ構造が出来そうで出来ないのでガッカリ感が有ります。
enum List { // (!) Recursive value type 'List' is not allowed
case Car(AnyObject)
case Cdr(List?)
}
正座して実装される日を待ちます。
参考: Algebraic Data Types in Swift
サブクラスで特殊化しようとしても、何らかのGenericにしないとコンパイルが通りません。
class BaseClass<T> { }
// (!) Classes derived from generic classes must also be generic <- えっ
class SubClass : BaseClass<Int> { }
typealiasで誤魔化したり、無意味な型パラメータを渡してスルーするなどのワークアラウンドが有りますが、いかんせんメタプログラマー涙目なので早めの改善が期待されます。
Failable Initializerで早目にnilを返そうとしても非Optionalなメンバを初期化し終わるまでinit?を抜けられません。
class Foo {
private var natural: Int
init?(natural: Int) {
// (!) All stored properties of a class instance must be initialized
// before returning nil from an initializer <- 何言ってんだこいつ
if (natural < 1) { return nil }
self.natural = natural
}
}
継承が絡むとsuper.initも呼ばないといけないので更にややこしくなります。 「失敗は素早く検出して抜けろ」というプラクティスに従ってすんなりnilを返させて欲しいところです。 implicitly unwrappedでワークアラウンド出来るとはいえ、どうやらこれはバグらしいので、改善を待ちましょう。
Swiftのコンストラクターは、
1.Explicitなメンバを全て初期化 2.(継承していれば)super.initを呼ぶ 3.その他の処理(Failable initでnilを返すケースも含む)
という順番で記述するのですが、2が終わるまでselfもprivate methodも呼べないので初期化処理をサブルーチンに分割したり、リセット処理と共通化したりする事が出来ません。
class Foo {
private var bar: Int
private func reset() {
bar = 0
}
init() {
// (!) Variable 'self.bar' used before being initialized <- そんなぁ..
reset()
}
}
一応これもimplicitly unwrappedで解決出来ますが、!が蔓延るとOptionalの恩恵がどんどん薄れていくので悲しくなります。 元々selfの存在が不確定な時点で自由にアクセス出来たObj-Cが変だったと言えばそうなのですが、そこに慣れていると結構面喰います。
これは散々言われてる上に時間が解決してくれる話ですが、今はガリガリ書いてると大体1時間に1回位はクラッシュする気がします。 大抵は20秒位で復活しますが、たまにコード補完が死んだ時の復旧作業が発生するのはなかなか困り者です。 ステップ実行のソース位置がずれるぐらいはご愛嬌ですが、言語パラダイムとか移行コストとか以前にここで躓いてしまうチームも多そうなので、是非中の人には頑張って頂きたいです。
Exceptionを無くす事自体は個人的には賛成なのですが、いかんせんObj-Cの低レベルAPIがハンドリングを要する例外(NSFileHandleとかNSFileHandleとか)を投げてくるパターンがあるので、完全に処置無しはきつい気がします。 そういう低レベルなコードは引き続きObj-Cで書いて、Swift自体は綺麗なままにしておくという方向性も一定有りではあります。
virtualの代わりになるかと思われたrequiredですが、initにしか使えません。 引き続きprotocolで代替出来ると言われればそれまでですが、他のメソッドでも使えたら嬉しい気がしました。 ただ、そもそも目的が異なるのでこれは別にいい気がしてきました。
Closureを担当した人はおそらくRubyistだったのではないかと思いますが、Rubyと違ってreturnする値は型推論に影響を与えるので、()で返したいClosure式は最後にreturnを付けるなどする必要が有ります。
class A {
func f(g: () -> ()) {
g()
}
func callF() {
// これはダメ
// (!) Type '()' does not conform to protocol 'IntegerLiteralConvertible'
f { 1 }
// これはOK
f { 1; return }
}
// でもこれはダメ
// (!) Missing return in a function expected to return 'Int'
func h() -> Int { 1 }
// これはOK
func h() -> () { 1 }
}
既にこの挙動に依存したコードも多くなってそうで取り返しは付かない気がしますが、そこだけreturnの意味論が特殊になってしまっている()を返す際の書き方が異なるので、一貫性を欠いた仕様に見えます。
とはいえ記述量を減らすには有効ですし、型推論のおかげで間違いが起こらないようにもなっているので、return一文でとやかく言うのも無粋かもしれません。
※ 一部訂正、ご指摘ありがとうございました。
@objc
を付ければ使えるとは言え、出来ればクリアなSwiftでもoptionalが使いたいところです。
アプリケーションレイヤでは必要ないかもしれませんが、ライブラリではさすがに必要でしょう。
せっかく型にOptionalがあるので、ちょっと糖衣構文があっても良さそうな気もしますが。。
現実歪曲空間では高速に動作する等と揶揄されていますが、まだ少なくともゲームループの中に突っ込みたいとは思わないです。 Obj-Cは即座にC/asmにフォールバック出来たので速度に関して疑問を挟む余地がほぼ無かったのですが、Swiftに関してはまた探求していかないとですね。
参考: http://blog.sudeium.com/2014/12/10/swift-performance-too-slow-for-production/
Rustに並び今後数年のハイブリッドパラダイム言語の流れを作ろうとしたその心意気や良し、です。 コード量はほぼ確実に減らせるので、言語を選べる状況ならば個人的には上からドメインレイヤまではもうデフォルトでSwiftで書いてしまって良いと思います。 まだまだ安定しているとは言い難いですが、Obj-CにしてもARCとBlocksが導入されるまでは大分辛かったですし、言語仕様が変わった時にXCodeが不安定になるのは前からなので、そうした頃を思えば現状は随分うまくやっていると思います。
ただ、ライブラリに関しては互換性や安定性の面で引き続きObj-Cで書いた方がベターだと思います。 何にせよBridging Headerに一行書けばObj-Cライブラリを取り込めるので、Cocoapods含めObj-C資産との共存はかなり楽です。
いくつかSwiftでリリースしてみたらまた印象が変わりそうですが、今のところはこんな感じでした。
この後で5万行程度のSwift+UIKitのアプリを出荷してみたところで得た知見としては、ここで挙げたようなメリットデメリットなどよりも、コンパイル時間の遅さ、コンパイラ最適化スイッチのバグ(exit 1地獄)、IDEの補完の遅さ等による"非機能的な"問題によるインパクトが大きかった。開発のスムーズさを犠牲にしてSwiftが型レベルで保証してくれる安全性はコンパイラのバグによって相殺されてしまった。
少なくともコード生成の対象としてSwiftを選ぶことは、万が一Obj-Cがdeprecateされない限り、今後とも無いだろう。