1

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.

Mason Edmison
  • 594
  • 3
  • 16
  • 2
    Sure just `inputs.map((x => modify[Machine](x)).compose(update))` Being honest, I would also recommend something less cryptic for real code. – Luis Miguel Mejía Suárez Jun 19 '21 at 21:48
  • 4
    Hmm, looks to me like `inputs.map(input => modify[Machine](update(input)))` because `update(input)` returns `Machine => Machine` which looks like what you should pass to `modify()`. – jwvh Jun 19 '21 at 22:06
  • 4
    "elegant" and "cryptic" are nearly antonyms in this context. If you find the code cryptic, then it's not elegant. – Silvio Mayolo Jun 19 '21 at 22:37
  • Appreciate the comments! The first comment doesn't compile. The second does and looks to work as expected - still not sure why it works though as `update` doesn't take any parameters. Could you provide an example using `compose`? – Mason Edmison Jun 20 '21 at 00:37
  • Right when I pressed enter, I realized why I "think" it works. update returns a function that takes an `Input`. So by passing an input to update this is actually returning another function that of `Machine => Machine` – Mason Edmison Jun 20 '21 at 00:38

2 Answers2

5

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)
)
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
0

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.

Mason Edmison
  • 594
  • 3
  • 16
  • Doesn't this answer directly contradict the answer from @Jorg that _you_ marked as the accepted answer? – jwvh Jan 02 '22 at 01:11
  • It does - I marked this answer originally because I _thought_ it was correct but only happened to stumble upon it after having a better understanding of eta expansion. Part of me just assumed it was correct too bc of the very high reputation :). Does my answer sound agreeable? If so, I will make this change. – Mason Edmison Jan 02 '22 at 15:17