I have a lot of functions that take the same kind of implicit parameter, something like:
// these can also call other defs using Stuff, so it's passed further down
def spam(x: Int)(using Stuff) = ???
def ham(y: String)(using Stuff) = ???
def eggs(using Stuff) = ???
// ... many more
There isn’t a clear hierarchical relationship (like class/trait) between the functions; they are just roughly “conceptually grouped together” if that makes sense. (The kind of stuff you would put in a singleton object to call.)
It’s not a big deal, but (using Stuff) has to be plastered everywhere like 50 times, so it’s a bit annoying 
I’d like to have something like a “singleton object that accepts an implicit parameter”:
object Spam(using Stuff):
def spam(x: Int) = ???
def ham(y: String) = ???
def eggs = ???
// ... many more
I can place them in a normal class, and that works fine I guess… the issue is that it obscures the intent; there really should be just one instance of such a class.
I guess the answer, like the answer to everything, is… typeclass?
1 Like
Can you make them all extension methods?
extension (m: Matrix[Double])(using BoundsCheck:BoundsCheck)
for me, that given is then available in all the defs?
object Spam
extension(Spam.type)(using Jam)
def spam(x: Int) = ???
def ham(y: String) = ???
def eggs = ???
Or something (I haven’t compiled this exact exapmle)
6 Likes
It’s working out pretty well so far, compiles, and tests are green. I’ll try to use it across the rest of my project, let’s see how it goes! Thanks @Quafadas (Spam.type would have never occurred to me!)
1 Like
This approach worked well, but at the end it became a bit convoluted (especially with how extension importing works, and extensions can’t be bulk exported I think); I opted for the normal class approach instead (even though the intent is obscured a bit, and there is some unnecessary instance-ing). I’m still using the extension approach in a few places, really helps clean things up!
You can put the extension in the implicit scope, which in case of an object is also the object itself:
scala> object stuff:
| extension(unused: stuff.type)(using Stuff)
| def spam(x: Int) = x + 1
| def ham(y: String) = y + " with ham"
| def eggs = "eggs"
|
// defined object stuff
scala> stuff.spam(4)
val res1: Int = 5
scala> stuff.ham("spaghetti")
val res2: String = spaghetti with ham
Though I found one issue with this approach:
scala> stuff.eggs
val res3: stuff.type => String = Lambda/0x0000000800670800@4d71ec5b
The compiler thinks I want to call the “non-extension” version of the eggs method. But maybe there’s another trick to work around that as well…
2 Likes
There you go! It’s a little less neat but maybe still acceptable.
scala> class stuff:
| extension(unused: stuff.type)(using Stuff)
| def spam(x: Int) = x + 1
| def ham(y: String) = y + " with ham"
| def eggs = "eggs"
|
| object stuff:
| given stuff = new stuff
|
// defined class stuff
// defined object stuff
scala> stuff.ham(stuff.eggs)
val res6: String = eggs with ham
2 Likes
Ah right, that does work.
My issue was that there were quite a lot of extension methods and I had to split them into separate files, so I could not place them inside the same object… Another issue was that I had a lot of vals: extensions require changing vals to defs, therefore re-evaluation.
I am using this approach for the smaller, more localized cases though. Still very helpful.
I blogged about it here.
2 Likes