fsprojects / FSharp.Compiler.PortaCode

The PortaCode F# code format and corresponding interpreter. Used by Fabulous and others.
Other
42 stars 11 forks source link

Question about the proper way to resolve functions #9

Open baronfel opened 5 years ago

baronfel commented 5 years ago

Hi, I was working on incorporating this into a Giraffe extension to allow for hot-reload of giraffe views and I was able to get an MVP going very quickly thanks to the examples over on the Fabulous repo. As it is, I've been very successful finding a value of a given name and testing if it meets certain types and coercing that into a shape I need to make a Giraffe HttpHandler. What I'm having trouble with is a more general mechanism to resolve an HttpHandler.

What I'd like to be able to resolve, at minimum, are:

For this second case I've had a hard time going from FSharp.Compiler.PortaCode.Interpreter+MethodLambdaValue to some kind of one-level-higher form of the member. Do you have any pointers or resources I can look at to help?

dsyme commented 5 years ago

Do these help, from Fabulous.LiveUpdate in the Fabulous repo. You could add a tryFindMemberByType

These could be helpers in PortaCode.

    let rec tryFindEntityByName name (decls: DDecl[]) = 
        decls |> Array.tryPick (function 
            | DDeclEntity (entityDef, subDecls) -> if entityDef.Name = name then Some (entityDef, subDecls) else tryFindEntityByName name subDecls 
            | _ -> None)

    let rec tryFindMemberByName name (decls: DDecl[]) = 
        decls |> Array.tryPick (function 
            | DDeclEntity (_, ds) -> tryFindMemberByName name ds 
            | DDeclMember (membDef, body) -> if membDef.Name = name then Some (membDef, body) else None
            | _ -> None)
baronfel commented 5 years ago

I managed to figure this out, but it was not simple.

I couldn't use EvalContext.GetExprDeclResult because my member wasn't in the members dictionary for some reason (despite Add- and Eval-ing the decls from the DFiles), so I had to reach for EvalContext.ResolveMethod, get the UMethod for the method, and if the UMethod's Value was a MethodLambdaValue, construct the correct parameters array from information on the DMemberDef to invoke the pre-compiled lambda.

The process of constructing the correct parameters was a bit error prone, I had to do a lot of manual type traversal to fill in generic parameters. Not hard, but it took me a second to really understand the API representation of the methods. What I came up with is here for reference.

But whew, now it works! Users can specify a function of the form webApp: dep1 -> dep2 -> dep3 -> ... -> depN -> HttpHandler and have the deps auto-resolved from the ASP.Net Dependency injection framework.

This is so cool, I'm having so much fun playing with this :)

baronfel commented 5 years ago

Anyway, closing this because I've figured it out.

dsyme commented 5 years ago

I couldn't use EvalContext.GetExprDeclResult because my member wasn't in the members dictionary for some reason (despite Add- and Eval-ing the decls from the DFiles),

Eagerly evaluated values aren't in the members dictionary - only code. Could you give a fragment of the source code?

baronfel commented 5 years ago

Sure, the test webapp I'm using to try to find different kinds of 'webApp' members is defined here and the main find-and-evaluate-member logic I'm using in my update handler is defined here.

dsyme commented 5 years ago

Hmm that should be in the members after AddDecls

baronfel commented 5 years ago

I did a bit more digging, and the functions are in fact in the members after AddDecls. The problem is that the key that's used for storage into the members dict hardcodes a RTypes [||] as the third portion of the key, but the functions added as members have UType arrays in that position. So we can resolve functions that are bound as values(let foo = fun a b -> ...) from GetExprDeclResult, but not functions defined as, well, functions!

Given this code:

module HotReload.Client.Main

type Model =
    {
        value: int
    }

type Message =
    | Increment
    | Decrement

let mutable update = fun (message : Message) (model : Model) ->
    match message with
    | Increment -> { model with value = model.value + 1 }
    | Decrement -> { model with value = model.value - 1 }

we get this key: image

EDIT: here's the full-expansion of the paramter types: image