-- dot notation on scope
TextArea.getCaretPosition inputArea
-- dot notation on reference
inputArea.getCaretPosition
The underscore-dot notation
We have already seen "The Power of the Dot" and how it can make programming easier when referring to functions that are in the scope of a module, a data type, or a type class.
We have seen usages like below where before the dot either
the explicit scope of the data type (TextArea
) is referenced or
a value thereof (inputArea
).
The underscore-dot notation allow us to be even more succinct.
Introducing the underscore-dot
Let’s suppose that we are in a situation where we know that
inputArea
is of type TextArea
. The simplest example is an explicit
declaration.
insertionPoint :: TextArea -> JFX Int
insertionPoint inputArea = inputArea.getCaretPosition
We see that inputArea
is somewhat duplicated and the underscore-dot notation
resolves that redundancy.
insertionPoint :: TextArea -> JFX Int
insertionPoint = _.getCaretPosition
In other words, the underscore-dot notation just replaces
\inputArea -> inputArea.getCaretPosition
with
_.getCaretPosition
Let’s see more places where this is useful.
More Usages
For getting the name of the current thread, we have a variety of notational options.
-- 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
The underscore-dot notation is also nice to use for higher-order functions
where one would otherwise use sectioning or partial application. Let’s try this with
the classical map
function and the usage of mapping a list of thread values
to their names.
map _.getName threads
Even the record-syntax can profit from using the underscore-dot notation, particularly
when updating or modifying a record. Assume we have Person
record with a name
field.
Modifying a person record just means passing in a person record and returning an updated one.
data Person = Person {name :: String}
setName :: String -> State Person ()
setName newName = do
State.modify _.{name = newName}
Final Thoughts
One can only use the underscore in combination with an immediately following dot. A free-standing underscore is not allowed as a means of referring to a yet unbound variable. This is very much unlike other languages.
In every function or lambda term there is at most one such unbound variable that the underscore can refer to since, technically, functions and lambda terms in Frege and Haskell only ever take one parameter. Any additional unbound variables remain unbound like in the example below.
String.startsWith "Dierk" "D"
"Dierk".startsWith "D"
map (\s -> s.startsWith "D") ["Ingo","Dierk"]
map (_.startsWith "D") ["Ingo","Dierk"]
Furthermore, underscores can appear in a nested fashion.
data Person = Person {addr :: Address}
data Address = Address {street :: String}
setStreet :: String -> State Person ()
setStreet newStreet = do
State.modify _.{addr <- _.{street = newStreet} }
The first underscore refers to a Person
, the second one to its Address
and there can be
absolutely no question about which underscore refers to which type.
However, if you ever find it difficult to "decipher" the meaning of an underscore in your code, you can replace it with an explicit parameter any time.
...
State.modify (\p -> p.{addr <- (\a -> a.{street = newStreet} ) } )
Future IDE support could easily provide refactorings that switch between the two notations.
Code completion after _.
would be another interesting IDE feature.
Comparison to Haskell
The way that Frege is handling the dot is not available in Haskell. One can see it as a special syntax extension.
This extension has many benefits. Between them are:
-
terse notation as seen above that is familiar to Java programmers
-
typically plays well with Java-like APIs and their native definitions
-
enables IDE code completion