Feature Request : onMissiingMethod : event handler

Hi there,
I’d like to ask for the addition of an “onMissingMethod” event handler.

If you call a method on a class and it doesn’t exist - it looks for a method called “onMissingMethod()” - within the target class.

I have used it before (in ColdFusion) as a proxy, in a database class, where the application had a “service” layer (the public API) and a gateway / DAO which was private.

There is some convention at play with my example, CLASS_service.scala and CLASS_dao.scala.
CLASS_service.scala is the public API - but the method signature NEARLY ALWAYS matches the DAO’s - so it ends up being a lot if duplicate / boiler plate code.

At its simplest - the CLASS_service.scala ONLY has onMIssingMethod defined,
This takes the method name and the arguments (of the missing method, just called), as default arguments.
And I use it to proxy the original method call through CLASS_dao.OrigMethodName(OrigArguments),

(in my example use-case) CLASS_dao is dynamically produced by the name of the CLASS_service originally called : matching on CLASS_ as “my” convention.

Ultimately, meaning I don’t have to code the public API, just to fulfill the application design.

It can be used for all sorts of things, logging / error- handling / common code from multiple places - though I’ve only used it as a proxy.

Take a look at scala.Dynamic. (I would use it very cautiously, if at all, though.)

1 Like

Underscoring @sangamon’s point: while this sort of “missing method” handler is common in many languages, it mostly makes sense for dynamically typed languages, not compiled, statically-typed ones.

That’s for good reason: Scala code is, in general, all being resolved at compile-time. But this “missing method” approach tends to be runtime-centric. So it really only makes sense if you do a deliberate end-run around compilation, using scala.Dynamic (or the capabilities coming in Scala 3).

That said, you can achieve similar goals in Scala, you just usually do it in different ways – usually involving a layer that is automatically created and compiled using macros, Shapeless, typeclasses or things like that. That’s very common – for example, most JSON serialization is done with automatically-generated code. So long as you keep your data structures well-formed (focusing on standards collections and case classes), you can do an enormous amount of magic this way. But it does require structuring the program a bit differently…

The OP asked for an EventHandler, which sounds like a runtime check. Type classes are resolved at compile time, and I think same is true for scala.Dynamic.

Usually, it is not possible to call a method, unless the compiler can prove that the object is of a type that has that method, so the whole idea of calling a method and considering the option that it may not exist doesn’t make sense.

(Well, you can fool the compiler by providing a version of a class that has a method while compiling, and then provide a different version that doesn’t have that method while running it, which would lead to a NoSuchMethodException if you try to call the method, which you can catch, but I guess this is not the use case you had in mind)

To really call methods with arbitrary names provided at runtime, you need to use reflection.

But, I think you really want something much simpler. Probably, whatever string you wanted to use as a method name, you want to use as an argument instead.

That’s actually exactly what Dynamic does.

1 Like

No, it really does runtime “resolution”.

Right, reflection would have to be thrown into the mix, as well.

If I understand correctly, the OP wants to do something like this:

trait DAO {
  def getUserInternal(id: Int): User
}

class DynamicService(dao: DAO) extends Dynamic {
  def applyDynamic(m: String)(args: Any*): Any = {
    dao.getClass.getMethods.find(_.getName == s"${m}Internal")
      .fold(throw new NoSuchMethodException(m))(_.invoke(dao, args:_*))
  }
}

val dynSrv = new DynamicService(dao)
dynSrv.getUser(42)

…which basically ticks off all possible marks on the “bad idea” list. (At least as far as Scala is concerned - perhaps Rubyists would feel different.) In hindsight, my first answer still was far from being discouraging enough. :slight_smile:

In general I’d agree, but at first glance I don’t see any application of these for this specific use case. All these techniques would require some static service API the client could code against - but writing this up is exactly what the OP wants to avoid. (But I may misunderstand the use case, or I may be missing some Scala technique to achieve this effect - I’d be somewhat surprised in the latter case, though.)

I agree that there isn’t a good way to do exactly what is being requested - I’m saying that there are low-boilerplate ways to do things like logging, serialization, database access and suchlike in Scala. But you need to structure your code differently to accomplish them - idiomatic Scala handles stuff like this with derived typeclass instances rather than inheritance, generally.

Granted, this only helps if you have some control over the APIs that you’re coding against - if you are required to do things in an inheritance-centric way, there isn’t a great answer.

So it sort of comes down to the meaning of “use case”. There are good Scala ways to solve these sorts of problems, but not the same ways, so context matters…

1 Like

This is pretty much what I was planning - and Coldfusion is a dynamically typed, runtime compiled language ; where I used it previously.

Scala.Dynamic - looks like I could use it for the use-case I have in mind.
But if it’s truly a Scala anti-pattern, I am genuinely happy to accept that I “shouldn’t” use it, for this use-case.

If it is correct in Scala to (by hand) duplicate the method is the service and the DAO, so be it.

I’d say that it truly is an anti-pattern. In Scala, it’s (almost) all about types, and using Dynamic/reflection, you are going around the system. I don’t see a nice way to avoid writing down the API for both service and DAO. However, as @jducoeur pointed out, there’s a variety of mechanisms that’ll help reducing actual implementation boilerplate.

Even using a dynamic language, I’d probably be wary of this approach. First, the service is the public-facing facade of the system, that’s where having a properly codified API seems most important, if only for documentation purposes. Second, you already concede that the APIs only match “nearly always”, and I’d think it rather likely that they’ll tend to deviate further as development/maintenance progresses, which will work towards reducing the benefits of this pattern over time while increasing the cost (through a growing number of special cases).