Scala: Functions and Partial functions are not functions. They are instances of some anonymous clss!
In Scala, you can define a function that takes and returns an Int like so:
val succ = (x: Int) => x + 1
succ
is a first class citizen here. It could be passed to other functions and returned from them just like any other value/variable.
But it’s hides its nature. It’s real self is somewhat sinister looking.
val succ = new Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}
Can you believe this shit?
Now look at how you can define a partial function in scala.
val divide: PartialFunction[Int, Int] = {
case d: Int if d != 0 => 42 / d
}
Not quite endearing. But ignoring the type definition, we can see that the function only handles inputs that are not zero. Elegant.
Not quite. Look at it’s real nature.
val divide = new PartialFunction[Int, Int] {
def apply(x: Int) = 42 / x
def isDefinedAt(x: Int): Boolean = x != 0
}
The Question
Looking at the actual form of both a normal function and a regular function, we notice something weird.
We instantiate an object using the new
keyword. In Scala, we can only instantiate a class. But here both Function1
and PartialFunction
are traits! So how does this syntax work?
It works because when the new
is followed by a block with methods, values, variables defined in it, then the block of code is treated as an anonymous class. We can confirm this by calling getClass()
on the created object.
Here’s a full example:
val anonObject = new {
def hello: String = "hi"
}
anonObject.hello
// "hi"
anonObject.getClass
// Class[_ <: AnyRef] = class $anon$1
What about traits abstract classes that a regular class can extend/mixin into itself?
Here’s were the syntax gets weird and I got so confused.
If you have a trait that you want to mix into the anonymous class, then you can’t just do this:
trait God {
def answer: Int
}
val anonObject = new {
def hello: String = "hi"
override def answer: Int = 42
} with God
Isn’t this what we’d think intuitively? But this is a syntax error! Instead we have to define it like this:
trait Anger {
def getAngry: String
}
val dog = new Anger { // NOTE the trait's placement!
def hello: String = "woof"
override def getAngry: String = "BARK!!"
}
dog.hello // 'woof'
dog.getAngry // 'BARK!!'
We have to put the trait name next to new
. It looks confusing as hell.
But at least I figured it out now.