#1804 Calling a Function using . Operator

andrewc Sun 4 Mar 2012

Hi all,

I'm interested in creating a DSL for quantitative analysis with Fantom. In this language users will create custom functions that operate on already defined classes. For example:

class Person
{
  Float height
  Float weight
}

// custom procedure defined after the definition of Person
bmi := |Person p -> Float| { p.weight / (p.height*p.height) } 

p := Person { height = 2f; weight = 75f } 
echo(bmi(p))

In this example Person was defined, and then we created a function that calculated the persons bmi. What I'd like to be able to do is enable users of this language to invoke bmi, using p as the first (and only in this case) parameter using the dot operator, i.e.

p.bmi

Obviously this isn't possible currently, so I was hoping I could get some hints as to how to approximate this. For example, I could imagine using Obj.trap to implement whatever dispatching I want, but I'm not sure how Obj.trap would pick up on the identifiers in the callers scope. In other words, I only want p.bmi to be valid when there is a variable with the name bmi and the value Func in scope. So my implementation of trap would have to know what variables were in scope when trap was called.

Just to reiterate, I'm not stuck on the . syntax, I'm open to anything that approximates this. I was just using the dot syntax to be illustrative of the problem I'm trying to solve.

Thanks, -Andrew

brian Sun 4 Mar 2012

I want, but I'm not sure how Obj.trap would pick up on the identifiers in the callers scope. In other words, I only want p.bmi to be valid when there is a variable with the name bmi and the value Func in scope.

If you want to calls dynamically, then trap provides a lot of flexibility:

foo->bar(a, b, c)

// calls your trap method like this
foo.trap("bar", [a, b, c])

From the point you can lookup functions using the function name and massage the parameters however you need.

Was there some specific problem with trap you are trying to work around?

dobesv Sun 4 Mar 2012

Well, what you're doing sounds a bit tricky to achieve as a Fantom DSL. The DSL has limited access to what is in scope. It does have access to information about classes, though, so if the available operations were defined as a trait or class instead of a local variable that might be more feasible.

Perhaps you could do it this way - for each operation defined, you add it to a trait named after the current function (assuming you can figure that out, currently the DslPlugin doesn't get access to that information).

Then you could wrap the object in something that uses trap() to proxy everything through to either the trait or the wrapped object. That would let you use the -> syntax to access these operations.

Although if Fantom had a syntax for constructing an object and adding a trait to it on the spot (it might, I'm not a Fantom expert yet) then why not just do it that way?

andrewc Sun 4 Mar 2012

Thanks for your replies.

@brian: How would I lookup the function using the name? For example, suppose I had this complete program.

class Person
{
  Float height
  Float weight
}

class Main
{
  Void main ()
  {
    // custom procedure defined after the definition of Person
    bmi := |Person p -> Float| { p.weight / (p.height * p.height) }

    p := Person { height = 2f; weight = 55f }
    echo(bmi(p))
    // would rather this look like
    // echo(p->bmi)
  }
}

How would I implement Person.trap to allow the last commented line,'echo(p->bmi)'? Maybe it's quite straightforward, I'm very new to Fantom, but it looks like you would need a way to reach back into the scope of the the caller of trap(...).

@dobesv: Yeah, I didn't mean to hint that a Fantom DSL would be a good way to solve this. Your approach sounds very similar to Scala's approach to adding methods to classes after their definition ;-) Without implicit converters, though, I don't know that it will be so transparent the end user of the trait/function.

KevinKelley Sun 4 Mar 2012

I've wondered before if there'd be a use for reflection access to these "loose" closure functions...

We've got Pod.types, Type.slots, (also fields and methods); so you can reflect your way through the "structure" of a program, discovering what's there. But unless they get assigned to a member variable, closures stay hidden.

They live somewhere, implementation-wise they're basically a method of an hidden implementation class...

We have sys::Func so that if you have a reference to one, you can find out what it is: its params and returns. You could probably implement some form of multimethods, even, by overloading trap to check the params it was called with and dispatch based on whatever strategy you like.

But there's no way to ask for all the Funcs defined in this pod, or this type, or method. Not saying that would even make sense -- a closure isn't necessarily like a method, in that it may close over some local vars and therefore not be usable except after having been created by its static context...

Maybe there's a way of breaking it down, by what's closed-over: there's

  • dynamic ones that use locals from their enclosing scope;
  • member ones that refer to fields of an enclosing instance
  • static closures that are free, have no capture of vars

Maybe it'd be cool to be able to call a static Func[] Func::list() method, and get hold of all of them. Add a couple introspection methods on Func to ask about whether it's free or has captures...

Anyway that's just early-morning rambling. Caffeine's kicking in but brain's not quite in gear.

@andrewc, not sure quite what you're after, but likely you can do something interesting by overriding with in your Person class, then maybe making a registry of, say, Str:Func, for the closures you want to be able to define. In your with method, look up by name and call it...

brian Sun 4 Mar 2012

@andrewc

We do something similar to create a "reflective" function library. However we don't use closures, rather we use normal static methods which then gives us all the flexiblity we need for reflection. Here is a simple example:

class Main
{
  static Void main() { echo(Person()->bmi) }
}

class Person
{
  override Obj? trap(Str name, Obj?[]? args := null)
  {
    m := PersonLib#.method(name, false)
    if (m == null || !m.isStatic || !m.hasFacet(Fn#)) throw UnknownSlotErr(name)
    args = args == null ? [this] : args.insert(0, this)
    return m.callList(args)
  }

  Float height := 10f
  Float weight := 20f
}

class PersonLib
{
  @Fn static Float bmi(Person p) { p.weight / (p.height * p.height) }
}

facet class Fn {}

We create our library functions using static methods tagged with a facet. In this case the Person object has hardcoded its dependency on PersonLib, but in practice we make the library types discoverable at runtime using indexed props.

andrewc Mon 5 Mar 2012

@brian

Yeah, that makes sense, but it's not exactly the same as what I'm hoping for. The key difference is the scope of bmi. In your solution (implemented with indexed props) the scope is global for the VM. Ideally bmi is something that's scoped locally.

Two possible alternatives come to mind: i) I could imagine using a ThreadLocal to keep a Stack of "registered" functions. The stack would follow the execution stack (i.e. add and remove frames as functions are called and return), and any Functions registered in the current stack frame would be made available in Person.trap(). But this solution requires the beginning and ending of each method to manage the Stack (i.e. add a frame and register functions, and pop frames).

ii) I could live with the global scope of index props, and then provide some mechanism for users to disambiguate between Functions with the same name. In our system users can write functions with the same name. We can force them to be in separate Pods, but if both Pods are loaded, my understanding is that index props won't be able to distinguish a::Bmi from b::Bmi. I looked at the grammar, and it doesn't seem like there would be a way I could distinguish the two in my dynamic call. i.e. I can't do person->a::Bmi.

<dynCall>        :=  "->" <slotExpr>

and slotExpr always begins with a &id or an id. Not surprised by this at all, but would have been… something anyway ;-)

So, where does this leave me.

I could use indexed props, and come up with a way to pick a Function in the case where there are multiple with the same name. But I need the mechanism for disambiguating to be local. It's really important that users can specify which bmi() they mean to use very close to the call of bmi().

I could abandon this chase and just tell people to use Functions in their native form:

bmi(p)

Thoughts? -Andrew

brian Mon 5 Mar 2012

I'm not sure I entirely follow, but sounds like maybe you can live with how "->" works and use ThreadLocals or indexed props right?

If so, then you could potentially solve your scope ambiguity a couple of ways:

// this resolves to alpha::bmi and beta::bmi
foo->bmi(...)

// use some naming convention with valid identifiers to scope
foo->alpha_bmi(...)

// or use chained dynamic invokes
foo->alpha->bmi(...)

Or maybe force a Fantom call to select the scope:

scope.use("alpha")
person->bmi(...)

andrewc Mon 5 Mar 2012

I like the idea of chained dynamics. Less enthusiastic about the underscore convention, because I don't want to force semantics that the grammar isn't helping with. Speaking of the grammar, the page doesn't define "<str>" in

<podSpec>         :=  <id> | <str> | <ffiPodSpec>

=)

Thanks for your thoughts,

-Andrew

Login or Signup to reply.