2014年8月21日木曜日

[Swift] 関数引数としてのタプル

以前書いた「カリー化関数の話」という記事の後半で
言われてみれば確かに「関数の引数定義」は「タプルの名前付き定義」の書き方と同じ。
() は、もしかして「結合順序」と言うより、タプルの境界を定義するものと考えた方が良いのかも、しれない。
などと書いていたのだが、ようやくこれを試してみた(Xcode6-Beta6)。

例えば以下のような関数を定義したとする。
func f(a: Int, b: Int) {
    println("a + b = \(a + b)")
}
通常は次のように使う。
f(1, 2)
こうなると (1, 2) はタプルに見えてくるだろうと言うことで、タプルそのものを渡せるのか。結論から言うと渡すことが出来た。
let v = (1, 2)
f(v)
これはかなりナイス。

名前付き引数を要求されている場合なら、名前付きタプルで OK。
func f(#a: Int, #b: Int) {
    println("#a + #b = \(a + b)")
}

let  v = (a: 1, b: 2)
f(v)
あちこちで使うようなテクニックでは無いにしろ、使えるのは便利だし、関数の引数定義はタプルを拡張したものであるようなことが伺える。

しかしいくつかの制限も見られた。


まず、引数に渡せるタプルは let 宣言に限られていて、var 宣言のタプルはエラーになった。
しかしこれはキャストすれば回避可能であった。
func f1(a: Int, b: Int) {
    println("a + b = \(a + b)")
}
var v1 = (1, 2)
// f(v1)            // error: Missing argument for parameter #2 in call
f(v1 as (Int, Int)) // success
名前付き引数なら、当然名前付きタプルでキャストすれば良い。
func f2(#a: Int, #b: Int) {
    println("#a + #b = \(a + b)")
}
var v2 = (a: 1, b: 2)
// f(v2)                  // error: Missing argument for parameter 'b' in call
f(v2 as (a: Int, b: Int)) // success

次に、引数が1つの場合は名前付きタプルは文法上渡せなかった。
func f3(#a: Int) {
    println("#a = \(a)")    
}
let  v3 = (a: 1)
// f3(v3)             // error: Missing argument label 'a:' in call
// f3(v3 as (a: Int)) // error: Cannot create a single-element tuple with an element label

let  v4: (a: Int) = (a: 1) // error: Cannot create a single-element tuple with an element label
Beta6 からは単一要素のタプルには名前がつけられなくなっている。
上記の例では v3.a にはアクセスできない。

元々、(Int) という宣言は、単なる Int 型として見られていたので、これが Beta6 でより強化されたのだろう*1

名前付き引数でなければ問題はないし、単一要素をわざわざタプル化しないだろうから現実的に問題ではないと言って良い。


「関数引数 ≒ タプル」と見た場合、総合的にはまぁ想定の範囲内の結果。
気になるのは var タプルの場合のキャストくらい。しかしこれも正式版までにはまた変わりそうな気もする。

...
*1: 余談だが Beta5 までなら上記の f3(v3 as (a: Int)) と f3(v4) は成功を確認している。

2014年8月7日木曜日

[Swift] コマンドライン(Command Line)プログラムとして使う [2] 外部モジュールの作成と使用

前回の続き。
思ってたより全然面倒な話だったので、試行過程も記録しておく。


さて。なぜ使い回すソースコードをモジュール化するかというと、Objective-C と違い Swift の import はヘッダファイルではなくモジュールだから、ということになるだろう。

モジュールを作る


とりあえず作ってみる。
xcrun swift -help を眺めると、モジュール化できそうなオプションがいくつか見つかる。

まずはモジュール用のサブディレクトリを作り、モジュール用のソースコードを以下のように書いた。
/* modules/Example.swift: モジュールとして使われる方 */
public func MyFunction(name: String) -> String {
    return "Hello, \(name)"
}

public class Example {
    public let  name: String

    public init(name n: String) {
        name = n
    }
}
外から参照できるようにすべて public にした。そして以下のモジュール化コマンドを実行。
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift \
    -frontend \
    -emit-module \
    -module-name Example \
    -emit-module-path modules/Example.swiftmodule \
    -emit-module-doc-path modules/Example.swiftdoc \
    modules/Example.swift
modules/ に Example.swiftmodule と Example.swiftdoc が作成される。

次はモジュールを使う側のファイルを(前回からの続きの command-6.swift として)作成。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift -I ./modules
import Example

println(MyFunction("Module"))

var  module = Example(name: "Command Line")
println("Hello, \(module.name)")
-I オプションでモジュールの場所を伝える。

しかし、これを実行するとエラーとなる。
% ./command-6.swift
LLVM ERROR: Program used external function '_TF7Example10MyFunctionFSSSS' which could not be resolved!
import は成功してるけど、シンボルを解決できていない。

swiftmodule の中には実行データが入ってないのか。思えば Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx には .swiftmodule、.swiftdoc の他 .dylib も一緒に入ってる。ということで dylib が必要なのだろう。


ライブラリを作る


ライブラリ化するため、今度はコンパイラ swiftc を使う。まずはオブジェクトファイルを作成。
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swiftc \
    -emit-library \
    -emit-object modules/Example.swift \
    -o modules/Example.o
出来たオブジェクトファイル(modules/Example.o)から dylib を作成する。
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun libtool \
    -macosx_version_min 10.9 \
    -L/Applications/Xcode6-Beta5.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \
    -dynamic \
    -install_name @rpath/libExample.dylib \
    -lswiftCore \
    -lSystem \
    -o modules/libExample.dylib \
    modules/Example.o
これでライブラリが modules/libExample.dylib として作成された。

install_name オプションで @rpath を指定したのは executable 側が検索パスを決めるため。今回のケースでは dylib を別の場所で使うワケではないので影響しないが、.app に入れることも考えればあった方が良いオプション。

作成した dylib を使用するように先の command-6.swift を修正する。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift -I ./modules -L ./modules -lExample
import Example

println(MyFunction("Module"))

var  module = Example(name: "Command Line")
println("Hello, \(module.name)")
-L オプションでライブラリの場所を、-l でリンクするライブラリを指定した。

これで実行できるかと思いきや、またもやエラーが。
% ./command-6.swift
<unknown>:0: error: could not load shared library 'libExample'
ライブラリを見つけられていない。

これに小一時間ハマってしまったがなんのことはない、実行時カレントディレクトリに libExample.dylib があれば見つけてくれた。
-L オプションはリンク時のライブラリの場所であって、実行時のライブラリの場所ではないってことだ。

せっかくスクリプトっぽく使えると思ったのに(いちいちライブラリ化するのは我慢するとしても)ライブラリと同じディレクトリにいないと実行できないとかよろしくない。

実行時ライブラリ検索は LD_RUNPATH_SEARCH_PATHS だろうから、これを指定する方法を模索したのだが、残念ながらこれも結局 swiftc でビルドするしか無い、という結論に落ち着いた。
% sudo xcode-select -switch /Applications/Xcode6-Beta5.app
% xcrun swiftc -I ./modules -L ./modules -lExample -Xlinker -rpath -Xlinker ./modules -o ./command-6 ./command-6.swift

% ./command-6
Hello, Module
Hello, Command Line
実行成功。いやビルドしちゃってるから。当たり前すぎて感動がない、っていう。

リンカオプションとして rpath を指定するのだけど、この時ばかりは xcode-select -switch しないとエラーになってしまった。

中で使われているであろう xcodebuild がなんか解決できなくてエラーになっていたようなので、ビルドするのなら素直に -switch しときましょう、と。


まとめ


スクリプトのように書けると思われた Swift コマンドラインプログラムだがひとたび自作の外部モジュールを使うとなると、モジュールのライブラリ化が必要で、使う方は executable としてビルドするか、もしくはライブラリをカレントにコピーしないといけない。

そういうものかと思う一方で、ここら辺に関しては本来 xcrun swift に良きに計らってもらいたいという気持ちもある。

Xcode を使うことでこれらの手間を軽減できるかと思いきや、現状(Xcode6-Beta5)は Command Line Tool もモジュール関係もテンプレートがない。

今のところコマンドラインは、そう言う手段がある、という程度が無難かも。
正式版に期待。


参考



2014年8月6日水曜日

[Swift] コマンドライン(Command Line)プログラムとして使う [1]

そう言えばコマンドラインとして使える話があったので、今更試してみたメモ。

コマンドラインツール群はコンパイラ等と同様にアプリケーションパッケージ内にある。
通常は xcode-select -print-path で表示されている Xcode 内のツールが使われるが、現状ベータなので xcode-select -switch で変更せずに直接パスを叩く方針にする。

REPL(Read-Eval-Print-Loop)を使ってみる


まずは REPL を立ち上げてみる。
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
Welcome to Swift!  Type :help for assistance.
  1>  
または以下でも同じものが立ち上がる。
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/lldb --repl
% /Applications/Xcode6-Beta5.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
終了するには Ctrl-D を押すか、REPL に :q (または :quit) とタイプする。

一通りのことは出来るようだが、出来ることに若干の違いがあるような気がする。


ファイルを実行する


コマンドラインなので、REPL よりもファイルから実行できた方が良い。
以下のようなフツーの swift ファイルを書いて実行することが出来た。
func hello(name: String) -> String {
    return "Hello, \(name)"
}

println(hello("Swift"))
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift command-1.swift
Hello, Swift
古いサンプルでは xcrun swift -i の指定があるものも残っているが、Beta5 から -i オプションは必要なくなっている。

通常のアプリケーションコードと違うのは、コマンドラインや REPL の場合は Playground と同様に order-dependent なので定義順にしか解釈されないこと。
アプリケーションコードであっても main.swift のようにトップレベルに実行可能コードが書けるものは order-dependent となるのだが、この辺の話は Apple Swift Blog の Files and Initialization に書いてある。

当たり前だが REPL や Playground とは違い、コマンドラインでは標準出力に出力しなければ結果は表示できない。


よりコマンドラインらしく、shebang を書いて実行権限をつければ単体で実行可能となる。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift

func hello(name: String) -> String {
    return "Hello, \(name)"
}

println(hello("Swift"))
% chmod +x command-1.swift
% ./command-1.swift
Hello, Swift
まるでスクリプトのように実行できる。


引数を取る


さて、コマンドラインとして使うなら引数が取れないと意味が無い。

Foundation を使えば Cocoa の流儀で取得できる。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
import Foundation

let  keyName = "keyName"
let  args = NSUserDefaults.standardUserDefaults()

println("\(keyName): \(args.objectForKey(keyName))")
% ./command-2.swift -keyName value
keyName: value

% ./command-2.swift -keyName '("a", "b")'
keyName: (
    a,
    b
)

% ./command-2.swift -keyName '{ "k1"=1; "k2"=2; }'
keyName: {
    k1 = 1;
    k2 = 2;
}
Array や Dictionary の指定が Swift-Style ではなく ASCII Property List Style になってしまうのは NSUserDefaults の都合上、致し方ない。

特定の引数を取得するのは NSUserDefaults で良いが、引数一覧を取得する場合は NSProcessInfo を使う。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
import Foundation

for arg in NSProcessInfo.processInfo().arguments {
    println("\(arg)")
}
% ./command-3.swift a b c
/Applications/Xcode6-Beta5.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
-frontend
-interpret
./command-3.swift
-enable-objc-attr-requires-objc-module
-target
x86_64-apple-darwin13.3.0
-target-cpu
core2
-module-name
main
-sdk
/Applications/Xcode6-Beta5.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk
-color-diagnostics
--
a
b
c
実はたくさんの引数がついていた。

Foundation を使わずに Pure Swift で引数を取ることが出来ないか調べたところ、practicalswift.com がヒットした。どうやら Process というのが定義されているらしい。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift

for arg in Process.arguments {
    println("\(arg)")
}
% ./command-4.swift a b c
./command-4.swift
a
b
c
これなら Pure Swift だし、NSProcessInfo を使うよりも実用的な引数一覧になった。

しかし、Process の実態には Xcode 上で定義にジャンプすることが出来ず、どこにどのように定義されているものかわからない。

この定義をどうやって見つけたのだろう?


他のモジュールを使う


先の例までで Foundation を使っていたが、当然 AppKit も使えるだろうということで、GUI テストとしてアラートを表示してみる。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
import Cocoa

var  alert = NSAlert()
alert.messageText = "Hello, Swift"
alert.runModal()
% ./command-5.swift
問題なく表示できた。これは素晴らしい。


これなら GUI が必要な時はシェルスクリプト代わりに swift を使うのもアリだ。


では自作のモジュールを取り込むときは?
シェルスクリプトで使い回し可能コードを source して取り込むように、swift コマンドラインで使い回し可能コードを import で取り込めないものか。

これを行う為には、使い回す swift コードをモジュール化する必要がある。


続きは次回


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 属性は便利ですね、という話。

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