-- dot notation on scope
TextArea.getCaretPosition inputArea
-- dot notation on reference
inputArea.getCaretPosition
アンダースコア・ドット記法
前回の記事「ドット記法の威力」では、モジュールやデータ型、型クラスのスコープに属する関数をプログラマがより簡単に参照できる方法として、ドット記法の使い方について学びました。
すでに見たとおり、ドット記法には以下の用法があり、ドットの直前には、データ型が参照されるスコープを明示する (TextArea
) か、もしくは値自体 (inputArea
) が現れます。
アンダースコア・ドット記法を用いることで、さらに簡潔に書くことができるようになります。
アンダースコア・ドット記法の導入
inputArea
の型が TextArea
であることがわかっている、という状況を仮定しましょう。最も単純な例は、明示的にこれを宣言することです。
insertionPoint :: TextArea -> JFX Int
insertionPoint inputArea = inputArea.getCaretPosition
これを見ると inputArea
がやや繰り返しになっていますが、アンダースコア・ドット記法を用いることでこの冗長性を排除することができます。
insertionPoint :: TextArea -> JFX Int
insertionPoint = _.getCaretPosition
言い換えれば、アンダースコア・ドット記法は単に
\inputArea -> inputArea.getCaretPosition
を
_.getCaretPosition
に置き換えるものであると言えます。
それでは、便利な使い方をもう少し見てみましょう。
その他の使用法
現在のスレッド名を取得する際、次のような様々な記法を使うことができます。
-- do notation
do
thread <- Thread.current()
name <- thread.getName
-- lambda with formal parameter
Thread.current() >>= \t -> t.getName
-- lambda with partial application
-- (needs 'Thread' as explicit scope)
Thread.current() >>= Thread.getName
-- underscore-dot notation
Thread.current() >>= _.getName
アンダースコア・ドット記法は、高階関数に対して、普通ならセクションや部分適用を使う代わりに使用する場合にも便利です。古典的な map
関数を使って、スレッドのリストからスレッド名にマッピングする例を見てみましょう。
map _.getName threads
レコード記法についても、特に更新したり値を操作したりする際に、アンダースコア・ドット記法が役に立ちます。フィールド name
を持つレコード Person
があったと仮定しましょう。ただしレコードに対する変更とは、単にレコードを渡して更新されたものを返す処理を指します。
data Person = Person {name :: String}
setName :: String -> State Person ()
setName newName = do
State.modify _.{name = newName}
最後に考察として
アンダースコアが使用できるのは、ドットが直後に続く場合 に限られます。まだ束縛されていない変数を参照するために独立したアンダースコアを使用することはできません。これは他の言語と大きく異なるところです。
すべての関数もしくはラムダ項は、アンダースコアで参照される自由変数を高々一つしか含みません。これは、Frege や Haskell では原則として、関数やラムダ項は引数を一つしか取らないからです。それ以外の自由変数は、以下の例のように束縛されないまま残ります。
String.startsWith "Dierk" "D"
"Dierk".startsWith "D"
map (\s -> s.startsWith "D") ["Ingo","Dierk"]
map (_.startsWith "D") ["Ingo","Dierk"]
さらに、アンダースコアは入れ子にして使うこともできます。
data Person = Person {addr :: Address}
data Address = Address {street :: String}
setStreet :: String -> State Person ()
setStreet newStreet = do
State.modify _.{addr <- _.{street = newStreet} }
最初のアンダースコアは Person
を、次のアンダースコアはその Address
を参照しており、それぞれがどの型を参照しているのかは明らかです。
しかし、コード中のアンダースコアの意味を「解読」することが難しい時であっても、明示的な引数に置き換えることはいつでも可能です。
...
State.modify (\p -> p.{addr <- (\a -> a.{street = newStreet} ) } )
将来的に、 IDE のサポート機能で二つの記法を切り替えを可能にするのは難しくありません。また _.
の後に続くコードの補完も、IDE の機能として面白そうです。
Haskell との比較
Frege でのドットの扱いを Haskell で同じように書くことはできず、特別な構文拡張であると考えることができます。
この拡張には、以下に挙げるような様々な利点があります。
-
上記のような、Java プログラマにとってより馴染みのある簡潔な記法が使える
-
Java 風の API やその元々の定義と相性がよい
-
IDE でコード補完が可能