2

A and I are doing some work with circe to encode/decode some ADTs and we ran into some functionality we fundamentally don't understand. The examples given in the circe documentation work as expected, but upon drilling down - it's not clear why the decoding example works and therefore we are having a hard time reasoning about how to modify if needed.

The functionality (from Circe Examples about ADTs):

import cats.syntax.functor._
import io.circe.{ Decoder, Encoder }, io.circe.generic.auto._
import io.circe.syntax._

object GenericDerivation {
  // Encoder Redacted

  implicit val decodeEvent: Decoder[Event] =
    List[Decoder[Event]](
      Decoder[Foo].widen,
      Decoder[Bar].widen,
      Decoder[Baz].widen,
      Decoder[Qux].widen
    ).reduceLeft(_ or _)
}

I get it - basically pick the first decoder that works from this list- makes sense BUT(!)

or appears to be a unary function taking a by name argument. Type signature is:

final def or[AA >: A](d: => Decoder[AA]): Decoder[AA]

And reduceLeft (from IterableOnce) requires a binary function. Type Signature is: def reduceLeft[B >: A](op: (B, A) => B): B

And then my brain exploded. I am clearly missing something and can't figure it out.

The example most decidedly works to convert ADTs. Why/how does this work given that the or function doesn't seem to be meeting the required type by reduceLeft?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66

2 Answers2

7

or is a method of one parameter but don't forget about this.

decoder1.or(decoder2) (aka decoder1 or decoder2) is a binary function with respect to decoder1, decoder2.

+ is also a method of one parameter

final abstract class Int private extends AnyVal {
  ...

  /** Returns the sum of this value and `x`. */
  def +(x: Int): Int

  ...
}

but you can still add two Ints: 1 + 1 aka 1.+(1).

All methods have one "parameter" more than listed in their signatures, namely this.

(All ordinary parameters are resolved statically and this is resolved dynamically.)

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • thank you and I am now invoking self.foreheadSlap - I was looking for the reference to `this` earlier and totally missed it in the function implementation From the trait: `trait Decoder[A] extends Serializable { self =>...` from within the `or` implementation `override def tryDecode(c: ACursor): Decoder.Result[AA] = self.tryDecode(c) ...` – six_minute_abs Jul 23 '20 at 16:23
2

Consider the following simplified analogy

case class Score(v: Double) {
  def add(that: Score): Score = Score(this.v + that.v)
}

val l = List[Score](Score(1.1), Score(2.2), Score(0.1))

l.reduceLeft((a: Score, b: Score) => a.add(b))  // unsugared
l.reduceLeft((a: Score, b: Score) => a add b)   // infix notation
l.reduceLeft(_ add _)                           // anonymous function placeholder parameter

Note the usage of infix notation and anonymous function placeholder parameter

Mario Galic
  • 47,285
  • 6
  • 56
  • 98