Galapagos Tech Blog

株式会社ガラパゴスのメンバーによる技術ブログです。

SwiftでもElmのようなパーサーコンビネータを利用できるようにしてみました。

こんにちは、iOS開発チームの本柳です。

コードのモジュール化について色々考えながら開発していたのですが、 あれこれ考えているうちに勢い余ってgalapagos/HighOrderHelperというライブラリを作ってしまいました。

galapagos/HighOrderHelper ?

このライブラリはElmがコア機能として提供しているHigh-Order Helpersという機能をSwiftに移植したものです*1

galapagos/HighOrderHelperはパイプラインオペレーターや関数合成オペレーター、他に関数を変形するための関数などを含む関数プログラミングを行うための手助けとなるような機能を含んでいます。

このライブラリの機能の一部を紹介しましょう。

パイプラインオペレーター

F#から生まれ、OCamlOSS界に降臨し*2、Elixirに採用されたことでWEB界隈に認知され、その後WEBの力で急速に開発者に浸透していったオペレーター|>です。

Elmでは。Forward apply(|>)、Backward apply(<|)という二種類のパイプラインオペレータがありますが、galapagos/HighOrderHelperを利用すると、これらをSwiftで利用出来るようになります。

このパイプラインオペレーターを利用すると、下記のようにコードを書くことが出来るようになります。

// パイプラインオペレータを使う雰囲気だけご覧頂くためのコード(実際にこのコードが動作するわけではない)
let entity = JSONString
                 |> JSON.parse
                 |> Entity.map

このオペレーターを利用するためには参照透過な関数を用意する必要がありますので、積極的に利用していく結果的に副作用のある処理が少なくなっていくのが嬉しいですよね。

ヘルパ関数

galapagos/HighOrderHelperは提供するオペレーターを利用しやすくするために幾つかのヘルパ関数を用意しています。

ここではflip関数をご紹介します。

以下のような関数があるとします。

func A() -> String {
    return "a"
}

func B(_ a: Int, _ b: String) -> String {
    return "\(a)-\(b)"
}

この時、A() |> (B, 1)のようなコードを書きたいのですが、型が合わないために書くことが出来ません。

flip関数は関数の第一引数と第二引数を入れ替えた関数を返す関数です。

この関数を利用するとA() |> (flip(B), 1)というように書くことが出来るようになります。

このような感じで、ヘルパ関数を利用すると様々なケースにgalapagos/HighOrderHelperは提供するオペレーターを利用出来るようになります*3


galapagos/HighOrderHelperの機能をちょろっとご紹介しました。

詳細はGithubに記載してあるので、興味のある方は是非御覧ください。

さて、ガラパゴスでは勢い余ってライブラリを書いてしまうようなアクティブなエンジニアを募集しております。

ご興味お持ちの方は是非、弊社の採用ページを御覧ください。

www.glpgs.com

たくさんのご応募お待ちしております。


*1:この手のライブラリは何番煎じなんだ?という気もするのですが、Elm Likeという点においてはこいつが初版のはず

*2:実のところOCamlOSS界隈に降臨したかどうかは知りませんので、詳しい方は教えてください

*3:これが、パズルを解いているような感じで楽しいのです!

Cookpad Tech Kitchen #7 参加レポート〜ごちそうさまでした〜

こんにちは!テストチームとのの(@tono2587)です。
今回はクックパッドでおいしいごはんをごちそうになった話を書きます!!!

f:id:glpgsinc:20170424173845p:plain 嘘ですごちそうになりながらテスト現場のお話聞いた参加レポートです(おいしかったのは本当です)(めっちゃおいしかったです)

Cookpad Tech Kitchen #7 〜 理想の開発現場の「ふつう」のお話 〜

日時:2017/04/21 Fri. 19:30〜
場所:クックパッド株式会社 東京都渋谷区恵比寿4-20-3 恵比寿ガーデンプレイスタワー12F 登壇者:秋山 浩一さん、関 将俊さん、深谷 美和さん、松尾 和昭さん

cookpad.connpass.com

公開された資料

どんなお話が聞けたか

  • 「理想の開発現場」について
  • 理想の開発現場の「ふつう」について
      「うまくいったらどうなるの?」
      「なんでやるんだっけ?」
      「やりたくないの?」
      「えー」
  • そのチームで喜ばれるふるまいについて
  • 明日からできそう??

「理想の開発現場」について

タイトルの「理想の開発現場」とはどういうこと? という説明を秋山さんより

  • いままでは、テストと開発それぞれが独立しているほうが理想の形だと思っていた。
     ・技術、お金、管理それぞれが独立(IV&V)
  • しかし、2015年のみわさんの発表を聞いてもう一つの理想の形に気がついた
     ・「人はミスをする」ことが受け入れられていて、テストエンジニアと開発者が一緒に品質をよくしている形
     ・ダイバーシティインクルージョン…多様に集まって、お互いに認め合うこと
  • ソフトウェアの世界では、このようにひとつの世界で情報交換したほうがよいのではないか
     ◯よいところ
     ・何を言っても大丈夫なこと…認めあっている
     ・「人」ではなく「こと」に焦点が当たっていること…品質をよくするために人が動く
     ・見方が違うことが大事
     ・共同作業と当事者意識…一緒にひとつになってやっている

解説にはカンペがあったそうです笑

今回はそんな理想の形=みわさんのチームについてのお話でした。

これから話す内容について関さんより

  • これまでは、できあがったチームの事例を報告していた
     →それを聞いたからといってできるとは限らない!
  • 今回は、チームになっていく過程を報告します
     →問題解決の最中に起こる話からよいチームができていっているのではないか?
     フレーズ→ふるまい→価値観 のようにチームの価値観ができていくのでは。
  • こんな名言があります
    『生きている花をつくろうとすれば、ピンセットで細胞を一つ一つ物理的に組み立てるのではなく、種から育てるであろう。』
         ーーークリストファー・アレクサンダー『時を越えた建設の道』

(種の撒きかたを教えてくれるんですね?!)

理想の開発現場の「ふつう」について

よくつかわれているフレーズがあります。

「うまくいったらどうなるの?」

  • このフレーズのよいところ
     ゴールがわかる、確かめる方法がわかる、ゴールまでのステップがわかる、うまく迷える
    →これをきっかけにしてぼんやりしていることをはっきりさせられる

  • 例:◯◯の調査をします と言われたとき
    ほんとうにやりたいことのための調査なので、そのやることがみんなにわかるようになったらやめようか、とか決めることができる
    Q:調査の区切りは先行投資だと難しいのでは?
    →時間で切る ことが多い

「なんでやるんだっけ?」

  • このフレーズのよいところ
     ・ゴールの意味がわかる、やりすぎを抑えられる
    →その作業自体の意味を考えたり、その作業でつくろうとしてるものがわかってくる。「不足」は不具合とかで見つけやすいが、やりすぎはわかりづらいのでこのフレーズが役に立つ
    →不安に思っていることはバラすほうがよい。
    →1人よりn人で考えたほうがみつけやすい

  • 例:だれかが迷っているとき、話し合いが急に静かになったとき、結論が出ないとき、困っているように見えるとき、熱中し(すぎ)ているとき

Q:Cookpadではどうですか?
人に干渉するのはけっこうやります。進捗の確認とか
Slackとか直接でちょくちょくコミュニケーションとる
できるならやったほうがいい

「やりたくないの?」

  • このフレーズのよいところ
     ・1人だけがやりたくない、状態にならなくできる(ヤダ共有)
     ・やる気がないからやらない、とはならなくできる
     ・上手い人がかわりにやってくれたりする
    →「嫌なこと」をなくしていくことができる。モチベートするのではなく、嫌なことを認めてなくしたり、みんなのものにすることができる

  • 例:問題を前に言葉が出ないとき、言い訳をする人が出てきたとき

Q:微妙なところに気づいてあげる工夫は?その人が「やりたくなさそう」という判断基準は?
→顔をみるから気づく。その人の話を聞いていてゴールが明確じゃないときや、明らかに目をそらすとかのとき

「えー」

  • このフレーズのよいところ
     ・瞬時に違和感を伝えることができる
    →「えー」って言うと課題かも、って気付ける
     ・それが何かをみんなが一斉に考えて話し始める
    →たいていの場合課題(問題)がみつかる

  • 例:「あれ?」とかも同じ使い方。ちょっと待って、というかわりだったりする
    →理不尽なこと言われたときには有効

◆スビードを大切にしているチームだからつかいます
 ・「早く」みんなに伝えたい
 ・自分で納得できるまで確かめてから伝えたい

Q:途中から入ったひとにも「えー」が悪く伝わらないコツは?(「えー」って普通否定的なイメージ)
→次の言葉までセットにしてる 「えー◯◯?」
Q:新しい人が「えー」の意味をわかるきっかけは?
→みんなのいる場で言われる/使われることが多いから慣れていく
→自分のことと同じようにその場にいる人が考え始める

そのチームで喜ばれるふるまいについて

  • 変だな?と思ったときにすぐ誰かにいう
  • バグかな?と思ったときにすぐ誰かに言う
     →スピード重視だから。バグじゃなくても怒られたりしない(いままではバグかな?くらいでは報告しなかったし、違うかもしれないことに開発者の工数をとることはよくないことだと思っていたけどいまはそうではない)
  • 仕様がわからないときだれかにきく
     →誰かが知ってるのに、わからない人が調べている時間のほうがもったいない
  • テストのやり方がわからないときだれかにきく
  • テストを思いついたとき、やる前にだれかにいう
     →ここバグ出そうだな〜って思ったときにすぐ言っちゃう
     →実装する前でも言う。考慮できてなかったところに気づいたりする
     →知っているなら先に言いなさいよ、というスタンス。いいものをつくりたいはずなのに、自分のやりたいことが優先されるべきではない。バグでそう→実装で確認は自分がやりたいことじゃないだろうか。
     「わざわざできあがってから言うのは、きみは意地悪だ」
  • こうしたフレーズを繰り返し言っていき、だんだんチームの価値観をつくっていく

明日からできそう?

15年も一緒にやっている長いチームだから、チームとしてできあがっているからこそ成り立つものなのでは?
と思われるかもしれないが、このチームに限った例ではないよ
→ラムダノートという会社と1ヶ月くらい一緒に仕事したらそっくりなチームができたから

まとめ

・テストエンジニアと開発者が一緒につくっていくのもチームの理想の形
・理想の開発現場ではなにが普通なのか
 ・よくつかわれているフレーズがあり、価値観をつくっていってると思う
 ・みんなが当事者
 ・新しく入ったメンバーにも繰り返し言っていき、慣れてもらう

Twitter公式タグも盛り上がってました!

感想

話を聞いて、わたしがすぐできそうなことについて考えました。

  • モヤモヤはすぐに伝える
     なんでも全部すぐ言うのがよいとはまだ思いませんが、悩みすぎたり、考えすぎて止まらないようにしようと思いました。まずは時間で区切ってみます。
  • 朝会をちょっと変えてみる
     いままで:進捗をざっくり確認→今日はAさんここ、Bさんこっちからお願いします。→他は何か(共有事項)ありますか?(終)
     これから:最後の(共有事項)の聞き方を具体的にしてみる。不具合でたかとか、仕様不明点ないかとか…
     →心当たりを見つけやすいし、細かいことも共有しやすくなりそう。
     →逆に無駄話みたいになっちゃうこともあるので、ほどほどでよいとアドバイスもらいました。
  • 「よいものをつくるため」を忘れない
     喜ぶのが自分ひとりだけ、にならないこと。
     →ちょっと面倒だな…ということも、「これを使う何万もの人がこっちのほうがうれしい」と言われたらがんばれる

ほか
ごはん全部おいしかったです。豚肉の角煮みたいなやつとか、生姜焼きとか、炊き込みご飯とか…でも食べながら聞くのは上手くできなかったです…もっと食べたかった…笑
ごちそうさまでした…
…というわけで、おいしいごはんとおいしいお話をいただいた1日でした。今よりもっとよくなるためにすぐできそうなことも見つかったので、早速やってみようと思います。

くいしんぼうとののが明日からもがんばる会社はこちら〜(エンジニア募集中)

www.glpgs.com

Swift's Strings

ガラパゴスのコードヒーヨアン(twitter: @luinily)です。

社内iOS勉強会の発表準備のため、Swift 4でStringの変更の説明を調べているうちに、そもそも現在のSwiftのStringはどうなっているのか、正確に把握していなかったことに気づいて、発表内容を現在のSwiftのStringの説明の方に変えました。

今回はその発表の内容を説明しようと思います。

Swiftの文字列はUnicodeの考え方に従っていますので、Swiftの文字列を理解するには、Unicodeを理解せねばなりません。

Unicode

アメリカで最初文字をパソコンで処理をしようとしたときは、まだメモリーも少なかったし、英語の使う文字だけで十分だったので1文字は1バイトで表現していました。そのあと、表現したい文字数が増えたり、国際化が進んで、各言語の文字を扱うためのエンコードができたりしていました。大学生の時、フランス語のWindows XPで日本語表示のインストールなどをした覚えがあります。ネットになると、複数のエンコードがありうるので、サイトによって全ての文字が化けて、表示に使うエンコードを変えたりしていました。

こういった問題を解決するため、すべての文字を表現できるエンコードUnicode、を作成することになりました。なぜUnicodeができるまでの歴史を簡単に説明したかというと、その歴史がUnicodeに影響を与えているからです。

Unicodeはどういうふうに文字をエンコードしているのか

Unicodeの文字はビットから文字まで複数の様子で定義されています:

  • Character

  • Unicode Scalar

  • Code Unit

一つの文字は一つか複数のUnicode Scalarで定義されています。一つのUnicode Scalarは一つか複数のCode Unitで定義されています。

f:id:glpgsinc:20170417115610p:plain

Code Unit

Code Unitはファイルなどに文字列を書き出すときに書かれる値です。複数のサイズが可能:8ビット(UTF-8)、16ビット(UTF-16)、32ビット(UTF-32)など。 複数のサイズが存在するのは、いろんな構築をしたシステムに対応できるためのと、英文など最初の8ビットで表現できていた文字列を使うと、1個の8ビットのCode Unitでかける文字がほとんど、メモリーの節約にもなります。

Unicode Scalar

Unicode Scalarは「文字」と「文字の変化様子」の2種類に分けられて、16進で表現される番号をふられている。例えば、日本語だと「す」という文字のUnicode Scalarは「3059」、「゙」でのUnicode Scalarは「3099」。二つ合わせれば、「ず」ができます。 つのUnicode ScalarをCode Unitで出力する場合、UTF-8では文字によって1つから4つのCode Unitが必要になります。

文字

一つの文字は一つか複数のUnicode Scalarの組み合わせでできています。Unicode Scalarの数には特に上限がありません。先の例で「す」と「゙」を組み合わせると「ず」になります。

同じ文字で複数の書き方が可能

一つの文字をUnicodeエンコードする場合、複数のやり方が存在することがあります。 例えば、さっきの「ず」は二つの Unicode Scalarを使ってできましたが、「ず」という文字にも一つのUnicode Scalarが定義されていますので、 Unicodeでは二つのUnicode Scalarの組み合わせで書けます:

  • 305A

  • 3059 + 3099

Swift

SwistのStringはこの考え方を反映しています。 Stringのプロパティーを見ると四つのビューが存在しています: String.characters: CharacterView String.unicodeScalars: UnicodeScalarView String.utf16: UTF16View String.utf8: UTF8View

見覚えありますよね?Unicodeの文字の定義を見たときにでた様子です!

  • CharacterViewはUnicodeの文字のコレクション

  • unicodeScalarsはUnicode Scalarのコレクション

  • utf16とutf8はCode Unitの16ビットのコレクションと8ビットのコレクション

Unicode Scalarの入出力

Stringの文字をUnicode Scalarから定義したいときは文字列の中に「\u{番号}」というふうに書きます。 例として「Pokémon」の「é」を使いましょう。「é」は「ず」と同じく、Unicode Scalarで二つの定義方法があります:「00E9」 (é)と「0065+0301」(e + ́)。Swiftで書くと下記のようになります。

f:id:glpgsinc:20170418190846p:plain

文字列から、Unicode Scalarを出力したいときは、String.unicodeScalarsにあるアイテムのvalueを取ります:

f:id:glpgsinc:20170418192346p:plain

char.valueをそのまま使うと、10進で表示されますので、16進でStringとして表します。

UTF-8UTF-16の出力

UTF-8UTF-16の出力はString.utf8、String.utf16プロパティーを使います。

f:id:glpgsinc:20170418193339p:plain

f:id:glpgsinc:20170418193557p:plain

お分かりになったと思いますが、8ビットで表現できるUnicode ScalarはUTF-8UTF-16で一つのCode Unitになります、16ビット表現されるUnicode ScalarはUTF-8で二つのCode Unitになり、UTF-16で一つのCode Unitになります。

従来のStringとの違い

Stringという型は、従来の言語でただの文字の配列です。Androidでよく使われているJavaのStringと比べて見ましょう。

JavaのStringはどういうものかというとStringのUTF-16のCode Unitの配列になります。 SwiftとAndroidのStringの文字数を比べて見ましょう。 例として、先ほどのPokémonを使います。まずは「é」が一つのUnicode Scalarで定義されている場合:

Swift:

f:id:glpgsinc:20170421190308p:plain

Java:

f:id:glpgsinc:20170421190727p:plain

この場合は同じですね。面白くなるのは「é」を二つのUnicode Scalarで定義した時:

Swift:

f:id:glpgsinc:20170421191856p:plain

Java:

f:id:glpgsinc:20170421191917p:plain

Swiftは7つのままですが、Javaの方は8つになりました!

今回Unicode Scalarが8つあって、Javaのcountが8つになりました。JavaUnicode Scalarの配列なのか、確認したいですね。そのために32ビットのUnicode Scalarを使って見ます。

どういうことかというと、JavaのStringは文字の配列ではなく、Unicode Scalarの配列ですので、今回は「e」と「 ́」が一つずつ数えられる。Swiftの方はUnicode Scalarではなく、文字を数えてるので、「é」の両方の定義でも数が変わりません。 SwiftでUnicode Scalarを数えたいときは、unicodeSclarsを使います:

f:id:glpgsinc:20170421201415p:plain

SwiftでUnicode Scalarが一つなのに、utf16の方で二つになります。Javaの方はどう?

f:id:glpgsinc:20170421201450p:plain

Javaも2になりますが、もうちょっと詳しく見ると「\u10000」の最後の「0」の色が違う、Unicode Scalarのコードの一部として認識されるのではなく、「0」という文字として認識されて、「𐀀」が「က0」として認識されてしまった。

結論として、SwiftのStringはUnicodeの文字の配列で、JavaのStringはStringのUTF-16のCode Unitの配列。 ただ、SwiftのStringは本当に配列なのか?配列だったら、Intをインデックスとして、文字にアクセスできますね?テストして見ると、コンパイルが通らない、SwiftのStringはIntをインデックスとして使える配列ではない。

なぜそうなったかというと、もともと配列は中に入れるアイテムのサイズが全部同じという前提で定義されていて、Intを使ってアクセスすとき、メモリーアドレスは単純に「配列のアドレス+インデックスxエレメントのサイズ」で計算されています。ただし、SwiftのStringが使っているUnicodeの文字のサイズはバラバラです。Unicode Scalarによってサイズも違いますし、複数のUnicode Scalarを使った文字もあります。Stringのいくつ目の文字のアドレスを計算するには、単純な掛け算ではなく、一文字目から一個ずと文字のサイズを足して計算します。

その原因でSwiftのStringはただの配列ではないし、Intのインデックスから文字の場所の計算のコストが高いので、Collection扱いもSwift 2でなくなりました。

Pokémonの場合、「é」以外の文字は1バイトですが、「é」は書き方のよって2バイトか3バイトが必要です。 f:id:glpgsinc:20170424180202p:plain

インデックスの扱い

SwiftでStringの文字にアクセスするには他の言語と違ってIntのインデックスが使えないことがわかりましたので、Swiftでどういうインデックスが使えるか紹介します。

Swiftではビューによって専用のインデックスタイプが用意されています:

  • String.characters: String.CharacterView.Index

  • String.unicodeScalars: String.UnicodeScalarView.Index

  • String.utf16: String.Utf16View.Index

  • String.utf8: String.Utf8View.Index

各インデックスの使い方は同じ:

  • startIndex:一つ目の文字のインデックス 例:myString.characters.startIndex

  • endIndex:最後の文字のインデックス+1 例:myString.unicodeScalars.endIndex

  • index(i: Index, offsetBy: Int):一つのインデックスから別のインデックスの計算。 例:myString.utf16.index(myString.utf16.startIndex, offsetBy: 2)

  • index(after: Index):次のインデックス

  • index(of: T):文字、UnicodeScalarなどのインデックス

ビューの間のインデックスの変換が可能です:String.CharacterView.Index(String.UnicodeScalarView.Index, in: String)、String.Utf16View.Index(String.Utf8View.Index, in: String)

Stringに直接インデックスも使えますが、String.CharacterViewのインデックスです。

StringのビューはCollectionになっているため、ループができますが、パーフォマンスの問題がそのまま残っているため、使うときに注意したほうがいいです。

サブストリング

Stringからサブストリングを取るときにSwiftは単純にその文字をメモリーの別のところにコピーしているわけではなく、新しいStringは元のStringへのポインターとサブストリングのインデックスを持っています。そうすることによって、サブストリングで処理を行うときに、Stringはコピーされないので、処理が速い。ただし、サブストリングが残ってる限り、元のStringを消しても、メモリーに残ってしまいます。長い文字列から一つの単語をサブストリングにしてから、元の長いStringを消してもメモリー上、全部残ってしまいます。Swiftのサブストリングの処理はメモリーより速さを優先している。

SwiftのStringの不思議なところ

文字列がUnicode Scalarの変化様子字で始めれば、たのStringと合わせる場合、文字がまーじされることがあります。

f:id:glpgsinc:20170424192657p:plain

絵文字の場合、想定外の文字数になることがあります:

複数の国旗絵文字を並べても文字数が1になります

f:id:glpgsinc:20170424193538p:plain

四人の絵文字の文字数は・・4

f:id:glpgsinc:20170424193609p:plain

これでSwiftのStringはちょっとわかりやすくなりましたかな?中を見てみると意外と面白いですね。

SwiftのStringについて調べたときに役に立った記事: