FSharp.Compiler.PortaCode icon indicating copy to clipboard operation
FSharp.Compiler.PortaCode copied to clipboard

Question about the proper way to resolve functions

Open baronfel opened this issue 6 years ago • 7 comments

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:

  • value named XXXX of type HttpHandler
  • value/member named XXXX of some F# function type whose final return type is HttpHandler. For these I would like to be able to determine the types of the parameters so I can retrieve them via the Asp.Net Core dependency injection mechanisms and then apply them to the actual function.

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?

baronfel avatar Feb 17 '19 00:02 baronfel

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)

dsyme avatar Feb 18 '19 22:02 dsyme

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 avatar Feb 20 '19 03:02 baronfel

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

baronfel avatar Feb 20 '19 03:02 baronfel

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?

dsyme avatar Feb 20 '19 14:02 dsyme

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.

baronfel avatar Feb 20 '19 16:02 baronfel

Hmm that should be in the members after AddDecls

dsyme avatar Feb 20 '19 18:02 dsyme

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

baronfel avatar Mar 30 '19 17:03 baronfel