Swift

Swiftで気になった事

More than 3 years have passed since last update.


Swift で気になった事

Swiftの仕様及び現状のSwiftコンパイラの動作について気になった事があったのでメモ。


例外処理が無い

例外が発生するような状況は、Optionalでnilを返すか、またはエラーで停止する。

割り切った仕様で、良いと思う。

Optionalを使えば、ぬるぽにもならずに大概の場合は対応出来る。

初期のJavaでのthrows節地獄でJavaが嫌いになって以来、Javaには触れてないんだけど、今のJavaの例外処理はどうなってるかな? いや、あまり興味無い。


Trailing closure が便利

元々Rubyで発明された機能(だと思う)。

使い方によってプログラムの見通しが良く簡潔に書ける場合があるので、積極的に使いたい。


yieldが無い

yieldが使えれば、イテレータ、ジェネレータ、コルーチン、軽量スレッドなどがとても記述し易いので是非欲しかった。

仕方が無いので、Generator.next()を使うか、クロージャを使う事になるが、それでもコルーチンは書けないよね。


メモリ管理が参照カウント方式で面倒

リンク切れの循環参照オブジェクトのメモリが自動で回収されないので、

unownedとかweakをプログラマが指定しなければならないのが面倒。

クロージャにもCapture Listを指定すべき場合があるとか、そんな事考えたくない。

メモリリーク問題はコンピュータが頑張って欲しい。

面倒な指定をしなくてもそのうち回収される、という事になるようにガベージコレクタを実装して欲しい。

これはSwiftの問題というよりiOSとMacOSの問題か。


Array,Dictionaryの実装がclassではなくstruct

代入、コピーしたArray, Dictionaryの挙動に妙なクセがある。

The Swift Programming Language” の “Assignment and Copy Behavior for Collection Types” の章は じっくり読んで頭のなかに叩き込んでおくべき かと。

多分この挙動でハマる人がいっぱい出てくる。


[07/10 追記]

Beta3で奇妙な挙動は修正されている。よかった。 (相当叩かれたのだろうな)

ただ、String, Array, Dictionary は、 NSString, NSArray, NSDictionary と異なり

classではなくstructで実装されているので注意。



External Parameter Name はClosureでは使えない仕様だが、定義時にはエラーにならない

 1> var ff = { (#x: Int) -> Int in return x + 1 }

ff: (Int) -> Int =
2> ff(2)
$R8: (Int) = 3

どうでも良いか。


Dictionaryにnilを代入した時の挙動

var d1 = Dictionary<Int, Int> の場合は、 d1[1] = nil とするとd1[1]が削除されるが、

var d2 = Dictionary<Int, Int?> の場合は、 d2[1] = nil とするとd2[1]の値がnilになる。


[07/10 追記]

Beta3では、d2[1]は削除された。 nilの代入で常に削除する挙動になったようだ。



空配列初期化時のOptionalの型がちょっと変

  1> var x1 = Int[]()

x1: Int[] = size=0
2> var x2 = Int?[]()
x2: Int?[]? = size=0
3> var x3 = Int[]?()
x3: Int[]? = nil
4> var x4 = Int?[]?()
x4: Int?[]? = nil
5> var x5 = Array<Optional<Int>>()
x5: Int?[] = size=0
6> var x6: Int?[]
x6: Int?[] = size=0
7> var x7 = Array<Int?>()
x7: Int?[] = size=0

x2 の型は Int?[] になるべきと思うのだが。

Int?[] 型にしたい場合は、x5,x6,x7 のようにする。


[07/10 追記]

Beta3で修正されている。表記法も、 Int[] ではなく [Int] のように変更されている。

var x2 = [Int?]()

x2: [(Int?)] = 0 values

[Int][(Int)] の違いは不明。



ジェネリックなclassで、型パラメータでメンバ変数を宣言するとコンパイラが死ぬ

Welcome to Swift!  Type :help for assistance.

1> struct Hoge<T> {
2. var x: T
3. init(x: T) { self.x = x }
4. }
5> class Fuga<T> {
6. var x: T
7. init(x: T) { self.x = x }
8. }
LLVM ERROR: unimplemented IRGen feature! non-fixed class layout
Assertion failed: (err == 0), function ~Mutex, file /SourceCache/lldb_KLONDIKE/lldb_KLONDIKE-320.3.100/source/Host/common/Mutex.cpp, line 246.
Abort trap: 6

配列などでラップすると使える。

class Fuga2<T> {

var xHolder: T[]
init(x: T) { self.xHolder = [x] }
var x: T {
get { return xHolder[0] }
set { xHolder[0] = newValue }
}
}

これはコンパイラのバグだと思うので、多分そのうち直るだろう。


[07/10 追記]

Beta3で修正されている。よかった。



関数の戻り値のstructはimmutableになっている

structではメソッドチェーンみたいな書き方をしたい場合にmutating funcは呼べない。

Welcome to Swift!  Type :help for assistance.

1> struct A {
2. init(){}
3. func f()->A { return self }
4. mutating func g()->A { return self }
5. }
6> A().f().f()
$R1: A = {}
7> A().f().f().g()
<REPL>:7:9: error: immutable value of type 'A' only has mutating members named 'g'
A().f().f().g()
^ ~

7> var a = A().f().f()
a: A = {}
8> a.g()
$R2: A = {}

f()もg()もstructを返しているが、この値がimmutableになっている様で、g()を呼び出すと

g()はmutating funcであるため、エラーになる。

mutating func を呼び出したい場合は、一旦変数に代入しなければならない。

メソッドチェーンを使いたいなら、classにしておけば良いだろう。


protocol はジェネリックに出来ない

これはとても変な仕様だと思う。

protocol Generator<T> {

mutating func next() -> T?
}

と書きたいのに、それは出来ずにこうなっている。

protocol Generator {

typealias Element
mutating func next() -> Element?
}

この Element の型がクセモノで、凝ったことをする時に困る。


protocol_cannot_use_Generics.swift

// このプログラムはコンパイル出来ません

// HogeGenerator は Int を生成する Generator である。
struct HogeGenerator : Generator {
var x: Int
init(x: Int) { self.x = x }
mutating func next () -> Int? {
return x++
}
}

// GeneratorHolder はIntを生成するGeneratorを受け取って変数 gen に保持する。
struct GeneratorHolder <G: Generator where G.Element == Int> {
var gen: G
init(gen: G) { self.gen = gen }
mutating func setgen(gen: G) { self.gen = gen }
mutating func next () -> Int? {
return gen.next()
}
}

// FugaGenerator は Int を生成する Generator である。
struct FugaGenerator : Generator {
var x: Int
init(x: Int) { self.x = x }
mutating func next () -> Int? {
return x--
}
}

var hoge = HogeGenerator(x: 10)
var holder = GeneratorHolder(gen: hoge)
// holderは、hogeを保持している。

holder.next()

var fuga = FugaGenerator(x: 100)
holder.setgen(gen: fuga)
// error: type '$T5' does not conform to protocol 'Generator'

// holderがfugaを保持するように変更したら、エラーになった!
// これはholder生成時にhogeを保持したので G が HogeGenerator に決定されたので、
// FugaGenerator は型が合わないのだ。
// (そういう意味のエラーメッセージとはわかりにくいが)

// とすれば、HogeGeneratorとFugaGeneratorの共通の親であるGenerator型の変数で保持すればいい。

struct GeneratorHolder2 {
var gen: Generator
init(gen: Generator) { self.gen = gen }
mutating func setgen(gen: Generator) { self.gen = gen }
mutating func next () -> Int? {
return gen.next()
//error: 'Generator' does not have a member named 'next'
}
}

// 今度は gen.next() がエラーになった!
// genには、Intを生成するGeneratorしか渡していないのだが、コンパイラはそんな事は知らないので、
// Elementの型がわからず、対応するnext()を発見できない。
// (そういう意味のエラーメッセージとはわかりにくいが)


では新たなプロトコルを作ればいい?


protocol_cannot_use_Generics_2.swift

protocol IntGenerator : Generator {

mutating func next() -> Int?
}

struct HogeGenerator : IntGenerator {
var x: Int
init(x: Int) { self.x = x }
mutating func next () -> Int? {
return x++
}
}

struct IntGeneratorHolder {
var gen: IntGenerator
init(gen: IntGenerator) { self.gen = gen }
mutating func setgen(gen: IntGenerator) { self.gen = gen }
mutating func next () -> Int? {
return gen.next()
}
}

struct FugaGenerator : IntGenerator {
var x: Int
init(x: Int) { self.x = x }
mutating func next () -> Int? {
return x--
}
}

var hoge = HogeGenerator(x: 10)
var holder = IntGeneratorHolder(gen: hoge)

holder.next()

var fuga = FugaGenerator(x: 100)
holder.setgen(fuga)

holder.next()

// 動いた!

// でも、他のIntを生成するGeneratorはどうするの?
// 例えば、RangeGenerator<Int> とか。

holder.setgen( (1...10).generate() )
// error: type 'RangeGenerator<$T3>' does not conform to protocol 'IntGenerator'

// RangeGenerator<Int>は、next()のシグネチャは同じであってもIntGeneratorを実装している訳ではないので、
// エラーになってしまう…


もしprotocolにジェネリックを使えたならば、下のように書けて、ちゃんと動くはず。

struct HogeGenerator : Generator<Int> {

...
}

struct GeneratorHolder {
var gen: Generator<Int>
...
}

struct FugaGenerator : Generator<Int> {
...
}

Appleが仕様を変更してくれる事を望む。


[07/18 追記]

クロージャを介在させる事で、一応GeneratorHolderは実現出来ます。

与えられたジェネレータインスタンスを、メンバ変数に保持するのではなく、クロージャにバインドされる変数に保持します。


class GeneratorHolder<T> : Generator {

typealias Element = T
var genClosure: (() -> Element?)!
func setGen<G:Generator where G.Element == T>(generator:G) {
var g = generator // クロージャにバインドされる
genClosure = {
return g.next()
}
}
func next() -> Element? {
return genClosure()
}
}

struct SomeGenerator : Generator {
typealias Element = Int
func next() -> Element? { return 10 }
}
class AnotherGenerator : Generator {
typealias Element = Int
func next() -> Element? { return 20 }
}

var holder = GeneratorHolder<Int>()
holder.setGen(SomeGenerator())
holder.next() // --> 10
holder.setGen(AnotherGenerator())
holder.next() // --> 20
holder.setGen((1...10).generate())
holder.next() // --> 1
holder.next() // --> 2
holder.next() // --> 3