Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seq.empty renders as "EmptyEnumerable" #17864

Open
bartelink opened this issue Oct 10, 2024 · 7 comments
Open

Seq.empty renders as "EmptyEnumerable" #17864

bartelink opened this issue Oct 10, 2024 · 7 comments

Comments

@bartelink
Copy link

bartelink commented Oct 10, 2024

Seq.empty<MyType> renders as the string "EmptyEnumerable" when fed through AspNetCore 8 ObjectResult

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 required

Expected behavior

render as []

Actual behavior

renders as "EmptyEnumerable"

Known workarounds

use Array.empty<MyType> instead

Related information

  • happen to be using JSON.NET and a random mix of middlewares and don't have time to root cause deeper, but it surprised me
  • MyType has a JsonConverter registered
  • Did not yet try with System.Linq.Enumerable.Empty<MyType>()
@github-actions github-actions bot added this to the Backlog milestone Oct 10, 2024
@bartelink
Copy link
Author

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

@Martin521
Copy link
Contributor

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 []

@bartelink
Copy link
Author

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....

@Martin521
Copy link
Contributor

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 [].

@bartelink
Copy link
Author

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)

@Martin521
Copy link
Contributor

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.

@bartelink
Copy link
Author

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: New
Development

No branches or pull requests

2 participants