FSharp.Compiler.PortaCode
FSharp.Compiler.PortaCode copied to clipboard
Question about the proper way to resolve functions
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?
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)
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 :)
Anyway, closing this because I've figured it out.
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?
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.
Hmm that should be in the members after AddDecls
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:
EDIT: here's the full-expansion of the paramter types: