Understand how to use apply and unapply

So apply and unapply are just defs that have extra syntax support.

Apply takes arguments and by convention will return a value related to the object's name. If we take Scala's case classes as "correct" usage then the object Foo's apply will construct a Foo instance without needing to add "new". You are free of course to make apply do whatever you wish (key to value in Map, set contains value in Set, and indexing in Seq come to mind).

Unapply, if returning an Option or Boolean can be used in match{} and pattern matching. Like apply it's just a def so can do whatever you dream up but the common usage is to extract value(s) from instances of the object's companion class.

From the libraries I've worked with serialization/deserialization defs tend to get named explicitly. E.g., write/read, show/read, toX/fromX, etc.

If you want to use apply/unapply for this purpose the only thing I'd suggest is changing to

def unapply(f: Foo): Option[JValue]

Then you could do something like:

val myFoo = Foo("""{name: "Whiskers", age: 7}""".asJson)
// use myFoo

val Foo(jval) = myFoo
// use jval

Firstly, apply and unapply are not necessarily opposites of each other. Indeed, if you define one on a class/object, you don't have to define the other.

apply

apply is probably the easier to explain. Essentially, when you treat your object like a function, apply is the method that is called, so, Scala turns:

obj(a, b, c) to obj.apply(a, b, c).

unapply

unapply is a bit more complicated. It is used in Scala's pattern matching mechanism and its most common use I've seen is in Extractor Objects.

For example, here's a toy extractor object:

object Foo {
  def unapply(x : Int) : Option[String] = 
    if(x == 0) Some("Hello, World") else None
}

So now, if you use this is in a pattern match like so:

myInt match {
    case Foo(str) => println(str)
}

Let's suppose myInt = 0. Then what happens? In this case Foo.unapply(0) gets called, and as you can see, will return Some("Hello, World"). The contents of the Option will get assigned to str so in the end, the above pattern match will print out "Hello, world".

But what if myInt = 1? Then Foo.unapply(1) returns None so the corresponding expression for that pattern does not get called.

In the case of assignments, like val Foo(str) = x this is syntactic sugar for:

val str : String = Foo.unapply(x) match {
  case Some(s) => s
  case None    => throw new scala.MatchError(x)
}

The apply method is like a constructor which takes arguments and creates an object, whereas the unapply takes an object and tries to give back the arguments.

A simple example:

object Foo {

    def apply(name: String, suffix: String) = name + "." + suffix

    def unapply(name: String): Option[(String, String)] = {
      //simple argument extractor
      val parts = name.split("\\.")
      if (parts.length == 2) Some(parts(0), parts(1)) else None
    }
  }

when you call

val file = Foo("test", "txt")

It actually calls Foo.apply("test", "txt") and returns test.txt

If you want to deconstruct, call

val Foo(name) = file

This essentially invokes val name = Foo.unapply(file).get and returns (test, txt) (normally use pattern matching instead)

You can also directly unpack the tuple with 2 variables, i.e.

scala> val Foo(name, suffix) = file
val name: String = test
val suffix: String = txt

BTW, the return type of unapply is Option by convention.

Tags:

Scala