[SOLVED] Weird design problem, looking for advice

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 :sweat_smile:

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

Good idea, worth a try!

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