2014年7月30日水曜日

[Swift] カリー化関数の話(Curried Functions)

最近目にすることの多かったカリー化関数(Curried Functions)について。

カリー化(currying)とは、複数の引数を取る関数について、最終的に引数を一つにするよう部分適用を行うこと。カリー化した関数は元の関数の(多くの場合)最初の引数だけを取り、戻り値は残りの引数を取って結果を返す関数、となるようだ。

言葉の定義はともかくカリー化できると、引数の値は動的だがある文脈では固定であって欲しい、という場合に便利になる。

最初の例としてわかりやすかった ijoshsmith.com の例。を少し改変してみる。
例えば、元の関数が以下のようなケース。
func appendSeparatorToStrings(strings: [String], separator: String) -> String {
    return separator.join(strings)
}

appendSeparatorToStrings([ "A", "B", "C" ], "-") // "A-B-C"
appendSeparatorToStrings([ "D", "E", "F" ], " ") // "D E F"
上記は String 配列を区切り文字で結合した String を返す関数だが、あるシーンで「改行で結合する関数」が必要になった、という想定でカリー化を考える。

カリー化は、戻り値が「結果を返す関数」になるので、シンプルに考えた場合は「ネスト関数(Nested Function)」として実装する。
// ネスト関数として定義 [1]
func appendSeparator(separator: String) -> ([String] -> String) {
    func appendStrings(strings: [String]) -> String {
        return separator.join(strings)
    }
    return appendStrings
}
Swift の場合は、カリー化関数の専用の記法があるらしく次のように書くことができる。
// カリー化記法で定義 [2]
func appendSeparator(separator: String)(strings: [String]) -> String {
    return separator.join(strings)
}
要は、わざわざネスト関数を書かなくても良くなっている。

このカリー化により、ある文脈では改行結合、またある文脈では空白結合、するような関数を手に入れることが出来る。
let appendNewlineToStrings = appendSeparator("\n")
appendNewlineToStrings(strings: [ "A", "B", "C" ]) // "A\nB\nC"

let appendSpaceToStrings = appendSeparator(" ")
appendSpaceToStrings(strings: [ "D", "E", "F" ])   // "D E F"
複数の場所で必要な関数をそれぞれ定義するよりも割が良い、ということなのだろう。

以下のように、似た役割の定義を増やしていくのに比べると、カリー化関数の方が品が良いのがわかる。
func appendNewlineToStrings(strings: [String]) -> String {
    return appendSeparatorToStrings(strings, "\n")
}
func appendSpaceToStrings(strings: [String]) -> String {
    return appendSeparatorToStrings(strings, " ")
}
// ...
これをリファクタリングすると、やはりネスト関数のようになるだろうし、最終的にカリー化の書き方が出来た方が便利だ。

もっとも、この程度の例ではクロージャを使って以下のように書けてしまうので、ネスト関数もカリー化記法も必要ない。
func appendSeparator(separator: String) -> ([String] -> String) {
    return { separator.join($0) }
}
どの書き方でも意味も型も同じだし、どれが分かりやすいかという程度のものだろう。両方の記述を覚えておけば、その時に適切な書き方を選択できるし、知らない記述を前に面食らうことも無い。


ネスト関数(やクロージャ)とカリー化記法の違いとしては、カリー化記法の場合は外部名(External Parameter Name)を省略できない、というのがある(外部名とアンダースコアによる省略については 前の記事 を参照)。試しにカリー化記法 [2] にてアンダースコア(_)による省略を行ったところ、Xcode6-Beta5 時点では定義場所で警告、呼び出し箇所でエラーとなった。
// ネスト関数 ([1]) として定義した場合は、外部名指定は必要ない
appendSeparator("\n")([ "A", "B", "C" ])
appendNewlineToStrings([ "A", "B", "C" ])

// Swift のカリー化記法([2])を使った場合は、外部名が必要になる
appendSeparator("\n")(strings: [ "A", "B", "C" ])
appendNewlineToStrings(strings: [ "A", "B", "C" ])
しかし、以下のように型指定してやることで外部名は必要なくなった(*A)。
// Swift のカリー化記法([2])で定義した appendSeparator
let appendNewlineToStrings: [String] -> String = appendSeparator("\n")
appendNewlineToStrings([ "A", "B", "C" ])  // 外部名が必要なくなった。
これは推測だけど、カリー化記法はネスト関数のシンタックスシュガーで、カリー化記法の場合の第二引数(と言って良いのか?)は自動的にハッシュ(#)修飾されている、ような印象*1。どちらの書き方でも同じ型(関数型)で受け取ることができる。

ちなみに appendSeparator 自体の型は以下のようになる。
let appendSeparatorFunction: String -> [String] -> String = appendSeparator
-> が増えると、どことどこが結合するか分かりづらいが、「ネスト関数」の簡易記法であるという前提で、型が同じであることをふまえると、
let appendSeparatorFunction: String -> ([String] -> String) = appendSeparator
であることがわかる。右からグループ化していけば良い、ということは Apple の The Swift Programming Language: Types に書いてあった。

より冗長に、関数の記法 () -> () に乗っ取って書くとすれば以下のように書けるだろう。
let appendSeparatorFunction: ((String) -> (([String]) -> (String))) = appendSeparator


さらに興味深いのは、インスタンスメソッドのカリー化利用が可能だ、という点。
これは oleb.net で紹介されていた。
class BankAccount {
    var balance: Double = 0.0

    func deposite(amount: Double) {
        balance += amount
    }
}
通常は以下のように使うが、
let account = BankAccount()
account.deposite(100)
以下のようにカリー化した書き方が出来る。
let depositor = BankAccount.deposite
depositor(account)(100)

let accountDepositor = BankAccount.deposite(account)
accountDepositor(100)
型はこんな感じになる。
let depositor: BankAccount -> (Double -> ()) = BankAccount.deposite
Apple 的には T -> U -> R な書き方していたり ole さんは T -> (U) -> (R) だったりで、どれがスタンダードになってくるのか分からないが、自分はなるべく () で結合順序が明示されていた方が分かりやすい。

さらに元の記事ではインスタンスメソッドのカリー化を利用した Target-Action の実装例が載っている。実践的な使い方の例としてかなり有益。


ところで「結合順序」とは言ったものの、「値はすべてタプルである」という話があって、
// すべての値はタプル
let a = 10
println( a.0 )
println( a.0.0 )
dankogai さんの記事(Swiftの関数の引数は、常に一つ)から、関数の引数定義も実はタプルであるのが読み取れる。
func call<A,R>(f: A->R, a: A) -> R {
    return f(a)
}

func add(x: Int, y: Int) -> Int {
    return x + y
}

call(add, (21, 21))
詳しいことは元の記事を見てもらうとして、
言われてみれば確かに「関数の引数定義」は「タプルの名前付き定義」の書き方と同じ。
() は、もしかして「結合順序」と言うより、タプルの境界を定義するものと考えた方が良いのかも、しれない*2

例えば、先に検証したカリー化記法で外部名を外す方法(*A)は、以下のように別名を付けることが出来た。
// Swift のカリー化記法([2])で定義した appendSeparator
let appendNewlineToStrings: ((otherName: [String]) -> (String)) = appendSeparator("\n")
appendNewlineToStrings(otherName: [ "A", "B", "C" ])  // 別の外部名の指定が必須になる
これはなかなか面白い。

最初の外部名を外した例(*A)では、無名タプルで定義したので指定がいらなくなった(ラベルが外された)のに対して、この例ではラベル付きタプルで宣言されたので指定が必須になっている。


今回のカリー化の話は「すべての {} は関数ブロックである」ことや「関数がファーストクラスである」ことなど、一貫性の裏付けとして非常に興味深かった。
「すべての値はタプル」といい、記法がたくさんあって複雑にも見えるが、このあたりの Swift の一貫性は素晴らしいと思った次第*3


参考


...
*1: 正直、このあたりは Apple のドキュメントでは物足りないので、詳しい説明が欲しいところ。良い解説がどこかに無いだろうか?
*2: 気にし過ぎない方が良いかもしれない(結果的に結合順序だし)。追々調査したい。
*3: 最近のモダンな言語ならばどれもそうなのだろうけど。

2014年7月25日金曜日

[Swift] アンダースコア (_) の使いどころ

色々あるのでまとめてみた。

数値リテラルの区切り文字として使用


単純に見やすさのため数値リテラル内を区切ることが出来る。評価時には無視される。
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

タプル展開時の無視変数として使用


特定の要素のみ展開し、不要な要素を _ で無視。
let status = (200, "OK")
let (code, _) = status
let (_, message) = status

for-in ループ変数が必要ないときに使用


ループの現在値が必要ない時は _ で無視できる。
let base = 3
let power = 10
let answer = 1
for _ in 1...power {
   answer *= base
}
タプル展開も同様。
for (key, _) in dictionary {
    println("\(key)")
}

タプル + switch-case 文で、条件判定の無視要素として使用


ワイルドカード的な。
let point = (1, 1)
switch point {
    case (0, _)
        //
    case (_, 1)
        //
    default
        //
}

関数引数の外部名を無名化するのに使用


関数やメソッドのパラメータは外部名(External name)と内部名(Local name)をわけて宣言でき、外部名を宣言した場合は呼び出し時に指定しなければならない(省略できない)。

通常、外部名は宣言しなければ呼び出し時に指定する必要はないが、以下のケースでは自動的に外部名が追加される。この時の外部名を無名化するために使用することができる。

関数


普通の関数は、外部名を明示的に宣言に加えない限りは無名呼び出しが可能。
しかしデフォルト引数がある場合は別で、自動的に(内部名と同じ)外部名が追加され、呼び出し時に省略することは出来なくなる。
func hello(name: String = "World") {
    println("Hello, \(name)")
}

hello()
hello(name: "Swift") // デフォルト値を上書きする場合、引数名を省略できない。
これを回避するため、外部名を明示的にアンダースコア(_)として宣言することができる。
*当然ながら「本来は適切な名称をつけるべし」とされている。
func hello(_ name: String = "World") {
    println("Hello, \(name)")
}

hello()
hello("Swift") // 外部名の指定が必要なくなる
・・・らしいのだが、現時点の Xcode6-Beta4 ではクラッシュして動作を確認できていない。
Xcode6-Beta5 でクラッシュせずに実行できる事を確認。

また、以下のように内部名も無名化することができる。
func someFunction(_: Int) {
    //
}
この場合は変数にアクセスできなくなる。クロージャなど、引数を無視したい(使わない引数がある)場合に活用できる。

余談ながら、以下のように(必要もないのに)外部名を無名化したら警告が出た。
func someFunction(_ localName: Int) {
    //
}
コンパイラが教えてくれるので、特に迷うことはなさそう。

メソッド


メソッドにあって関数にない特性として、セカンドパラメータ以降は常に外部名が追加される、というのがある(デフォルトでは内部名 = ハッシュ修飾(#)と同じ効果)。
// 通常
class Counter {
    var count: Int = 0
    func incrementBy(amount: Int, numberOfTimes: Int) {
        count += amount * numberOfTimes
    }
}

let counter = Counter()
counter.incrementBy(5, numberOfTimes: 3) // セカンドパラメータ以降は外部名指定が必要
*この特性のため、セカンドパラメータ以降をハッシュ修飾(#)すると警告が出る。

この挙動を抑制する方法として、_ を外部名に指定することで、メソッド呼び出し時のセカンドパラメータ名を指定しなくともよくなる。
// 無名化
class Counter {
    var count: Int = 0
    func incrementBy(amount: Int, _ numberOfTimes: Int) {
        count += amount * numberOfTimes
    }
}

let counter = Counter()
counter.incrementBy(5, 3) // セカンドパラメータに名称が必要ない
*ファーストパラメータに対して _ を付けると警告になる。

イニシャライザ


イニシャライザの場合はさらに「全てのパラメータに対して外部名が追加される」という特性がある。
class Color {
    let red = 0.0, green = 0.0, blue: 0.0
    init(red: Double, green: Double, blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }
}

let black = Color(red: 0.0, green: 0.0, blue: 0.0)
これも関数やメソッド同様、必要であればすべてのパラメータに外部名を追加することは出来るが、アンダースコア(_)を指定することで無名化できる。
class Color {
    let red = 0.0, green = 0.0, blue: 0.0
    init(_ red: Double, _ green: Double, _ blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }
}

let white = Color(1.0, 1.0, 1.0)
イニシャライザは関数やメソッドと違って、パーレン記号の前に関数名のような識別子を持てないため、パラメータ名は意味を持たせるための重要な役割があると言える。無名化する場合は利用するときに困らない理由(名称が省略されても意味が明白、など)が欲しいところ。

外部名の自動追加は、基本的に Objective-C スタイルの(説明的な)呼び出しになるように、との配慮らしい。ObjC ではマナーに過ぎなかったことが Swift では言語で強制した感じ。


使ってはいけないところ


逆にアンダースコア(_)が使えないところとして明示されているのは、今のところ operator 定義時の優先度(precedence)値。
0〜255 の整数値だが、この時の区切り文字としては使えないとのこと。


まとめ


色々あると思ったけど、結局はどこを切っても「無名ラベル」として使えると。
なんなら以下のように宣言しても怒られない(使えないけど)。
let _ = 1
若干変わってると言えるのは数値リテラルの区切り文字くらいだろうか。

2014年7月23日水曜日

アイコンフォント利用のお供に - Symbol Imagine の紹介


アイコンフォントの利用をより便利にするユーティリティ Symbol Imagine の紹介。


アイコンフォントについて


アイコンフォントとは、文字の代わりにアイコンがグリフとして納められているフォントであり、以下のような特徴のため Web サイトやモバイル等の開発素材として使われています。

  • 1ファイルに納められているため、ディスクアクセス(リクエスト数)を減らせる
  • ベクターデータなので、高解像度対応も容易
  • 文字として描画するので、画像で表示するよりも軽い

私のような絵心に乏しい開発者には非常にありがたい存在です。
しかしこのアイコンフォント、プログラムの中で使うには若干の面倒さがあります。

使用するアイコンの指定方法が微妙
意味を持たないアルファベットや Unicode でアイコン指定しなければなりません。
この欠点をリガチャを使って解決しているフォントもあります。

表示するまでの微調整が面倒
フォントなので、プログラムでは属性付き文字として構築&描画することになります。
テーマや状態に合わせて、表示位置やサイズ、色の調整が必要です。

フォントファイルをパッケージにバンドルしなければならない
使用するフォントは、ユーザのマシン内にインストールされているとは限らないので、フォントファイルはアプリケーションが持ちます。


このような手間のため、開発時には画像であった方が便利だった、というケースがありました。もしかすると、アイコンフォントをプログラムの中で実行時に画像化してから描画している開発者もいるのではないでしょうか。

というわけで、これら開発時の手間を解決するため、
このたび Symbol Imagine というユーティリティアプリケーションをリリースしました。


Symbol Imagine とは


以下の機能を持っています。
  • アイコンフォントに登録されているシンボルレパートリーの一覧を表示
  • 大きさ・色を変更してプレビュー
  • プレビュー結果を画像化して書き出し

アイコンフォントの特徴により、表示を確認するまでには手間あり、さくっとモックアップを作るにはいささか不便です。
ふんだんに存在するアイコンフォントの中から、自分のアプリケーションとマッチしたテイストのアイコンを選定し、色、位置、サイズの調整するまでに、この手間は小さからぬ問題となります。

Symbol Imagine では色、サイズをカスタマイズできますので、実際に使用する前にアイコンのプレビューを確認することが出来ます。



アイコンフォントを使用する際に困るのが、アイコン指定が(リガチャが入っているフォントを除き)アルファベットや Unicode であることです。
割り当てられているアイコンがフォント毎にそれぞれ異なっているし、1フォント内にもたくさんのアイコンが入っているので、とても覚えられるようなものではなく、各フォント毎にチートシートが必須です。

Symbol Imagine は Unicode とグリフの一覧を出しますので、フォントのチートシート代わりにも使えます。
Unicode スカラ値やグリフ名で絞り込むこともできます。


インストールしていないフォントも表示可能

OS X 付属の純正アプリケーション Font Book では、フォント内レパートリー一覧を表示することができますが、フォントはインストールする必要があります。


Symbol Imagine は、インストールしていないフォントファイルを指定してプレビューすることが出来ます。特定のプログラムからしか使わないフォントファイルを自分のコンピュータにインストールする必要はありません。
様々なアイコンフォントを見比べて、どのアイコンを使うか吟味することが出来ます。

ライセンスにはご注意

それぞれのフォントにはライセンスが存在します。
Symbol Imagine の出力結果を実際の配布物に含める際には、使用するフォントのライセンスに従ってご利用ください。

フリー / オープンソースで配布されているフォントには OFL や MIT が多くあるので、それらであれば問題ないと思います。



ダウンロードはこちらから。

2014年7月22日火曜日

[Swift] @autoclosure 属性 の使いどころ

Xcode6-Beta5 にて @auto_closure は @autoclosure に変更された。
同様に LogicValue は BooleanType になった。
 [新] @autoclosure ← [旧] @auto_closure
 [新] BooleanType ← [旧] LogicValue
この変更にあわせて記事を修正した。

Apple Swift Blog にて @autoclosure 属性による遅延評価が紹介されていた。



C の assert() 実装を Swift で行った場合の例が引き合いに出されていて分かりやすい。

C 言語の assert 実装はマクロであるから、リリースビルドでは ((void)0) と展開されて、式の評価自体がない。
同様の実装を Swift で考えたとき、以下のような関数 func assert(x: Bool) の宣言では常に式の評価が発生してしまう。
func assert(x: Bool) {
#if !NDEBUG
    /*noop*/
#endif
}

assert(someExpensiveComputation() != 42)
というわけで、引数をクロージャに変更。するとシンタックスが残念な感じになる。
assert({ someExpensiveComputation() != 42 })
そこで @autoclosure の登場。
@autoclosure 属性は関数の引数に指定でき、渡された式をクロージャとしてラップする。
func myassert(predicate: @autoclosure () -> Bool) {
#if !NDEBUG
     if predicate() {
          abort()
     }
#endif
}
呼び出しが自然で遅延評価可能になる。
myassert(someExpensiveComputation() != 42)

標準ライブラリでは && 演算子も @autoclosure 引数を取るように宣言されていて、右オペランド(の式)は自動的にクロージャとしてラップされ、左オペランドが false であった場合は右オペランドの評価はされない。
func &&(lhs: BooleanType, rhs: @autoclosure () -> BooleanType) -> Bool {
   return lhs.boolValue() ? rhs().boolValue() : false
}
もし rhs が単なる BooleanType であったなら、&& が実行された段階で rhs が評価されてしまうし、 通常のクロージャ引数 rhs: () -> BooleanType にすると rhs の評価を遅延させることが出来るけども、以下のように面倒な書き方になってしまう。
let result = A() && { B() }

てなワケで、評価は遅延させたいけどもクロージャではなくて単純な式を渡せた方が良い場合に @autoclosure 属性は便利ですね、という話。

ちなみにこの属性が適用されたクロージャは引数を取ることは出来なかった(コンパイルエラーとなる)。式が自動的にラップされるので引数指定する機会がないし、当然か。