BED-Con talk Common runtime library Generate glue code using Scala macros

Generate glue code using Scala macros

The problem

Regular scala functions and PHP functions do not fit well which each other:

  • Scala functions are strictly typed while PHP functions are usually untyped or “not completely” typed.
    • Since PHP 5 it is possible to declare type hints for function parameters, but this only applies for classes, interfaces, arrays and callables. There is no way to distinguished a string parameter from an integer parameter.
  • PHP functions support call-by references, while scala functions usually take immutable parameters.
    • Of course it is possible to use “holder” objects for results, but his is not considered clean code.
  • In scala the parameter match of a function call is always evaluated at compile time, in PHP it is evaluated at interpretation time.

Example

At the moment the PHP function trait in JBJ looks like this:

1 trait PFunction {
2   def name: NamespaceName
3 
4   def parameters: Seq[PParamDef]
5 
6   def call(parameters: List[PParam])(implicit callerCtx: Context): PAny
7 }
  • name is the name of the function as it should be used in a function call. Since the function be defined in a namespace this is a NamespaceName instead of just a string.
  • parameters list of parameter definitions including type hints if present
  • call the actual invocation of the function with concrete parameters. PParam serves as a wrapper if the parameter was given as concrete value of by reference.

Consider one wants to implement the buildin function strncmp. It would be quite inconvenient to implement the PFunction trait itself, instead one would like to just implement it as regular scala function like this

1 object StringFunctions {
2   def strncmp(str1: String, str2: String, len: Int): Int = {
3     str1.take(len).compareTo(str2.take(len))
4   }
5 }

First we need a generic helper to extract parameters with PHP compatible error handling.

1 trait ParameterAdapter[T] {
2   def adapt(parameters: Iterator[PParam])(implicit ctx: Context): T
3 }
  • parameters is the iterator of the parameters that need to be processed. The adapter is supposed to take one or more parameters from the iterator (or throw some error)
  • The result is a tuple of the extracted scala value and a list of remaining parameters to be forwarded to the next ParameterAdapter.

Now we need some glue code to convert this function to a PFunction usable by the interpreted PHP, which might look like this:

 1 new PFunction {
 2   def name = NamespaceName("strncmp")
 3 
 4   def parameters = Seq(
 5     AdaptedParamDef("str1", None, false, None),
 6     AdaptedParamDef("str2", None, false, None),
 7     AdaptedParamDef("len", None, false, None)
 8   )
 9 
10   private val errorHandlers = ParameterAdapter.errorHandlers("strncmp", ParameterMode.EXACTLY_WARN, 3, 3, false, NullVal)
11 
12   private val adapters = (
13     RelaxedParamterAdapter(0, StringConverter, errorHandlers),
14     RelaxedParamterAdapter(1, StringConverter, errorHandlers),
15     RelaxedParamterAdapter(2, IntConverter, errorHandlers)
16   )
17 
18   def doCall(parameters: List[PParam])(implicit callerCtx: Context): PAny = {
19     if (parameters.size > 3)
20       errorHandlers.tooManyParameters(parameters.size)
21 
22     val parametersIt = parameters.iterator
23     val param1 = adapters._1.adapt(parametersIt)
24     val param2 = adapters._2.adapt(parametersIt)
25     val param3 = adapters._3.adapt(parametersIt)
26     val result = StringFunctions.strncmp(param1, param2, param3)
27     PValConverter.toJbj(result)(callerCtx)
28   }
29 }

Of course it is extremely cumbersome to write all this by hand for each function. I.e. one would like to have a function like this:

1   def generatePFunctions(instance: Any): Seq[PFunction]
  • instance is the instance/object to be scanned for scala functions, which should be decorated by a PFunction as shown above.

The usual Java-approach would be to “generate” this kind of glue-code via reflection and (potentially) AOP. In Scala it is possible to use type-safe marcos for this kind of work.