fsprojects / FSharpPlus

Extensions for F#
https://fsprojects.github.io/FSharpPlus
Apache License 2.0
849 stars 95 forks source link

Type inference problem with sequence #341

Closed nikolamilekic closed 5 months ago

nikolamilekic commented 4 years ago

Description

sequence does not work as expected due to a problem with type inference (compiler bug?).

Repro steps

open FSharpPlus
open FSharpPlus.Data

let parseProjectAndSprintId (input : string)
    : StateT<float, Result<_, string>> = monad {

    let parsed = input.Split('/')
    if parsed.Length = 2 then
        return int parsed.[0], int parsed.[1]
    else
        return! lift <| Error (sprintf "%s is not a valid ProjectId/SprintId." input)
}

let issuesFunction (projectsAndSprintIds : (int * int) list) =
    projectsAndSprintIds
    |> List.collect (fun (projectId, sprintId) -> [projectId, sprintId])

let test = monad {
    let sprintIds = [|"2/315"|]
    let! (projectAndSprintIds : List<int * int>) =
        sprintIds
        |> toList
        |> Seq.map parseProjectAndSprintId //<-- Changing this to List.map is a known workaround
        |> sequence

    let output = issuesFunction projectAndSprintIds

    if output |> Seq.isEmpty then
        return! (lift (Error ""))
    else
        return output
}

StateT.run test 0.0

Expected behavior

A compiler error, or for StateT.run test 0.0 to return Ok as sequence input is a non-empty collection.

Actual behavior

StateT.run test 0.0 returns Error because output |> Seq.isEmpty is true.

Known workarounds

Changing the Seq.map call to a List.map before sequence is called. Making this mistake is how I discovered this problem. Shouldn't the compiler protect me from these mistakes?

Related information

The reason I believe this is triggering a compiler bug is that calling GetType on projectsAndSprintIds from within issuesFunction returns an IEnumerator, and the declared type of the argument is List<int * int>. An fsx with a more detailed debug output can be found here: FSharpPlusAndTypeInference.fsx.

gusty commented 4 years ago

I have smaller repro:

This is a way to transpose sequences:

let x: seq<seq<int>> = traverse ZipList (seq [seq [1;2]; seq [10;20]]) |> ZipList.run
// val x : seq<seq<int>> = seq [seq [1; 10]; seq [2; 20]]

but if we mistype x as seq<int>:

let x: seq<int> = traverse ZipList (seq [seq [1;2]; seq [10;20]]) |> ZipList.run

Exception raised during pretty printing.
Please report this so it can be fixed.
Trace: System.InvalidCastException: Unable to cast object of type 'mkSeq@132[System.Collections.Generic.IEnumerable`1[System.Int32]]' to type 'System.Collections.Generic.IEnumerable`1[System.Int32]'.
   at FSharp.Compiler.Interactive.Shell.Utilities.AnyToLayoutSpecialization`1.FSharp.Compiler.Interactive.Shell.Utilities.IAnyToLayoutCall.FsiAnyToLayout(FormatOptions options, Object o, Type ty) in F:\workspace\_work\1\s\src\fsharp\fsi\fsi.fs:line 88
   at FSharp.Compiler.Interactive.Shell.FsiValuePrinter.PrintValue(FsiValuePrinterMode printMode, FormatOptions opts, Object x, Type ty) in F:\workspace\_work\1\s\src\fsharp\fsi\fsi.fs:line 408
val x : seq<int> = 

and x gets created !

I'm pretty much sure this behavior was not observed in previous version of the F# compiler but I can't confirm, as my old Visual Studio seems to be broken.

gusty commented 5 months ago

This seems to be fixed in current F# (version 8 at the moment of writing this). Your repro works now, I mean it actually fails, but with the proper error message, about using seq instead of list, which prompts you to apply the workaround you showed.

My repro also fails properly now, and x is not created.

Closing this.