ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

Swift エンジニアが学ぶ Rust - 0.序章

Rust はとても良い言語です、 TRust me。

ダジャレから始めるのは初めてでしょうか。Rust 勉強中の tobi462 です。

私はプログラミング言語を学ぶのが好きな畑の人間でして、最近では Rust を学んでいるのですが、これがなかなかどうして面白いのです。

Love Swift ?

おそらく私の一番得意な言語は Swift か Java なわけですが、どちらが好きかと言われれば断然 Swift です。

Haskell のような代数データ型としての enum はとても好きですし、Protocol Oriented Programming や extension による既存クラスの拡張、switch によるパターンマッチも好きです。

機能やシンタックスが多すぎて覚えるのが大変な点や、switch によるパターンマッチの読みづらさといった点、バージョンアップごとに破壊的仕様変更が入る(これほど互換性を意識しないプログラミング言語も珍しいのではと思ったりします)などの点を除けば、 概ねよく出来た言語だと感じていました。

Rust ?

そんな中、Rust という一匹のプログラミング言語に出会いました。 www.rust-lang.org

Mozilla が主導になって開発しており、ポスト C++ として期待される言語だとか、高速だとか安全な並列性だとか、なんとかかんとか。

そして他の言語では聞かないような「所有権モデル」なるものを有しているとか。

Rust の第一印象

そういうわけで Rust を軽い気持ちで学び始めたわけです。

そして感じたのは、所有権モデルの難しさです。

いえ、所有権モデルの考え方自体は難しくないのです・・・そう感じるのです。しかし、実際にコードを書いてみるとコンパイルが通せないことこの上なしです。

コンパイラはとても分かりやすいエラーメッセージを出力してくれます。曰く、ライフタイムがどうとか、借用されてるとか、ムーブされてるとか・・・。

なるほど分からん、というわけです。

HTTPサーバを練習がてら書いてみたのですが、 コンパイルエラーが直せなくて泣きたい気持ちになりました。

そんなわけで、ポスト C++ として優れた言語であろうことは感じつつも、学習コストに見合う言語だとは感じませんでした。

2nd Edition 日本語版

それから1年くらいは Rust を触らなかったように記憶しています。

そんな折に、以下のツイートが TL に流れてきました。

[https://twitter.com/yyu/status/985924298282975237:embed]

Rust のコミュニティが分かりやすい学習リソースを目指して 2nd Edition をリリースしたのは聞いていましたが、英語なのでちょっと面倒だと思って読んでいませんでした。

その日本語版が読めるということで、懐かしさもあり読んでみることにしました。

Rust の美しさ

2nd Edition 日本語版を読み進めていくうちに、私は Rust に夢中になっていました。

当初、複雑だと思った Rust の機能は十分によく考えられて用意されており、分かりづらいと思っていた一部のシンタックスも機能を本質的かつ端的に表現しており、それでいてヒューマンリーダブルとコンパイル速度の両立を計っているのだと感じました。(褒めすぎ?)

そして Rust の機能の理解が進むに連れ、Rust のコードの見え方が少しずつ変化していくことにも気づきました。

これは Haskell を学んだ時の感覚に似ている。そう感じました。

Rust の特徴

先ほどから Rust の言語的な紹介をまるでしていませんでしたが、Rust は以下に焦点をあてた言語であるととされています。

  • 速度
  • 安全性
  • 並行性

速度

速度は C++ と同等レベルを目指しており、関数型プログラミングや Trait といったモダンな機能を取り入れつつも、実行速度は C++ と同程度になっているようです。

一般的にプログラミング言語が高機能になる、すなわち抽象化が進むほど実行時のオーバーヘッドは高くつくものですが、Rust コンパイラは「ゼロコスト抽象化」という考え方を元に、実行時のオーバーヘッドが限りなく少なくなるように設計されています。

安全性

メモリ管理について記憶をたどると C++より後の言語はだいたいガベージコレクタ(GC)を採用している印象があります。

GC はメモリ管理を自動化しようという考え方で、実行時のオーバーヘッドと引き換えにプログラマはメモリ管理から開放され、より生産的になれるというものでした。なお、 Swift で採用されている ARC も GC にあたるようです。

C言語の時代ではメモリ管理はプログラマの重要な仕事でしたが、ハードウェアの圧倒的な進化もあり、現代のプログラミング言語ではメモリ使用量やレイアウトを意識する機会も圧倒的に減ったのではないかと思います。

GC は実際のところ 1960 年代の Lisp でも実装されていたものらしいですが、それはさておきプログラマはメモリ管理から開放されたおかげで、メモリリークやダングリングポインタなどのバグを圧倒的に減らせたとされています。

これは Joel on Software | Joel Spolsky, 青木 靖 |本 | 通販 | Amazon などでも語られていました。

GC というメモリ管理手法は、手動管理に比べると圧倒的に安全であり、解放済みのメモリアドレスにアクセス(ダングリングポインタ)して未定義動作を引き起こしたり、あるいはバッファオーバーフロー脆弱性なども防ぐことが出来ました。

とはいえ GC も万能というわけではなく実行時のオーバーヘッドがつきまといます。

Java では フルGC により一時的にプログラムが停止してしまう問題はよく知られています。

さて前置きが長くなりましたが、 Rust の所有権システムでは、GCのようなオーバーヘッドを発生させることなく、かつ手動管理のような非安全性を排除したメモリ管理を採用しています。

これは一言ではとても説明できないので今後に譲りたいと思いますが、端的に言えばコンパイル時にメモリ安全性を確保するアプローチ、といったところでしょうか。

並行性

プログラミングにおける並行性はとても難しいものです。

マルチスレッドのプログラミングは、誇張するわけでもなくシングルスレッドのプログラミングよりはるかに難しく、それを安全に動作させるというのはさらに難しいです。

現代では Actor モデルとして知られる、メッセージ受け渡しモデルの並行プログラミングが主流になりつつあると感じられますが、これは GC と同じくオーバーヘッドがあり、ハードウェアの限界まで搾り取る必要のあるプログラムには向いてないとされます。(とは言え、Go言語の近況をみるとそれも分かりません)

Rust では OS の生スレッドをAPIとしてサポートしつつ、並行プログラミングにおける課題であるデータ競合に対して、コンパイル時に誤りを検出しようというアプローチを取っています。

すなわちオーバーヘッドなしでOSのスレッドを利用しつつも安全な並行性を実現できるということです。

Rust は最強言語の夢を見るか?

多くのプログラミング言語あるいはミドルウェアやFWがそうであるように Rust も適材適所でしょう。Rust が向いている領域もあれば、Rust が向いてない領域もあるでしょう。

スマートフォンがいくら便利だからと言って、それを缶詰の蓋をあけるのに利用する人はいないでしょう。(いないよね?)

なぜ、わざわざこんな話を持ち出したかというと、(Rust を学習中の身ではあり現時点の予想に過ぎないのですが)おそらく Rust が向いている、すなわちトレードオフに見合うソフトウェア開発プロジェクトというのはさほど多くは無いのではないかと想像しているためです。

現代の潤沢なハードウェアにおいて、ハードウェアの限界まで絞りだすようなパフォーマンスが必須の要件となるプロジェクトはさほど多くはないでしょう。

もちろん OS や ドライバ などのソフトウェア開発などには向いているでしょうが。

教養としての Rust

さて、それを考えると Rust を学ぶべきでしょうか?

プログラミング言語の人気度ランキングにしたがって、職につきやすい言語を学ぶべきではないでしょうか。例えば、JavaとかScalaとかRubyとか(実際、高給なのはScalaなんですかね?)。

それでも私は Rust を学ぶ価値があると感じます。

それは古今東西の偉大なプログラマが Lisp を学ぶべきだと言ってきたのと同じ理由であり、すなわちより良いプログラマになるために Rust を学ぶべきなのではないかということです。

Rust の所有権システムはメモリ管理についての考え方をあらためさせてくれますし、シンタックスと調和の取れたモダンな言語仕様からも多くの刺激を得られます。

Swift はかなりモダンな言語だと思っていましたし、実際モダンな言語だと思いますが、それでも Rust を学んでいくと見えてなかったものが見えてくる気がしました。

特に C言語やC++などの手動メモリ管理をする言語を触ったことがなければ、Rust の所有権システムから得られる学びというのは大きいと感じます。(と言いつつ、C言語とかで低レベルなメモリ管理を学ぶほうが良いのかな、という思いもあったりします)

まぁ、つまり教養として Rust を学ぶというのはとても有意義なことで、それはプログラマとしての考え方を広げてくれるのではないか、そんな風に私は感じるわけです。

D.C. - ダ・カーポ -

という訳で、話は最初に戻ります。

すなわち Swift エンジニアの私が Rust を勉強していこう というわけです。

学習するならアウトプットすべきですし、私が学んだ知識を広く共有することで、誰かの役に立てばそれはプログラマ冥利に尽きるというものです。

そんなわけで Rust の学習記録をブログ記事として連載していきたいと思います。

まぁこういうのは最初だけモチベーション高くて、すぐに飽きてしまう(プログラマとしては決して悪くない正確かもしれませんがそれはさておき)かもしれませんが、まぁ続けていけたらなと思います。

最初の記事にこんなにエネルギーを使って果たして良いものかと思いつつ、このあたりで。

Swift 4.2 で追加される removeAll(:where) メモ(SE-0197)

どうも、運動したあとはお酒が飲みたくなってしまう tobi462 です。

ちょっと時間が空きましたが、今回はremoveAll(:where)という新たに追加されたコレクション系のAPIの Proposal を見ていきたいと思います。

今までの Swift 4.2 の記事は以下です。

なんだか増えてきましたね。

Tl;Dr

let isOdd: (Int) -> Bool = { $0 % 2 == 1 }

var xs = [1, 2, 3, 4, 5, 6]
xs.removeAll(where: isOdd) // mutating only
xs // => [2, 4, 6]

removeAll(where:) / SE-0197

RangeReplaceableCollection プロトコルに removeAll(where:) という mutating なメソッドが追加されました。

通常、特定の要素を除外する一般的な方法としては filter が利用できます。

xs.filter { !isOdd($0) }

しかし、これには2つの欠点があると Proposal では述べられています。

  • isOdd で無いもの 、という指定方法であり可読性が悪い(否定の条件は読みづらい)
  • 新しいコレクションを生成するのでメモリ効率が悪い

可読性

可読性については例えば Ruby であれば select(Swiftでいうところの filter )に対して、 reject というメソッドが用意されています。

xs = [1, 2, 3, 4, 5, 6]
xs.reject {|x| x % 2 == 1}

これは除外したいものをクロージャで指定しており分かりやすいと思います。同様の考えで Swift にも removeAll(where:) を追加するのは良い考えであると思います。(ただし、 filter と違って mutating なメソッドしか無いようですが・・・)

メモリ効率

filter の計算量は要素数を n とした場合 O(n) で固定です。

ただし、常に新しいコレクションを返すためメモリ効率という点から見ると優れているわけではありません。また要素が大きい場合はコピーコストがかかるという課題もあります。

今回の removeAll(where:) は(Proposalによると) shuffle-down というアルゴリズムを使用しており、新たなメモリを確保する必要なく要素の削除が行われるようになっています。

実際に merge された実装は以下のようです。
Add remove(where:) to RangeReplaceableCollection by airspeedswift · Pull Request #11576 · apple/swift · GitHub

なにやら込み入っていますが、要素を swap しつつ、最後に一気に除去する作りに見えます。

これにより filter と違って新たなメモリを確保することなく要素の削除が行えるという思想のようです。

速いか?

100万個の Int が格納された配列から奇数のみを抽出した場合、 filterremoveAll(where:) でどちらが速いか検証してみました。

import Foundation

let isOdd: (Int) -> Bool = { $0 % 2 == 1 }

let mesure: (String, () -> Void) -> Void = {
    let start = Date()
    $1()
    let interval = Date().timeIntervalSince(start)
    print("\($0):\n  \(interval)")
}

let xs = Array(1...1_000_000)
var ys = xs

mesure("filter") {
    xs.filter { !isOdd($0) }
}
mesure("removeAll(where:)") {
    ys.removeAll(where: isOdd)
}

-O で実行したところ、手元のMacBookでは以下の結果となりました。

$ ./swift -O removeAll.swift
filter:
  0.018193960189819336
removeAll(where:):
  0.01980602741241455

10回実行した結果の平均みたいな丁寧な計測方法はしていないのですが、そこまで実行速度には変化が無いようです。

removeAll(where:) はメモリ効率に優れてはいるものの、 filter に比べて処理が高速といったことはおそらくなさそうです。

filter バージョンが欲しい?

mutating なAPIではなく、filter のように新しいコレクションを返すAPIが欲しい人もいるかもしれません。

そうした場合は以下のように自前で実装するのが手っ取り早そうです。

extension Sequence {
    func reject(_ isReject: (Element) -> Bool) -> [Element] {
        return filter { !isReject($0) }
    }
}

let xs = [1, 2, 3, 4, 5, 6]
let ys = xs.reject(isOdd) // => [2, 4, 6]

標準APIでないので初見で戸惑う可能性もありますが、filter の条件を否定で書くよりは素直で可読性がよく見えます。まぁ、トレードオフですね。

まとめ

なんかいろいろ書きましたが、指定した条件の要素を削除できるAPIが追加されたよ、って理解で良いような気がします。

莫大な要素数を処理するとか、あるいはメモリがとても制限された環境だとかであれば、このあたりのメモリ効率などを考えることもあるかもしれませんが。

というわけで無駄に長くなってしまいましたが、今回はこれにて。

【Docker】alpine の `apk update` でエラーが発生した時のメモ

どうも、tobi462 です。

今日は docker-compose build での apk update 時に以下のエラーが発生した時の対処メモです。

Step 7/11 : RUN apk update
 ---> Running in 45856361a825
fetch http://mirrors.aliyun.com/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://mirrors.aliyun.com/alpine/v3.4/main: temporary error (try again later)
WARNING: Ignoring APKINDEX.47c31cae.tar.gz: No such file or directory

Tl;Dr

Docker のバージョンを最新にしたら解決しました。

$ sudo apt-get install docker-ce
$ docker -v
Docker version 18.03.1-ce, build 9ee9f40

突然の死

Jenkins / Ubuntu 16.04 LTS にシステムを自動デプロイをしていたのですが、ある日からデプロイに成功しなくなりました。

そして Job のログを見ると以下のようなエラーが出ていました。

fetch http://mirrors.aliyun.com/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://mirrors.aliyun.com/alpine/v3.4/main: temporary error (try again later)
WARNING: Ignoring APKINDEX.47c31cae.tar.gz: No such file or directory

どうやら docker-compose build 時、何かをDLしようとしたときにエラーになっているようです。

その後、 RUN コマンドを分解して実行したところ、 apk upate で失敗していることがわかりました。

しかし、ローカル(Mac)で docker-compose build しても成功します。

エラーメッセージでぐぐったらいろいろ情報が見つかったので、色々試してみました。

curl で DL できるか試してみる

URLが死んでいるのかと思い、まずは curl でアクセスできるか試してみました。

$ curl -o x.tar.gz http://mirrors.aliyun.com/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  663k  100  663k    0     0  1654k      0 --:--:-- --:--:-- --:--:-- 1654k

どうやらこれは問題ないようです。

ミラーリポジトリを試してみる

ググった中にミラーリポジトリを試してみると良い、という情報があったので試してみました。

どうやら alpine では /etc/apk/repositories にパッケージマネージャ用のリポジトリURLが記載されているようです。

# cat /etc/apk/repositories
http://dl-cdn.alpinelinux.org/alpine/v3.4/main
http://dl-cdn.alpinelinux.org/alpine/v3.4/community

そこで DockerfileRUN コマンドを追加して、ミラーリポジトリを追記してみました。

RUN echo http://mirror.yandex.ru/mirrors/alpine/v3.5/main > /etc/apk/repositories; \
    echo http://mirror.yandex.ru/mirrors/alpine/v3.5/community >> /etc/apk/repositories

残念ながらこの方法では解決しませんでした。

まぁ、 curl で DL は出来ていたのであまり期待はしていませんでしたが。

IP v6 を有効にしてみる?

正直、これはあまり理解していなかったのですが、以下のように /etc/modulesipv6 を足してみたら解決したという記事があったので試してみました。

こういう時、Docker は環境を壊す心配をせずに試せるのでありがたい限りです。

RUN echo "ipv6" >> /etc/modules

まぁ、これも駄目だったのですが。

docker のバージョンアップ

そもそもなぜローカルのMacではビルドが成功するのだろう、と思って Docker のバージョンを調べてみました。

すると、ローカルでは 18.x 系だったのに、Ubuntu 16.04 LTS では 17.x 系でした。

なので試しに Ubuntu 上の Docker も最新にしてみることにしました。

$ sudo apt-get update
$ sudo apt-get install docker-ce

実のところあまり期待していなかったのですが、これで解決しました。

おわり

ということでトラブル対処メモでした。役に立つ人もいるかもしれないので記事にしました。

今回のケースでは Docker のバージョンアップで解決しましたが、状況によって解決策は異なるかもしれません。

Swift 4.2 で追加されるコンパイラディレクティブ(SE-0196)

何でもは知らない、知ってることだけ。

どうも、最近書き出しの挨拶を考えるのに時間を要する tobi462 です。

今日は SE-0196 で追加されたコンパイラディレクティブの紹介です。

今まで書いた Swift 4.2 の記事は以下です。

いつのまにかシリーズ化してきましたね。 (化物語みたいに)

Tl;Dr

#warning("強制アンラップはやめるべき") // コンパイル時の警告
let message = self.message!

#error("APIのアクセストークンを設定してください") // コンパイルエラー
let API_TOKEN = ""

in Objective-C

Objective-Cでは以下のようなコンパイラディレクティブが利用できました。

#warning 暫定対処、後ほど修正するべき
#error APIトークンを設定する必要あり

それぞれコンパイル時にWarning、Errorとできる仕組みです。

これによって後ほど修正すべき箇所をWarningとして警告しておいたり、ライブラリのテンプレートコード中に埋め込んでおき、実装が必須であることを明示できるというメリットがあります。

これはコメントで代替することも可能ですが、(標準の)コンパイル時において警告またはエラーとして明示することができる点がメリットになります。

in Swift 4.2

Objective-Cでサポートされていたこの機能が、Swift 4.2 でようやくサポートされました。

#warning("暫定対処、後ほど修正するべき")
#error("APIトークンを設定する必要あり")

また、#if のような他のコンパイラディレクティブとも併用して利用することも出来るようです。

#if os(iOS)
#error("macOSはサポート外です")
#endif

上記のように、あるライブラリを作成していたときに特定のプラットフォームだけサポート外としてエラーにしたいということもできるのは便利と言えるでしょう。

まとめ

やれやれ、1記事にするほどの内容でも無かったんじゃないかな、阿良々木くん。

というわけで Swift でもコンパイラディレクティブ(Warning、Error)が使えるようになったという話でした。

また次回?

Swift 4.2 で追加される @dynamicMemberLookup メモ(SE-0195)

全地球100億人のSwifterな皆さん、コンバトラーっ! tobi462 でーっす!

え、いつもとノリが違う?まぁ、そういう日もあるんじゃないでしょうか。

そんなわけで、前回前々回と続き Swift 4.2 の記事です。

今回は、Swift 作者であるラトナーさんの Proposal (SE-0195) のようですよ。

Tl;Dr

@dynamicMemberLookup
struct AnimeCharacter {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "阿良々木月火", "music": "白金ディスコ"]
        return properties[member, default: ""]
    }
}

character.name  // => "阿良々木月火"
character.music // => "白金ディスコ"

dynamic?

Swift では Objective-C と異なり、コンパイル時に多くの静的チェックが行われます。

それはメソッド呼び出しやプロパティアクセスについても同様で、コンパイル時に存在しないシンボルへアクセスするようなコードはコンパイルエラーになります。

struct Person {
}

let alice = Person()
alice.name // コンパイルエラー

Swift などの静的型付けに慣れ親しんだ方はこれが極めて自然な挙動に見えるかもしれません。実際、コンパイル時にチェックされることでタイポなどのミスを事前に検出できるという大きなメリットがあります。

しかし、Swift から Python などの動的型付けを呼び出したい、つまり相互運用性を考えた時はどうでしょうか?

以下は Proposal から抜粋したコードです。

// import pickle
let pickle = Python.get(member: "import")("pickle")

// file = open(filename)
let file = Python.get(member: "open")(filename)

// blob = file.read()
let blob = file.get(member: "read")()

これは Swift 4.1 においても正当なコードですが明らかに冗長に感じます。

すなわち以下のように書けたらスマートではないでしょうか?

// import pickle
let pickle = Python.import("pickle")

// file = open(filename)
let file = Python.open(filename)

// blob = file.read()
let blob = file.read()

というのが、この Proposal の発端のようです。

@dynamicMemberLookup

それを実現するために @dynamicMemberLookup というアノテーションが導入されました。

class や struct 、 enum に付けられるもので、あわせて subscript(dynamicMember :xxx) の実装が必要になります(実装しないとコンパイルエラー)。

@dynamicMemberLookup
struct AnimeCharacter {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "阿良々木月火", "music": "白金ディスコ"]
        return properties[member, default: ""]
    }
}

上記は String を受け取る subscript(dynamicMember) を実装していますが、これは以下のように呼び出すことが出来ます。

let character = Character
character.name  // => "阿良々木月火"
character.music // => "白金ディスコ"

このように事前に定義されていないシンボルを呼び出せているように見えるのが、 dynamicMemberLookup のメリットといえるでしょう。

あまり Swifty に感じない人も多いかもしれませんが、これは以下のコードのシンタックスシュガーと思えば良いでしょう。

let character = Character
character[dynamicMember: "name"]  // => "阿良々木月火"
character[dynamicMember: "music"] // => "白金ディスコ"

こう考えると、Dictionary などへのアクセスと同様に感じられるのではないでしょうか。

メソッド呼び出し風の書き方

さて、メンバーへのアクセス風の構文は分かりましたが、メソッド呼び出し風の書き方をしたい場合はどうなるのでしょうか?

それには以下のようにクロージャを返す subscript(dynamicMember: xxx) を宣言します。

@dynamicMemberLookup
struct Person {
    subscript(dynamicMember member: String) -> (_ input: String) -> Void {
        return {
            print("Hello, \($0)")
        }
    }
}

これは以下のように呼び出せます。

let person = Person()
person.hello("Goodbye") // => "Hello, Goodbye"

少し分かりづらいかもしれませんが、.hello でクロージャを取得し、それに対して (“Goodbye”) で呼び出しを行っている感じです。関数型プログラミングに通じている方は「カリー化」されている関数に対する呼び出しと考えると分かりやすいかもしれません。(これがカリー化という訳ではないのですが)

使いどころ?

さて、ここまで @dynamicMemberLookup について見てきましたが、果たしてどういったところに使えるのでしょうか?

お気づきだと思いますが、この機能が有効に適用できる箇所は限られています。

前述したようにコンパイル時にシンボルがあることを検出できるのが静的型付け言語のメリットです。

例えば、すべてのプロパティ・メソッドアクセスをこの機能で置き換えることは可能ですが、それはもはや動的型付け言語と同様「実行しなければ正しく動くかわからない」状態になるでしょう。

1つの例としては前述したPythonなどの動的型付け言語との相互運用性です。

// import pickle
let pickle = Python.import("pickle")

// file = open(filename)
let file = Python.open(filename)

// blob = file.read()
let blob = file.read()

このようにドット記法で呼び出せるのはとてもスマートだと感じます。

もう1つの例として Proposal では JSON のデータ構造へのアクセスが挙げられています。

@dynamicMemberLookup
enum JSON {
    case intValue(Int)
    case stringValue(String)
    case arrayValue(Array<JSON>)
    case dictionaryValue(Dictionary<String, JSON>)
    
    var stringValue: String? {
        if case .stringValue(let str) = self {
            return str
        }
        return nil
    }
    
    subscript(index: Int) -> JSON? {
        if case .arrayValue(let arr) = self {
            return index < arr.count ? arr[index] : nil
        }
        return nil
    }
    
    subscript(key: String) -> JSON? {
        if case .dictionaryValue(let dict) = self {
            return dict[key]
        }
        return nil
    }
    
    subscript(dynamicMember member: String) -> JSON? {
        if case .dictionaryValue(let dict) = self {
            return dict[member]
        }
        return nil
    }
}

最後の subscriptdynamicMemberLookup になっていますが、これを利用しない場合は以下のようになります。

let json = JSON.stringValue("Example")
json[0]?["name"]?["first"]?.stringValue

これは SwiftyJSON/SwiftyJSON ともよく似た見た目かと思います。

これが以下のように書けるようになります。

json[0]?.name?.first?.stringValue

Subscriptのアクセスに[]が不要となっているので、とてもスッキリした見た目になっています。

ご利用は計画的に

このように事前にすべてを定義できないけれども、 . を使ってアクセスしたほうがスッキリするケースで @dynamicMemberLookup を活用できるかと思います。

逆に言えば、それ以外のケースでは利用は慎重になるべきでしょう。

例えば 事前にすべてを定義できない と聞くと、 UserDefaults のラッパーとして利用することを考えることも出来るかもしれません。

@dynamicMemberLookup
struct UserDefaultsWrapper {
    let userDefaults: UserDefaults
    subscript(dynamicMember member: String) -> String? {
        return userDefaults.string(forKey: member)
    }
}

let wrapper = UserDefaultsWrapper(userDefaults: UserDefaults.standard)
let userName = wrapper.userName

しかし、これは明らかにアンチパターンです。

なぜなら userName とタイプすべきところを userNeme とタイポしてもコンパイラは何の警告も出さないためです。

さらに . アクセスされているので、一見するとこのコードはコンパイル時に静的に解決されているように見えてしまいます。

このように呼び出し時のタイプ量を減らすことを目的に利用するのは明らかに間違いでしょう。

おわり

というわけで、今回は @dynamicMemberLookup の紹介というかメモでした。

実のところ、私は最初にこの機能を知った時に Swift らしくないと感じました。静的にシンボルを解決するというSwiftの思想と相反すると感じた為です。

しかし、「Swifty な呼び出しを実現するためのシンタックスシュガー」と捉え直すと、まぁ使い方さえ間違えなければトレードオフとしてはありかな、という意見に落ち着きました(なんだか上から目線?)。

わたしの知らない Swift な物語はまだまだ続くのでしょう。

それでは。

【iOS】Hyperion-iOSもあるんだよ!

かむいです。今月転職しました。

転職活動も忙しかったりして1ヶ月ぶりの投稿となります。
時間過ぎるのホント早い!

Hyperion-Androidが話題に

先週 Android界隈で話題になったコレ。 こりゃすげぇ!てなりましたよね。

qiita.com

てっきりAndroidだけかと思っててんですけどちゃんとiOSもあったんですよ!

github.com

僕はもうかよちんの「さすがです!」LINEスタンプを開発者に送りつけたい気持ちになったのですが、サンプルコードがあったのでまずは動かしてみたわけです。

そしたら実際に動かしらもっと感動しちゃったので、そのことについて書きます。

導入の仕方

CocoaPodsかCarthegeで導入できます。

詳細はREADME.mdがわかりやすいのでそれ見てもらえれば早いのですが、CocoaPodsで言うと:configurations => ['Debug'] と設定することでデバックビルドでだけこれが見れるよという訳。なるほどね!

実際に動かして見た。いや、動かしてみて

Gifや画像を見て納得して終わらず、是非ともサンプルコードを起動するだけで良いのでご自身で触って見て頂きたいです。 zipでソース一式を落としてくればものの2分もせずに体感出来ます。

凄いことその1 : Zeplinとかで見ながら確認してたアレやコレを実行したアプリの中で見れる!

デザイナーに作ってもらったワイヤーフレームをZeplinなどのツールを使ってUIの情報をアプリに落とし込みますよね。

・テキストの配置位置
・テキストのフォントサイズ
・文字色、ボタン色
・セルのサイズ
etc...

それらを実行したアプリ上で確認できるんです。なんなら画面拡大も出来ちゃう
これマジ

https://camo.githubusercontent.com/ec23fbb49d169c85a5d881e35be0c16902c652bf/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f6c34456f4e4f494c72354f6676677973772f67697068792e676966

https://camo.githubusercontent.com/9641f342cee745d6d05c03cc100841be575738be/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f7854315239486639333833576a75636f6d492f67697068792e676966

凄いことその2: アニメーションを細かくチェックすることが出来る

画面遷移やカスタムアニメーション処理とか、実際に想定した動きになっているか、実は早過ぎてよくわかってない動きも実装の中にはあると思います。 それが0.5倍速とか0.25倍速に設定できて、実際の動きをゆっくり確認出来ちゃいます。
これマジ

https://camo.githubusercontent.com/5d18c81486e741052e51c384495b48ddecbfb8da/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f323646655a634e463944627138394d42692f67697068792e676966

凄いことその3: 皆んなでサードパーティプラグイン作ろ!作成ガイド鋭意製作中!

Calling all developers!!! Be one of the first to create a third-party plugin.   
The plugin creation guide is a work in progress, but if you are feeling ambitious  
 you can reference the plugins we have already created along with our documentation.

開発者の方も書いておりますが、新たなプラグインの製作者を募集しております! まだ機能が少ないのでむしろ俺が望むデバッグ機能はコレや!というのがあれば是非すでに書かれているHyperionCore Referenceを読んで見てはいかがでしょうか。

最後に

何か話題になるOSSがあったとして、それが Android界隈だけで盛り上がっていたらiOS側もあるのではと動いてみること、そういう気持ちでAndroid界隈の動向も追うのは大事だなと今更ながら痛感した次第です(・ω・)

Swift 4.2 では boolean の反転(toggle)ができるようになる(SE-0196)

どうも。ルパン三世 Part 5 の続きが楽しみな tobi462 です。

さて前回のenumの新機能に続き、また Swift 4.2 の話題です。

といっても、今回は驚くほど短いです(汗)

Tl;Dr

// before
messageLabel.isHidden = !messageLabel.isHidden

// after
messageLabel.isHidden.toggle()

toggle()

SE-0196 にて Bool の extension として toggle メソッドが用意されました。

冒頭に書いたとおり、いままで foo.bar = !foo.bar というコードを書かなければならなかったところが foo.bar.toggle() と書けるようになるわけです。

まぁ、自前で実装していた方も多いかもしれませんが。

実装

具体的な実装は以下のようになっているようです。

extension Bool {
  @_inlineable
  /// Toggles the value of the Boolean. 
  ///
  /// Calling this method sets the variable to `true` if it was `false`,
  /// and sets it to `false` if it was `true`. For example:
  ///
  ///    var bools = [true, false]
  ///
  ///    bools[0].toggle()
  ///    // bools now contains [false, false]
  public mutating func toggle() {
    self = !self
  }
}

swift/Bool.swift at swift-4.2-branch · apple/swift · GitHub

まぁ・・・まんまですね(笑)

ちなみに2行目の @_inlineable は初めて知ったのですが、SE-0193 で既に実装されていたようです。

どの Swift バージョンで入ったかはすぐに終えなかったのですが、とりあえず Swift 4.2 では利用できました。

関数のインライン化はパフォーマンス的に有利な面もあると思いますが、多くのケースでは過度な最適化にあたると思うので、普段のiOSアプリ開発では利用しないほうが良いのでしょう。(きっと)

閑話休題。

おわり

foo.bar = !foo.bar はある意味、DRY原則にも反していると言えます。

今後は foo.bar.toggle() というスッキリした書き方ができて良いですね。