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.