mzbaran
mzbaran

Reputation: 624

Can't figure out syntax in scala compose function

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

Answers (2)

mzbaran
mzbaran

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

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369458

There are a couple of syntax features in use here:

Operator Syntax

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)

Operator Syntax, part 2

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

Placeholder syntax

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 = _ + _

NOT η-expansion

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.

Conclusion

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

Related Questions