Polymorphic based embedding DSL: infix operators


#1

Hello,

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)
println(learn_predict)
val learn_predict_predict = ML1.bind(learn_predict, evaluate)
println(learn_predict_predict)

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
println(l_p_1)

So my questions are:

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

TIA,
HF


#2

Hi,

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


#3

Thanks for the suggestion. It is indeed simpler.


#4

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,
–Hossein

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


#5

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.


#6

Note that a recent publication introduced a different approach to do something similar:
http://conal.net/papers/compiling-to-categories/
There is no Scala implementation yet but that could be a nice idea of use of macros :slight_smile:


#7

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.