Open mfeineis opened 5 years ago
I appear to be falling foul of this same issue, unfortunately without an apparent workaround. In my instance I want to use a custom type to contain various record types and then use extensible records to extract the required field.
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
type alias Model =
{ currentChange : Change }
initialModel : Model
initialModel =
{ currentChange = PersonUpdate {name = "Christopher", address = "Glasgow"} }
type alias Named a =
{ a | name : String }
type alias Person =
{ name: String, address : String }
type alias Business =
{ name: String, employeeCount : Int }
type Change
= PersonUpdate Person
| BusinessUpdate Business
type Msg
= UpdateCurrentChange Change
changeToString : (Named a -> String) -> Change -> String
changeToString stringFromNamed change =
case change of
PersonUpdate person ->
stringFromNamed person
BusinessUpdate business ->
stringFromNamed business
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateCurrentChange change ->
{ model | currentChange = change }
view : Model -> Html Msg
view model =
div []
[ button [ onClick (UpdateCurrentChange (PersonUpdate {name = "Chris", address = "Glasgow"})) ] [ text "Chris" ]
, button [ onClick (UpdateCurrentChange (BusinessUpdate {name = "Tramway", employeeCount = 100})) ] [ text "Tramway" ]
, div [] [ text <| changeToString (\named -> named.name) model.currentChange ]
]
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
Producing the following error:
Type Mismatch
Line 47, Column 29
The 1st argument to `stringFromNamed` is not what I expect:
47| stringFromNamed person
^^^^^^
This `person` value is a:
Person
But `stringFromNamed` needs the 1st argument to be:
{ a | name : String }
Hint: Seems like a record field typo. Maybe address should be name?
Hint: Can more type annotations be added? Type annotations always help me give
more specific messages, and I think they could help a lot in this case!
Essentially the same issue as above, however when the calls to the function parameter occur multiple times in the same higher order function (as in the case statement in the example) then the workarounds of modifying or removing the signature mentioned above no longer work.
There is an Ellie with the issue here: https://ellie-app.com/77C7SXCNFMza1
Also, I've seen mentioned elsewhere that the purpose of extensible records is to allow access only to a subset of a larger record type so if this use of them for extracting data from multiple record types with the correct fields isn't intended functionality maybe this would be a candidate for a new, more specific, error message.
Apologies if adding to this issue isn't the correct protocol, I can add a new issue if necessary.
Yeah, to reiterate, both of the examples given in this issue boil down to quantification.
In @mfeineis example:
calcValue : Model -> ({ a | id : Int } -> String) -> String
calcValue withId calc =
calc withId
I could pass in a function { foo : Int, id : Int } -> String
, and according to your type signature, it should be accepted. Clearly, it should not, because Model
has no foo : Int
field.
In languages that allow explicit quantification, this could be expressed like so: Model -> (forall a. { a | id : Int } -> String) -> String
, which means that the function you pass in should work for "any imaginablea
", not just for "some specified a
"
So, the examples here don't work for the same reason that this code doesn't work:
foo : (a -> a) -> Int -> Int
foo f x = f x
Quick Summary: The following program should type-check as the extensible record type matches the model type, interesting enough it does without type annotation but annotating it leads to a confusing error message claiming that
Model
doesn't match with{ id : Int }
which isn't true.SSCCE
produces a type mismatch error
Additional Details
Here is an Ellie reproducing the problem.
Changing the type signature to
calcValue : {a | id: Int} -> ({ a | id : Int } -> String) -> String
works.