Open bartelink opened 1 month ago
I realize this is horrendously incomplete, but I didn't find the condition to be easy to search for, and saying nothing is not ideal either.
If it was me fixing as a quick win without root causing / understanding the required interfaces/moving parts more deeply, I'd make Seq.empty
delegate to Array.empty
(which is my actual workaround for now), but obviously there's plenty to be won from an optimal implementation too.
Here's hoping I eventually get to the point of circling back with a trimmed down repro that identifies whether this is a JSON.NET problem, a JsonConverter+JSON.NET problem, or only solvable by implementing a relevant interface on EmptyEnumerable
Interesting:
> let es: seq<int> = [];;
val es: int seq = []
> let es2: seq<int> = Seq.empty;;
val es2: int seq
> es;;
val it: int seq = []
> es2;;
val it: int seq = seq []
Yeah, it'd definitely be a more widespread problem if it was something fundamental. There are a lot of potential things in play with ObjectResult, JSON.NET and JsonConverters in the picture - JSON.NET covers a heck of a lot of scenarios and has plenty idiosyncracies so not ruling anything out. The biggest thing I can do to narrow it down is use Enumerable.Empty instead in my context and see whether it happens to work correctly, but this unfortunately hasn't made it to the top of the TODO list atm....
I don't think it is in aspnetcore.
> let es: seq<int> = [];;
val es: int seq = []
> let es2: seq<int> = Seq.empty;;
val es2: int seq
> printfn $"{es}";;
[]
val it: unit = ()
> printfn $"{es2}";;
Microsoft.FSharp.Collections.IEnumerator+EmptyEnumerable`1[System.Int32]
val it: unit = ()
But tbh I find EmptyEnumerable
more correct than []
.
I'm not ruling anything in or out
In my context, []
is much more 'correct' than a sequence mapping to a string
JSON value
The specific reasons why fsi will render in a given manner are definitely something that's interesting and relevant, but for me isn't an open and shut answer either.
> printfn $"{System.Linq.Enumerable.Empty<unit>()}"
System.Linq.EmptyPartition`1[Microsoft.FSharp.Core.Unit]
(But I have yet to test how that renders either - All I know for a fact is that Array.empty
renders equivalent to the wrapper that Seq.choose
yields)
F# type checking infers es
in the above example to be of type int list
and es2
to be of type ÈmptyEnumerable
.
I don't think there is anything wrong with it.
I guess that in the case that you mention above, using Seq.choose
, there is a reason why type checking infers a list or array.
Yeah, it's not entirely/only about the static type from the perspective of the compiler though - ultimately ObjectResult
is not strongly typed (the data is an obj
), so it's about the interfaces and other aspects of the wrapper type; that's what dictates how JSON.NET and/or AspNetCore's wiring ends up rendering the result. (Seq.empty casts the internalt wrapper type to seq<'t>
, but that turns out not to be enough)
Seq.empty<MyType>
renders as the string"EmptyEnumerable"
when fed through AspNetCore 8ObjectResult
The same does not happen for
Seq.choose
and other such functions when they yield an empty sequence, as they yield a different internal impl type wrapping their output, which in my context winds up producing[]
as requiredExpected behavior
render as
[]
Actual behavior
renders as
"EmptyEnumerable"
Known workarounds
use
Array.empty<MyType>
insteadRelated information
MyType
has a JsonConverter registeredSystem.Linq.Enumerable.Empty<MyType>()