Scala Method Overloading and Default Argument Values

Using the magent pattern to get around a limitation with Scala’s overloading implementation.

I was writing some code recently that wanted to do this(somewhat contrived to simplify the example):

1
2
def set(key: String, value: String)(implicit ttl: Duration = 1.hour): Int
def set(key: String, value: ByteString)(implicit ttl: Duration = 2.hour): Int

There’s some specific detail about how the Scala compiler implements default values for method parameters - that I havent’t investigated and probalby wouldn’t understand anyway - that makes it say the following:

1
... multiple overloaded alternatives of method set define default arguments.

There’s clearly a way around this problem; don’t overload. So, I turned to the magnet pattern as popularized by the Spray library. It’s really just a specialized use for type classes that carries a cool name so it’s easy to talk about and reference. My use of the pattern, in this case, is even simpler because the return type of the method doesn’t vary with the magent instance. At any rate, here’s the deal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
sealed trait ByteStringMagnet {
val bs: ByteString
val ttl: Duration
def apply(f: (ByteString, Duration) => Int): Int = f(bs, ttl)
}

object ByteStringMagnet {
import language.implicitConversions

implicit def fromString(s: String)(implicit ttlive: Duration = 1.hour): ByteStringMagnet = new ByteStringMagnet {
val ttl = ttlive
val bs = ByteString(s)
}

implicit def fromByteString(bytes: ByteString)(implicit ttlive: Duration = 2.hours): ByteStringMagnet = new ByteStringMagnet {
val ttl = ttlive
val bs = bytes
}
}

object Main extends App {
/** * Now we have only one instance of the `set` method; the magnet pattern takes * care of pulling the various types down into the argument. */
def set(key: String, magnet: ByteStringMagnet): Int =
magnet { (value, ttl) =>
println(s"$ttl")
5
// Do something with the ByteString value and return an Int 5
}

set("key", "str")
set("key", ByteString("str"))
}

Problem solved. The implicit conversions are resolved nicely because they’re right on the ByteStringMagnet and, in general, you should never have to define an implicit conversion anywhere else (unless of course you need to extend the functionality from outside of the library).