2015年5月8日金曜日

[OS X] 写真.app ではカメラロールの削除ができない


OS X 10.10.3 から登場した iPhoto 置き換えの 写真.app。

操作感は iOS のそれに合わせてあり動作は iPhoto より軽快。iPhoto 依存機能を使い倒していたワケでもないので、このアップデートは歓迎するところだが。。

iPhoto 時代はできていた「Mac に読み込み済みの写真を iPhone のカメラロールから削除」ができなくなった。

↑コレです。移行済みでキャプチャ取れず、画像検索させていただいた。


iPhone で撮影した画像は自動的にフォトストリームにアップロードされ、Mac 側で iPhoto を起動すればフォトストリームの画像は自動的に iPhoto ライブラリに追加されていたので、iPhone の容量節約のためにカメラロール内に残った取り込み済み画像は削除していた。

つまりこんな使い方をしていた。

  1. iPhone で写真を撮影
  2. 自動でフォトストリームにアップロードされる
  3. Mac で iPhoto.app をすると、フォトストリームの新しい写真は Mac にコピーされる
  4. iPhoto で「すでに Mac に読み込まれたカメラロールの写真」を削除
  5. 必要な写真は iTunes 経由で改めて iPhone へ同期

新しい 写真.app では「4.」ができない。

もしかして 写真.app は iPhoto と違ってフォトストリームの画像をライブラリにコピーしないのか? 読み込み済み写真もあえて取り込まないと、フォトストリームから追い出された時にその写真は消えてしまうのか?

念のため iPhone でダミーの写真を撮影し、フォトストリーム経由で Mac 側 写真.app のモーメント(って言うのかな? 写真タブのトコロ)に表示されたのを確認したのち、その画像を削除してみたところ、フォトストリームとカメラロールからは削除されたが Mac には残った。

つまりフォトストリーム上の写真が自動で Mac にコピーされる(フォトライブラリに恒久的に残る)挙動は iPhoto と同じ。


写真.app でも「読み込み後に項目を削除」というチェックボックスはあるけど、フォトストリームの画像はすでに取り込み済み。あえて取り込み済み画像を選択してこのチェックをオンで読み込めば削除はされるけど、フォトライブラリは当然重複画像だらけになる。


動画はフォトストリーム経由で取り込めないので、「読み込み後に項目を削除」が有意な働きをするのは動画だけ。

写真.app で出来なければ、イメージキャプチャで削除するまでだが。



なぜ無くなったのだろう?

地味に面倒なのだが。

2015年5月2日土曜日

[Swift] NSApp で学ぶ AnyObject

発端


とあるコードを書いていた時、以下の現象に遭遇した。
NSApp.active // 呼べない(error)
NSApp.activationPolicy // 呼べる(success)
NSApp.activationPolicy() // 呼べない(error)
はて。

NSApplication クラスに active プロパティは存在するのに呼び出しエラーが発生。
activationPolicy() メソッドに至っては呼び出し可能なものの関数呼び出しの () を付けられない始末。


宣言を調べてみる


想定どおり active はプロパティ(computed-property)として宣言されているし、activationPolicy() はメソッドとして宣言されている。
var NSApp: AnyObject!

// … 中略 …

class NSApplication : NSResponder, … {
 // … 中略 …
 var active: Bool { get }
 // … 中略 …
 func activationPolicy() -> NSApplicationActivationPolicy
 // … 中略 …
}
当然、以下のように明示的な NSApplication 型であれば定義どおりの動作となる。
let app: NSApplication = NSApplication.sharedApplication()
app.active // OK
app.activationPolicy // NG … というか Function 型が返る
app.activationPolicy() // OK
ということは NSApplication 型ではなく AnyObject! 型になっている NSApp 変数を使用したために、この現象が起きたのは明白だ。


疑問


気になるのは2つ。
  • なぜ AnyObject! に対する activationPolicy の呼び出しは成功するのに active 呼び出しは失敗するのか
  • なぜ AnyObject! に対する activationPolicy の呼び出しはメソッドであるにもかかわらず () をつける事ができないのか


Swift の AnyObject と Objective-C の id は少し違う


NSApp は Objective-C の場合 id 型で宣言されている。その NSApp が Swift で対応するクラスなんでも型の AnyObject が使われているのは納得がいく。

Objective-C の id 型なら、中身が何であろうと(宣言さえ存在していれば)どんなメッセージでも送る事ができる。

しかし Swift の AnyObject は、中身のインスタンスの正しいメンバ呼び出しでも失敗する。
class Foo {
 var myProperty = "Foo's property"
 func myFunction() { println("Foo's function") }
}
var foo: AnyObject = Foo()
foo.myProperty // error: 'AnyObject' does not have a member named 'myProperty'
foo.myFunction() // error: 'AnyObject' does not have a member named 'myFunction'
Swift の AnyObject は ObjC の id のような汎用インスタンス参照として用意されたものだが、完全に型安全だ。本来の型にキャストすることなしにメンバを呼び出すことはできない。

ObjC で id 型を扱った時に発生しやすいランタイムエラーを避けることができるものの、動的呼び出しのポテンシャルも損なっている。


AnyObject に対するメソッド呼び出しが成功したワケ


では NSApp が AnyObject であるのに NSApplication メンバを呼び出せるのはどういうワケかというと、Swift には AnyObject 型を ObjC の id 型と互換性を取るための仕組みがある。

You can also can any Objective-C method and access any property without casting to more specific class type. This includes Objective-C compatible methods marked with the @objc attribute.

Using Swift with Cocoa and Objective-C

Objective-C 互換クラスを作成するための属性 @objc を付けたクラスならば、一旦 Objective-C クラスにブリッジされるため id 型と同等の働きとなる。 ObjC 由来のクラス(NSObject)を継承しているクラスならば、すでに @objc 属性が付いたものとして扱われる。

つまり NSApp(AnyObject)の実体である NSApplication は ObjC 由来のクラスなので @objc 属性付きであるため、メンバ呼び出しが ObjC のそれと同等となった。
AnyObject である NSApp に対して activationPolicy が呼び出せたのは、この機能のおかげだ。


AnyObject に対するプロパティ呼び出しが失敗したワケ


次の例を見れば分かる通り、本来なら @objc 属性を付けたクラスならば AnyObject 型に対してプロパティ呼び出しも成功するはず。
@objc class Foo {
 var myProperty: String = "property"
}
var foo: AnyObject = Foo()
foo.myProperty // success
にも関わらず、NSApp.active 呼び出しは失敗した。なぜか。

実は active の他にも NSApp という AnyObject に対して、呼び出しが失敗するプロパティは幾つかあったが、逆に成功するプロパティもあった。
どうやら Swift と ObjC での宣言の違いが原因のようだ。
Swift 宣言ObjC 宣言AnyObject での呼び出し可否
unowned(unsafe) var mainWindow: NSWindow? { get }@property (readonly, assign) NSWindow *mainWindow;OK
unowned(unsafe) var keyWindow: NSWindow? { get }@property (readonly, assign) NSWindow *keyWindow;OK
var active: Bool { get }@property (getter=isActive, readonly) BOOL active;NG
var hidden: Bool { get }@property (getter=isHidden, readonly) BOOL hidden;NG
成功する呼び出し(mainWindow, keyWindow)は Swift / ObjC ともに宣言が一致している。逆に失敗する呼び出し(active, hidden)は、ObjC で getter に別名が付けられている。

要するに Swift 側の宣言と ObjC 側の宣言でプロパティ名が一致していれば AnyObject に対して呼び出し可能となる。逆に active と isActive のように宣言が一致していなければ Swift 側の宣言で呼び出す事ができない。

@objc 属性がついた AnyObjectは ObjC ブリッジのため ObjC API でメンバを探すらしい。なので NSApp で active や hidden を呼び出したいのなら
NSApp.isActive // success
NSApp.isHidden // success
上記のように ObjC の定義を使えば成功するのであった。


AnyObject に対するメソッド呼び出しに () が付けられなかったワケ


activationPolicty 呼び出しに () が付けられなかったのも同様の問題だろうと思われる。

Objective-C 側では
- (NSApplicationActivationPolicy) activationPolicy NS_AVAILABLE_MAC(10_6);
という宣言で、メソッドではあるけども KVC 準拠なので ObjC 側でも呼び出しはプロパティ扱いになる。 この ObjC 宣言にマッチして呼び出しが発生するので、「プロパティに () を付けるな」というエラーになっていたのだろう。

@objc な AnyObject ではこういう事が起きてしまうらしい。


結論

  • 中身が @objc なクラスのインスタンスであれば AnyObject の実体に対するメンバ呼び出しはアリ
  • @objc な AnyObject の中身が Swift / ObjC 両方に定義があるクラスのインスタンスでは、ObjC 側の定義で呼び出す

参考

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
若干変わってると言えるのは数値リテラルの区切り文字くらいだろうか。