Clearer type errors by respecting type declarations
Is your feature request related to a problem? Please describe.
I think this is best explained by showing you why I ended up making this suggestion. I was fixing some code after a bunch of changes in a board game project. I had this error to fix(picture included to show where the error showed up)
I deduced for myself, maybe I'm wrong, that because I'm making the boolean check of "piece <> Nothing" where Nothing is a Piece<'T> instead of a GamePiece<'T>. So the error is sorta assumed in reverse even though I have specified the type on the parameter line.
Here is the code if somebody would like to copy it:
let checkLine (line : GamePiece<TickTackToePiece> option list) =
line.Length > 0 && List.forall (fun piece -> piece <> Nothing && piece = line.Head) line
Describe the solution you'd like
Stick to declared types when assuming types, rather than the use of a type in a line of code
Additional context "related: #1103"
Is this is something that could be considered a beginner issue, I would love to give it a shot at some point 👍
(This should probably be a discussion instead.)
This is due in part to the fact that type inference is applied from left to right in an expression like
List.forall predicate xs
In your example, when the compiler is typechecking the predicate lambda that you are passing to List.forall, it does not yet know that piece is a GamePiece<_>; it only knows that it is a Piece<_> because you compare it to Nothing.
Here is a minimal reproduction of your problem (or at least of something similar to it):
type Base () = class end
type Derived () = inherit Base ()
type Base<'T> () = class end
type Derived<'T> () = inherit Base<'T> ()
let Nothing = Base<Base> ()
let Something = Derived<Derived> ()
let f (xs : Derived<Derived> list) = List.forall (fun x -> x <> Nothing && x = Something) xs
That could normally be addressed by piping line into List.forall so that the compiler already has the type information it needs by the time it is checking the predicate:
line |> List.forall (fun piece -> piece <> Nothing && piece = line.Head)
However, in your specific case, you will still run into a problem if you are relying on inheritance in the type parameter of GamePiece<_> and/or Piece<_>, as I have shown in my reproduction above, because F# does not support covariance.
And even if you aren't, you would still need to explicitly upcast piece to the same base type as Nothing:
type Piece () = class end
type Base<'T> () = class end
type Derived<'T> () = inherit Base<'T> ()
let Nothing = Base<Piece> ()
let Something = Derived<Piece> ()
let f (xs : Derived<Piece> list) = xs |> List.forall (fun x -> x :> Base<_> <> Nothing && x = Something)
Inheritance often does not work well together with type inference and automatic generalization, and if you try to use them together you will find that you often need to use explicit casts when you want to treat derived types as their base types, etc.
I have created a discussion instead: https://github.com/dotnet/fsharp/discussions/18570
Here is the code for my types, I did not use inheritance:
type Piece<'Type> =
| Nothing
| GamePiece of 'Type
type ChessPiece =
| King | Queen | Rook | Bishop | Knight | Pawn
type TickTackToePiece =
| Cross | Naught
type Position = { X : int; Y : int }
type GamePiece<'PieceType> = {
Id : Guid
Piece : Piece<'PieceType>
Position : Position
}
type Board<'PieceType> = {
Cells : Map<Position, GamePiece<'PieceType>>
Size : int
}
type Player<'PieceType, 'PlayerId> = {
Id : 'PlayerId
Pieces : Map<Position, GamePiece<'PieceType>>
}
type Game<'PieceType, 'PlayerId> = {
Board : Board<'PieceType>
Players : Player<'PieceType, 'PlayerId> list
NextPlayer : 'PlayerId
LastPlacedPiece : Position
}
Covered by https://github.com/dotnet/fsharp/discussions/18570 now.