Skip to content

Commit

Permalink
FSharp Quotations Evaluator to 2.1.0 (#4696)
Browse files Browse the repository at this point in the history
* FSharp Quotations Evaluator to 2.1.0

* ConstantExpression

* tweak constant expression

* use built-in expression traversal

* add support for evaluating non-constant constructor parameters in quoted expressions

Co-authored-by: Chet Husk <chusk3@gmail.com>
Co-authored-by: Aaron Stannard <aaron@petabridge.com>
  • Loading branch information
3 people committed Jan 4, 2021
1 parent 365989b commit abe36b9
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 12 deletions.
39 changes: 37 additions & 2 deletions src/core/Akka.FSharp.Tests/ApiTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,49 @@ type TestActor() =

override x.OnReceive msg = ()

[<Fact>]
let ``can spawn actor from expression`` () =
type TestActorWithArgs(arg1, arg2, arg3) =
inherit UntypedActor()
do
ignore (arg1, arg2, arg3)

override x.OnReceive msg = ()


[<Fact>]
let ``can spawn simple actor from expression`` () =
let system = Configuration.load() |> System.create "test"
let actor = spawnObj system "test-actor" <@ fun () -> TestActor() @>
()

[<Fact>]
let ``can spawn actor with constant args from expression`` () =
let system = Configuration.load() |> System.create "test"
let actor = spawnObj system "test-actor" <@ fun () -> TestActorWithArgs(box 1, box true, box "yo") @>
()

[<Fact>]
let ``can spawn actor with captured args from expression`` () =
let system = Configuration.load() |> System.create "test"
let arg1 = 1
let arg2 = true
let arg3 = "yo"
let actor = spawnObj system "test-actor" <@ fun () -> TestActorWithArgs(box arg1, box arg2, box arg3) @>
()

[<Fact>]
let ``cannot spawn actor with simple expr args from expression`` () =
let system = Configuration.load() |> System.create "test"
// this formulation is supported in FsApi's expression evaluator, however the checks in Props.Create
// do not support this, so we test that we can evaluate this but not actually run it, as a proof of concept
Assert.Throws<InvalidCastException>(fun () ->
let actor = spawnObj system "test-actor" <@ fun () ->
let arg1 = 1
let arg2 = true
let arg3 = "yo"
TestActorWithArgs(box arg1, box arg2, box arg3)
@>
()
)

//[<Fact>]
// FAILS
Expand Down
2 changes: 1 addition & 1 deletion src/core/Akka.FSharp/Akka.FSharp.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Quotations.Evaluator" Version="1.1.3" />
<PackageReference Include="FSharp.Quotations.Evaluator" Version="2.1.0" />
<PackageReference Include="FsPickler" Version="5.3.2" />
<PackageReference Include="FSharp.Core" Version="5.0.0" />
</ItemGroup>
Expand Down
54 changes: 45 additions & 9 deletions src/core/Akka.FSharp/FsApi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -363,26 +363,62 @@ module Linq =
| :? MethodCallExpression as c -> Some(c.Object, c.Method, c.Arguments)
| _ -> None

let (|Constant|_|) (e : Expression) =
match e with
| :? ConstantExpression as c -> Some(c.Value, c.Type)
| _ -> None

let (|Method|) (e : System.Reflection.MethodInfo) = e.Name

let (|Invoke|_|) =
function
| Call(o, Method("Invoke"), _) -> Some o
| _ -> None

let (|Ar|) (p : System.Collections.ObjectModel.ReadOnlyCollection<Expression>) = Array.ofSeq p
let (|Ar|) (p : System.Collections.ObjectModel.ReadOnlyCollection<'t>) = Array.ofSeq p

let (|P|_|) (p: Expression) = match p with | :? ParameterExpression as p -> Some(p.Name, p.Type) | _ -> None

let (|UnitVar|_|) (v: Quotations.Var) = if v.Type = typeof<unit> then Some () else None

type VarEnv = Map<Var, Expression>

let toBCLExpression<'Actor> (f: Quotations.Expr) =
let rec inner env (e: Quotations.Expr) =
match e with
| Patterns.Lambda(UnitVar, body) ->
let innerExpr = inner env body
Expression.Lambda(innerExpr, [||]) :> Expression
| Patterns.NewObject(ctor, parameters) ->
let parameters = parameters |> List.map (inner env) |> Array.ofList
Expression.New(ctor, parameters) :> Expression
| Patterns.Value(v, ty) ->
Expression.Constant v :> Expression
| Patterns.Call(Some instance, meth, args) ->
let instance = inner env instance
let args = args |> List.map (inner env) |> Array.ofList
Expression.Call(instance, meth, args) :> Expression
| Patterns.Call(None, meth, args) ->
let args = args |> List.map (inner env) |> Array.ofList
Expression.Call(meth, args) :> Expression
| Patterns.Let(variable, binding, subsequent) ->
let thisLetVar = Expression.Variable(variable.Type, variable.Name)
let computeThisLetVarValue = inner env binding
let assignment = Expression.Assign(thisLetVar, computeThisLetVarValue) :> Expression
let env = env |> Map.add variable (thisLetVar :> Expression)
let others = inner env subsequent
Expression.Block([|thisLetVar|], [|assignment; others|]) :> Expression
| Patterns.Var var ->
env |> Map.find var
| e ->
failwithf "Unknown expression %A" e

inner Map.empty f :?> Expression<Func<'Actor>>

let toExpression<'Actor>(f : System.Linq.Expressions.Expression) =
match f with
| Lambda(_, (Call(null, Method "ToFSharpFunc", Ar [| Lambda(_, p) |])))
| Call(null, Method "ToFSharpFunc", Ar [| Lambda(_, p) |]) ->
Expression.Lambda(p, [||]) :?> System.Linq.Expressions.Expression<System.Func<'Actor>>
| _ -> failwith "Doesn't match"

type Expression =
static member ToExpression(f : System.Linq.Expressions.Expression<System.Func<FunActor<'Message, 'v>>>) = f
static member ToExpression<'Actor>(f : Quotations.Expr<(unit -> 'Actor)>) =
toExpression<'Actor> (QuotationEvaluator.ToLinqExpression f)
toBCLExpression<'Actor> f

[<RequireQualifiedAccess>]
module Configuration =
Expand Down

0 comments on commit abe36b9

Please sign in to comment.