Polymorphic based embedding DSL: infix operators


I am trying to make a small DSL that will allow me to compose different operations based on some set of external classes/objects. The domain is machine learning. So my initial attempt resulted in:

class AlgT[]
class ModelT[
class DataT[]
class QueryT[
class LabelT[]
class ResultT[
class MetricT[_]
class Op[A, B]

object ML1 {
implicit object T1
implicit object T2

// (D->M) * ((Q,M) ->(R,M)) ~~> (D,Q) -> (R,M)
def bind[A, B, C, D](op1: Op[DataT[A], ModelT[B]],
                     op2: Op[(QueryT[C], ModelT[B]), (ResultT[D], ModelT[B])])(implicit d: T1.type)
: Op[(DataT[A], QueryT[C]), (ResultT[D], ModelT[B])] = ???

// ((D,Q) -> (R,M)) * ((L,R) -> E) ~~> (D,Q,L) -> E
def bind[A, B, C, D, E, F](op1: Op[(DataT[A], QueryT[B]), (ResultT[C], ModelT[D])],
                           op2: Op[(LabelT[E], ResultT[C]), MetricT[F]])(implicit d: T2.type)
: Op[(DataT[A], QueryT[B], LabelT[C]), MetricT[F]] = ???


Note that the implicits are their because of type erasure (bind is overloaded). So now I can do this:

val learn = new Op[DataT[Int], ModelT[Int]]
val predict = new Op[(QueryT[Int], ModelT[Int]), (ResultT[Boolean], ModelT[Int])]
val evaluate: Op[(LabelT[Boolean], ResultT[Boolean]), MetricT[Double]] = new Op[(LabelT[Boolean], ResultT[Boolean]), MetricT[Double]]
val learn_predict: Op[(DataT[Int], QueryT[Int]), (ResultT[Boolean], ModelT[Int])] = ML1.bind(learn, predict)
val learn_predict_predict = ML1.bind(learn_predict, evaluate)

So far so good. But the syntax is awful and not DSL like. So I came up with the following classes:

implicit class ML1Z[A,B](op: Op[DataT[A], ModelT[B]]) {
def bindx[C, D](op2: Op[(QueryT[C], ModelT[B]), (ResultT[D], ModelT[B])])
: Op[(DataT[A], QueryT[C]), (ResultT[D], ModelT[B])] = ???

implicit class ML2Z[A,B,C,D](op: Op[(DataT[A], QueryT[B]), (ResultT[C], ModelT[D])]) {
def bindx[E, F](op2: Op[(LabelT[E], ResultT[C]), MetricT[F]])
: Op[(DataT[A], QueryT[B], LabelT[C]), MetricT[F]] = ???


Now I can do this:

import Base._
val l_p_1 = learn bindx predict bindx evaluate

So my questions are:

  1. Is their any simpler way to this?
  2. Are their any potential problems with either of the “solutions” above?



An alternative solution, without implicit conversions, would be to add the methods directly to the Op class.

Thanks for the suggestion. It is indeed simpler.

If you’re ready for heavier language engineering, your next good bet is Scala-Virtualised [1]. There, you don’t even need to pollute the classes themselves to get the infix operator. You can define them separately…

Have fun,

[1] https://link.springer.com/article/10.1007/s10990-013-9096-9

Interesting. I had come across this before. I will read the article but I don’t think I will use it as it seems to require a specific version of the Scala compiler/environment. Appreciate the link though.

Thank you.

Note that a recent publication introduced a different approach to do something similar:
There is no Scala implementation yet but that could be a nice idea of use of macros :slight_smile:

Nice. I will be reading the article and looking at the video. Curiously I had already visited the author’s site when looking into automatic differentiation. Thanks.