Omid
Omid

Reputation: 1989

Runtime avoiding asInstanceOf

Here is a simplified version of a design that is trying to compose function in runtime. So the pipelineS comes in the runtime (in form of json or something) and the traverse executes it. Also, we know that the runtime input WILL be a valid composition(functions in/out types matches).

I want to define the functions that have type information and avoid asInstanceOF.

One partial solution I see is Here but it's very complicated and it's hard to expand to different size of input arguments.

abstract class Func extends Product {
  val meta:FuncMeta
}

case class FuncMeta(name:String, outType:String, inTypes:List[String])
case class Fun0(meta:FuncMeta, run:() => Any) extends Func
case class Fun1(meta:FuncMeta, run:Any => Any) extends Func
case class Fun2(meta:FuncMeta, run:(Any, Any) => Any) extends Func

val literal2 = Fun0(FuncMeta("literal2", "int", List.empty), () => 2)
val literal10 = Fun0(FuncMeta("literal10", "int", List.empty), () => 10)
val twice = Fun1(FuncMeta("twice", "int", "int" :: Nil) ,(a:Any) => a.asInstanceOf[Int] * 2)
val larger = Fun2(FuncMeta("larger", "bool", "int" :: "int" :: Nil) ,(a:Any, b:Any) => a.asInstanceOf[Int] > b.asInstanceOf[Int])
val add = Fun2(FuncMeta("add", "int", "int" :: Nil), (a:Any, b:Any) => a.asInstanceOf[Int] + b.asInstanceOf[Int])

//a Map[String, Func] for runtime access and retrieve of functions
//Basically this is the way the Functions are stored
val funcs = List(literal2, literal10, twice, larger, add).map(x => x.meta.name -> x).toMap

def traverse(tree: Treee[Func]):Any = {
  val t = tree.t
  val kids = tree.kids

  val rs = kids.map(k => traverse(k))
  t match {
    case Fun0(meta, run) => run()
    case Fun1(meta, run) => run(rs.head)
    case Fun2(meta, run) => run(rs(0), rs(1))
  }
}

//RUNTIME information
//can be a userinput Json that gets converted to the following by accessing the funcs Map
val pipelineS = Treee[Func](
    funcs("larger"),
    List(
        Treee(funcs("literal10")),
        Treee(funcs("literal2"))
      )
  )

println(traverse(pipelineS))

Upvotes: 0

Views: 115

Answers (2)

simpadjo
simpadjo

Reputation: 4017

Don't think there is an easy solutions if you want to avoid type casts during function execution.

If I really had to do it I'd start like that:

1) Start with functions of one argument. FunctionMeta[I, O] must be typed with the input type and the output type. Type casts would happen when you parse your json into FunctionMeta[I, O], not when you run it.

2) Use type-aligned data structures to store the sequence of functions as proposed in the discussion you linked.

3) After you make functions of 1 argument work - model functions of multiple arguments as functions from HList.

Upvotes: 0

Well, you can create a typed encoding like this one:

sealed trait Term {
  type T
  def run(): T
}

object Term {
  type Aux[_T] = Term { type T = _T }
}

final case class Lit[A] (value: A) extends Term {
  override final type T = A
  override final def run(): T = value
}

final case class Tuple[A, B](a: Term.Aux[A], b: Term.Aux[B]) extends Term {
  override final type T = (A, B)
  override final def run(): T = (a.run(), b.run())
}

final case class Fun[I, O](input: Term.Aux[I])(f: I => O) extends Term {
  override final type T = O
  override final def run(): T = f(input.run())
}

Which can be used like this:

val expr: Term = Fun(Tuple(Lit(2), Fun(Lit(3))(_ * 5))) {
  case (a, b) => a + b
}

expr.run()
// res: Term.T = 17

The problem would be how to create such expression from your user input.

Upvotes: 1

Related Questions