Open njlr opened 1 month ago
That's awesome! I love how this looks in F#.
Let me work on this for a few days. I think I can extend the expression parser to accept the tree that F# produces.
If you want to give it a try yourself, clone the project and put a breakpoint in Jinaga/Repository/SpecificationProcessor.cs
in the GetLambda
method. Then run the unit test in Jinaga.Test/Specifications/SpecificationTest.cs
called CanSpecifySuccessors
. Once you hit that breakpoint and have a look around, you can write an F# version of the unit test. See what's different.
I'll let you know what I find.
When I got the code in the debugger, I found that the expression had the following structure:
.Call System.Linq.Queryable.Where(
.Call $facts.OfType(),
.Call Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToLambdaExpression(
.Call Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.SubstHelper(
.Constant<Microsoft.FSharp.Quotations.FSharpExpr>(NewDelegate (Func`2, x,
Call (None, op_Equality,
[PropertyGet (Some (x), Company, []), company]))),
.NewArray Microsoft.FSharp.Quotations.FSharpVar[] {
.Constant<Microsoft.FSharp.Quotations.FSharpVar>(company)
},
.NewArray System.Object[] {
(System.Object)$company
}
)
)
)
It looks like there are ways to turn F# quotations into C# expressions, but I'm not familiar with them. For example F# Quotation Evaluator. This might take a while.
Pushed branch fsharp-tests
Making progress! The following test passes:
[<Fact>]
let CanSpecifyIdentity() =
let specification = Given<Airline>.Select(
<@ Func<Airline, FactRepository, Airline>(fun airline facts -> airline) @>
)
Assert.Equal(
"""(airline: Skylane.Airline) {
} => airline
""",
specification.ToString().ReplaceLineEndings()
)
I must clarify that the Given<T>.Select
used in that test is not the one published by the C# library. I needed to express types in F# that have no C# equivalent in order to make that interface work. So I wrote a small wrapper in F#.
I hope I can get rid of the Func<TFact, FactRepository, TProjection>
syntax. Projections tend to be complex types. It would be better to let type inference figure that out.
Update pushed to the fsharp-tests
branch.
It turns out that F# will translate the Expr to Expression in this specific case. The syntax is much simplified, the projection type is inferred, and this test passes with the regular Given<T>.Select
:
[<Fact>]
let CanSpecifyIdentity() =
let specification = Given<Airline>.Select(
fun airline facts -> airline
)
Assert.Equal(
"""(airline: Skylane.Airline) {
} => airline
""",
specification.ToString().ReplaceLineEndings()
)
It appears that the core issue is that F# only translates the initial Expr, not nested Exprs. This is apparently a fairly common complaint with respect to F# and ORMs.
It looks like the direction that the F# language is taking is to use query syntax. So I think we should focus on making this syntax work:
let employeesOfCompany =
Given<Company>.Match<Employee>(
fun company facts ->
query {
for employee in facts.OfType<Employee>() do
where (employee.Company = company)
select employee
}
)
What are your thoughts?
It appears that the core issue is that F# only translates the initial Expr, not nested Exprs. This is apparently a fairly common complaint with respect to F# and ORMs.
It looks like the direction that the F# language is taking is to use query syntax. So I think we should focus on making this syntax work:
let employeesOfCompany = Given<Company>.Match<Employee>( fun company facts -> query { for employee in facts.OfType<Employee>() do where (employee.Company = company) select employee } )
What are your thoughts?
This does look nice, and it mirrors the C# version too :+1:
I tried playing with the example code:
But when I run it I hit a run-time error:
I'm not at all familiar with
Expression
so unsure where I went wrong here.Any ideas?
(You can paste this into an
fsx
file then run withdotnet fsi ./Repro.fsx
)