Reputation: 624
I am working through the red book and when checking my answers for the exercises , I found that the solution for one of the exercises (6.11) was quite different (and much more elegant and cryptic than my own solution).
Here is the code:
object Candy {
def update: Input => Machine => Machine = (i: Input) => (s: Machine) =>
(i, s) match {
case (_, Machine(_, 0, _)) => s
case (Coin, Machine(false, _, _)) => s
case (Turn, Machine(true, _, _)) => s
case (Coin, Machine(true, candy, coin)) =>
Machine(false, candy, coin + 1)
case (Turn, Machine(false, candy, coin)) =>
Machine(true, candy - 1, coin)
}
def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = for {
_ <- State.sequence(inputs map (modify[Machine] _ compose update))
s <- get
} yield (s.coins, s.candies)
def modify[S](f: S => S): State[S, Unit] = for {
s <- get // Gets the current state and assigns it to `s`.
_ <- set(f(s)) // Sets the new state to `f` applied to `s`.
} yield ()
def get[S]: State[S, S] = State(s => (s, s))
def set[S](s: S): State[S, Unit] = State(_ => ((), s))
The bit I am unsure of is this line:
inputs map (modify[Machine] _ compose update)
I understand how compose works but this particular syntax is really throwing me for a loop. Is there a way that this can be rewritten that is not so compact that would help a noob understand?
Thanks in advance.
Also for anyone else trying to better understand how this code works, this post I found helpful.
Upvotes: 1
Views: 175
Reputation: 624
Happened to stumble upon this. For anyone reading this, the underscore in inputs map (modify[Machine] _ compose update)
is eta expansion, ie the _
translates the method into a function allowing this to be further composed to create a function of the form Input => State[Machine, Unit]
.
val t: Function1[Input, State[Machine, Unit]] = modify[Machine] _ compose update
Note that in scala3
, this eta expansion takes place automagically and the _
syntax is not needed.
Upvotes: 0
Reputation: 369458
There are a couple of syntax features in use here:
Scala allows message sends to use whitespace instead of a period as the message sending operator, i.e.
foo.bar(baz, quux)
can also be written as
foo bar(baz, quux)
When using operator syntax with a single argument list with a single argument, the parentheses can be left off, i.e.
foo bar(baz)
can also be written as
foo bar baz
Anonymous functions in Scala can be written using the underscore _
as a placeholder for the arguments. Roughly speaking, each underscore is replaced with each argument for the closest lexically enclosing anonymous function, in order of appearance in the source text, i.e.
val adder: (Int, Int) => Int = (x, y) => x + y
can also be written as
val adder: (Int, Int) => Int = _ + _
The underscore _
has many uses in Scala, so sometimes if you don't look careful enough, you might confuse different usages. In this case, at a cursory glance, it seems like the underscore _
could mean that modify
is η-expanded into a method value, but that is not the case. The underscore _
is an anonymous function argument placeholder.
So, if we put the above three syntax features together, the snippet in question desugars to
inputs.map(
// ↑ Operator Syntax
(
f => modify[Machine](f)
// ↑ Placeholder Syntax ↑
).compose(update)
// ↑ Operator Syntax
)
If you ever have doubts about what the syntax of a particular piece of code means, you can print out the internal state of the Scala compiler or the Scala REPL / "interpreter" at various phases using the -Xprint:<name-of-phase>
command line option to scala
. For example, this is what -Xprint:parser
prints for the snippet in question:
inputs.map((modify[Machine]: (() => <empty>)).compose(update))
And this is -Xprint:typer
:
inputs.map[this.State[this.Machine, Unit]](
(
(f: this.Machine => this.Machine) => $anon.this.State.modify[this.Machine](f)
).compose[this.Input](Candy.this.update)
)
Upvotes: 5