読者です 読者をやめる 読者になる 読者になる

近況

雑記

最近大きな環境の変化があったり先日行ってきたYAPCで影響を受けて心境が変わってきたので、近況という形でブログに残しておきたい。

大きな心境の変化として、プライベートの時間の使い方を変えようと思っている。より優先度の高いことに時間を使うようにして、プライベートでプログラミングする時間は以前より少なくなりそう。あれこれ手を出すというよりは1つのことに集中して時間を充てる方が効果的なんじゃないかと思うようになったので、何にしようか考えている。

  • 仕事で使う可能性がありそうなScalaとPlayFrameworkに興味がある。以前にすごいH本を読んで関数型プログラミングを実践してみたい気持ちがある。
  • あとは、いま仕事ではRailsプロジェクトに携わっているけど、リファクタリングを喫緊の課題として感じていて、プライベートの時間でいかに対処すべきか本を読んで考えてみたり、必要なライブラリの開発に時間を充てるのもいいかもしれない。
  • JavaScriptをキャッチアップしたい気持ちもちょっとある。YAPCのセッションでES6の話を聞いて、ちゃんと学んで既存のプロジェクトに手を加えたい気持ちが湧いてきた。ただこれは上のリファクタリングが済んだ先の話だと思った。
  • ドメイン駆動設計」をずっと読んでたけど、引き続き「実践ドメイン駆動設計」も読んでみようかなという選択肢もある。ただ、まだ仕事で使えるような段階にはない気がする。
  • naoty/Timepieceの開発は停滞しているけど、どうしようかなと思っている。仕事でSwiftが書けるのであればまだモチベーションを維持できるのだけど、残念ながらそのような環境にはいない。

こう整理してみると、リファクタリングについてプライベートでも時間をとるのがよさそうだなと思う。それが一段落ついたらJavaScriptリファクタリングとかDDDとかに移っていけそう。もっと余裕が出てきたらScalaやろうかなぁ。

なんとなくあと2, 3年先までの見通しが立ってきた。仕事ではとりあえず今のプロジェクトを地道に改善していくことになりそう。自分の成長というよりはプロダクトの成功やチームの成長にフォーカスしていきたいという気持ちに移ってきている。Rubyの上にも3年、という感じ。iOS/Androidもやるけど。プライベートでも、今の生活の先に明るい未来が見えてきている。堅実に仕事を進めつつ、貴重な時間を大切にしていきたい。

Timepieceを0.4.0にアップデートした

Objective-C/Swift/iOS

Timepieceを0.4.0にアップデートした。ぶっちゃけブログの記事にするくらいならちゃんとCHANGELOGにしろという話なんだけど、技術的な詳細も少し話したいのでブログの記事にした。軽微なバグの修正と以下の2点が今回の変更点だ。

タイムゾーンのサポート

これがメインの変更になる。「サーバー側から受け取る時間のタイムゾーンiOSアプリケーションのタイムゾーンが異なっており、それらを比較したい」みたいなissueをもらったので対応した。「こういう感じのインターフェイスはどう?」みたいなのを聞いてたら「Sweet!」だのと褒められたので、その気になって実装してしまった。けっこう大変だった。

iOSでは、NSDateオブジェクトそのものにタイムゾーンは存在しない。システムで設定されるタイムゾーンNSCalendar経由で取得することになる。

NSCalendar.currentCalendar().timeZone

なので、今回のようなケースだと、NSDateオブジェクトそれぞれにタイムゾーンが存在するように見せる必要がありそうだった。なお、オフセットを調整することも考えられるが、時間を足し引きしてしまった段階でそれは別の時間となってしまう。同じ時間で別のタイムゾーンを持つNSDateオブジェクトが必要だった。実装方針としては、

ということを念頭に置いた。

ところで、TimepieceはNSDateを始めとするいくつかの既存のオブジェクトのextensionとして実装されている。extensionでNSDateオブジェクトにtimeZoneのようなプロパティを追加することは普通はできない。しかし、Objective-CのランタイムAPIを使って動的にプロパティを追加することで、これをなんとか実現させることができる。Swiftの場合でも、ランタイムAPIを使うことは可能だ。

import ObjectiveC

public extension NSDate {
    var timeZone {
        return objc_getAssociatedObject(self, &AssociatedKeys.TimeZone) as? NSTimeZone ?? calendar.timeZone
    }

    func change(#timeZone: NSTimeZone) -> NSDate! {
        // ...

        objc_setAssociatedObject(newDate, &AssociatedKeys.TimeZone, timeZone, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))

        // ...
    }
}

objc_から始まる関数がランタイムAPIだ。これらの関数によって動的にオブジェクトにプロパティを読み書きしている。change(timeZone:)で生成されたNSDateオブジェクトのみ動的に追加されたプロパティを持つ。タイムゾーンが異なるNSDate同士の計算も問題なく行われている。これは上述の通りオフセットの調整による実装ではないためだと思う。

playgroundの追加

機能というわけじゃないけど、リポジトリにplaygroundを追加した。それに伴ってプロジェクトからワークスペースに変更した。実際にTimepieceを試してもらうには、サンプルのアプリケーションを作ってPodfileを書いて…みたいなことをする必要がありとても面倒だったので、Timepieceが使えるplaygroundを用意した。

Pull request大歓迎

タイムゾーン周りの実装は正直あんまり自信はないんだけど、テストはちゃんと通ってるしまぁいいかくらいの気持ちでリリースした。観点が漏れている可能性は大いにありうるので、ぜひPull requestしてもらいたい。

コマンドラインを拡張しやすくするヤツ書いた

Go

gitなど既存のコマンドラインを拡張して新しいサブコマンドを追加する方法はいくつか考えられる。

git alias

gitの場合はgit aliasを使うことで簡単にサブコマンドを追加できる。gitのとき限定。

ラッパー

github/hubのような既存のコマンドラインをラップしたスクリプトを書き、alias hub=gitのようにaliasすることで既存の機能を保ちつつ機能を追加できる。

問題点としては、複数のラッパーによる拡張が難しくなる。例えば、ここでbubというgitのラッパーを書いたとする。githubの機能とbubの機能を拡張したい。hubは入力されたサブコマンドがhubになければgitにフォワードしている。なので、hubbubを同時に拡張するにはbubhubのラッパーとして実装することになってしまう。依存関係をハードコーディングすることになるため、まったくスケーラブルじゃない。

命名規則とext

command subcommandと入力されたらcommand-subcommandを実行するように名前解決する仕組みがよさそうだと思う。例えば、git prというコマンドはまずgit-prを探し、あれば実行し、なければgit prを実行する(そしてエラーになる)。gem uninstall allというコマンドはgem-uninstall-allgem-uninstall allgem uninstall allの順に探索されて見つかり次第実行される。

このような命名規則を基に名前解決するツールを書いた。

$ go get github.com/naoty/ext
$ go get github.com/naoty/gem-uninstall-all
$ alias gem="ext gem"
$ gem uninstall all # Run gem-uninstall-all

正直、いろんな問題がありえそうだが、昨日思いついたままに書いたものなので、まだ想定できてない。gem-uninstall-railsというコマンドがあったらrailsをアンインストールできないとかありそう。

上の例で、hubbubを同時に拡張したい場合にextを使うと以下のようにできる。

$ go get github.com/naoty/hub-bub
$ alias git="ext hub"
$ git bub # Run `hub-bub`

残念ながら、hubを使いたい場合はこうするしかないような気がする。


追記(2015-07-23)

gitには、git subcommandgit-<subcommand>として名前解決して実行する機能があったことをさっき知った。なので、gitに限って言えばextのようなツールは不要だと思う。

$ cd $HOME/bin
$ vi git-hello
#!/bin/sh

echo "Hello, world!"
$ chmod +x git-hello
$ git hello
Hello, world!

Pocketのもう読んでない記事を掃除するヤツ書いた

Go

普段、Pocketを使って「あとで読む」記事を管理している。ちょっとした時間に見つけた記事をPocketに追加しておいて、通勤時間などにiOSアプリで消化している。ただ、長い記事が増えてくるとだんだん消化しきれなくなってきて、消化しようというモチベーションが失せてくる。そこで、一定期間が経っても消化できていない記事を自動的に削除するツールを書いた。

使い方

  1. まず、Pocketのdeveloperサイトに行ってアプリケーションを作成する。すると、Consumer keyが得られる。次に、Access tokenが必要なのだけど、これはmotemen/go-pocketを使ってOAuth認証を行い取得した。

  2. Herokuボタンからデプロイする。ここで、上で取得したconsumer keyとaccess tokenを環境変数としてセットする。さらに、削除対象とする期限を環境変数で指定できる。デフォルトは24時間となっている。僕は72時間にしている。

  3. Heroku schedulerのダッシュボードでsweepを実行するタイミングを設定する。

Deploy

所感

先日、Herokuが公式にGoをサポートしたので、さっそくテストを兼ねてこういうものをGoで書いてみた。tools/godepの使い方を覚えなくてはいけないことを除けば、いつもどおりにHerokuにアプリケーションをデプロイできた。

個人的にちょっとしたCLIツールをGoで書くことが増えたが、ちょっとしたジョブを定期実行させるときにGoでちょっとしたツールを書いてHeroku schedulerにやらせるという手法は非常にお手軽なので今後も機会がありそうだなと思った。

コミット数が多いファイルを表示するコマンドを書いた

Go

インストール

$ go get github.com/naoty/hot

使い方

$ cd src/github.com/naoty/Timepiece
$ hot
24: README.md
17: Sources/NSDate+Timepiece.swift
15: Tests/NSDate+TimepieceTests.swift
10: Timepiece.xcodeproj/project.pbxproj
9: Timepiece.podspec
7: Sources/Duration.swift
7: Tests/DurationTests.swift
7: Tests/Int+TimepieceTests.swift
6: Sources/Int+Timepiece.swift
4: Sources/NSDateComponents+Timepiece.swift
2: .travis.yml
2: Tests/NSTimeInterval+TimepieceTests.swift
2: Timepiece.xcodeproj/xcshareddata/xcschemes/Timepiece OSX.xcscheme
2: Sources/NSTimeInterval+Timepiece.swift
1: Timepiece.xcodeproj/xcshareddata/xcschemes/Timepiece iOS.xcscheme
1: Timepiece.xcworkspace/contents.xcworkspacedata
1: .gitignore
1: LICENSE
1: Sources/NSCalendar+Timepiece.swift
1: Sources/NSCalendarUnit+Timepiece.swift
1: Sources/String+Timepiece.swift
1: Tests/NSCalendarUnit+TimepieceTests.swift
1: Tests/String+TimepieceTests.swift
1: Timepiece.playground/Contents.swift
1: Timepiece.playground/Sources/SupportCode.swift
1: Timepiece.playground/contents.xcplayground
1: Timepiece.playground/playground.xcworkspace/contents.xcworkspacedata
1: Timepiece.xcodeproj/Timepiece-Info.plist
1: Timepiece.xcodeproj/TimepieceTests-Info.plist
1: Timepiece.xcodeproj/project.xcworkspace/contents.xcworkspacedata

表示件数を-n <表示したい件数>で指定したり、パターンを指定してマッチしたファイルだけ表示することができる。

$ hot -n 5 "**/*.swift"
17: Sources/NSDate+Timepiece.swift
15: Tests/NSDate+TimepieceTests.swift
7: Sources/Duration.swift
7: Tests/DurationTests.swift
7: Tests/Int+TimepieceTests.swift

動機

仕事で1年以上開発が行われているコードベースを引き継ぐことになった。僕の仕事は既存のコードを理解しつつ、新たに機能を追加していくことだ。そこで、効率的に既存のコードベースの全体像を把握するため、このようなツールを作ることにした。どれが主要なファイルなのかコミットログから把握できる。

他の使い方としては、例えば各ファイルのコミット数、循環的複雑度、テストカバレッジ等から、プロジェクト全体のバグの出やすさみたいなものを可視化できるかもしれない。

「すごいHaskell たのしく学ぼう!」を読んだ

書評 Objective-C/Swift/iOS

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

本書は一度は8章あたりで挫折したが、今回13章あたりまで読みファンクタ―、アプリカティブファンクタ―、モノイド、モナドといった概念がなんなのか理解とまでは言えないけど知ることができた。

一度は挫折したが今回またリベンジしようと思った理由は、今後モバイルアプリを開発していくにあたって関数型プログラミングの概念を理解して採り入れていくことが必要になってくると思ったからだ。Swiftletによる不変型の宣言やOptional型などの文脈付きの型など関数型プログラミング言語としての側面をもっていると思う。また、データバインディングSwiftBond/Bondなど)やJSONのパース(thoughtbot/Argoなど)といった場面で関数型プログラミングの概念が登場してきている。Swiftのポテンシャルを最大限に発揮して、堅牢で生産性の高いコードを書くには関数型プログラミングの知識が必要になってきていると最近感じている。

本書を読んだ結果として、データの構造について新しい視点を得ることができた。MaybeEitherといった概念を"文脈"と呼んでいるのが自分の中にはなかった発想だった。例えば、MaybeMaybe Intを区別して考えるのはとても抽象的だけど強力な考え方と思った。Maybeは「あるかもしれないし、ないかもしれない」という文脈を表し、Maybe Intは「Int型かもしれないし、何もないかもしれない」型を表している。これらを分けることで、文脈を保ったまま計算するという発想が出てくるのだと思う。文脈を保ったまま計算する段階として、本書ではFunctorApplicative、そしてMonadが登場してきた。

Swiftでは、Haskellにおける型コンストラクタにあたる概念がない。Genericsを使うことでMaybeのような型を表現することはできるが、ある型が型引数をとるのかとらないのか、とるとしたらいくつとるのかを知る術はない(はず)。Haskellではそれらは種類という概念で説明されている。Maybeの種類はMaybe :: * -> *だし、Eitherの種類はEither: * -> * -> *となっているので、それぞれ型引数を1つと2つとることがわかる。HaskellFunctorは種類が* -> *の型コンストラクタしかインスタンスにできないのだけど、こういう概念をSwiftで表現できない。

というわけで、Swift関数型プログラミングをするにはHaskellほどうまくはできないことがなんとなくわかった。Genericsなどで擬似的に表現するしかない。Functorfmapを以下のように実装してみた。

extension Optional {
    func fmap<U>(f: T -> U) -> U? {
        switch self {
        case .Some(let value):
            return f(value)
        case .None:
            return .None
        }
    }
}

let maybeOne: Int? = 1
let maybeTen = maybeOne.fmap({ x in x * 10 })

SwiftOptional<T>型はつまりT?型のことなのだけど、Optional型を拡張してfmapを追加している。return f(value)のところは暗黙的にU?型にラップしている。このように実装することで、Optional型のもつ「あるかもしれないし、ないかもしれない」という文脈を保ちつつ、中身の1というIntを計算している。

ここではFunctorだけを簡単に実装してみたが、これに加えてApplicativeMonadを実装するとより抽象的な計算が可能になってくる。JSONのパースなどを実装する際にはApplicativeの操作が必要になってきそうな感じがする。自分はまだ関数型プログラミングの実装を実際にしたわけではないので、理解したとは到底いえない。パーサーの実装をしてみたり、上で紹介したライブラリのコードを読んでみることで関数型プログラミングを実践的に理解していきたい。

naoty/todoとnaoty/nowisで定期的なtodoを管理できるようにした

Go

前回のエントリで紹介したnaoty/todoと今回作ったnaoty/nowisを組み合わせることで、定期的なtodoをコマンドラインで管理できるようにした。

使い方

$ nowis saturday && echo 'Today is Saturday!'
Today is Saturday!

nowisコマンドは、現在時刻が引数で与えた曜日かどうかを判定して真なら終了コード0を返し偽なら1を返す。上のように&&で任意のコマンドと組み合わせることで、特定の曜日だけ実行できるようになる。

定期的なtodoの管理

nowisを組み合わせて定期的なtodoを管理するにはいくつか方法が考えられるが、zshの設定ファイルを使う。

# .zlogin

nowis sunday && todo add --once 部屋を掃除する
(nowis tuesday || nowis thursday) && todo add --once 燃えるゴミを出す

上のように設定することでzshにログインするたびに上のスクリプトが実行される。todo add --onceで既に存在する場合は追加しないようにできるので、これで特定の曜日になると自動的にtodo addされるようになる。

15分くらいで作ったので現状は曜日の判定しかできないけど、応用範囲が広そうなのでもうちょっと細かく判定できるようにするかも。