Chris A
Chris A

Reputation: 368

Function with extensible record parameter passed as a parameter cannot be used from within a case statement

I'm trying to extract some information from some records using extensible records. If I create a function taking the extensible record type and returning a string and use that within a case statement then there are no issues (namedToString, in the below example). However, if I attempt to use a function passed as a parameter (stringFromNamed) I will get an error complaining:

This business value is a: 
   Business  
But stringFromNamed needs the 1st argument to be:  
   Named a -> String (edited)  

from the example code below:

type alias Named a =
    { a | name : String }

type alias Person =
    Named { address : String }

type alias Business =
    Named { employeeCount : Int }

type Change
    = PersonUpdate Person
    | BusinessUpdate Business

namedToString : Named a -> String
namedToString changeFields =
    changeFields.name

changeToString : (Named a -> String) -> Change -> String
changeToString stringFromNamed change =
    case change of
        PersonUpdate person ->
            -- This works
            namedToString person

        BusinessUpdate business ->
            -- This will cause the error
            stringFromNamed business

This example includes just the required code but a more complete example can be found at https://ellie-app.com/77nCPLh55j3a1

What is causing the issue and how can I achieve my goal of passing in a function which will extract some information from an extensible record?

Upvotes: 3

Views: 271

Answers (2)

Chris A
Chris A

Reputation: 368

I discovered this issue in the Elm compiler repo: https://github.com/elm/compiler/issues/1959

This appears to be a bug in the compiler which can be worked around if you just want to pass an external record of one type to a function parameter. You can do this by removing the type signature of the higher order function, changeToString in the example above.

Unfortunately, if we wanted to perform the same action from the function parameter, e.g. stringFromNamed, on every case the error will return so another workaround must be found.

The workaround I'm using is to create a new record type which includes exactly the fields from the extensible record and then creating an instance of this from the fields of the other records adhering to the extensible record type. Not a big problem with only a couple of cases and an extensible record with only one field but this doesn't scale especially well. Example below:

type alias Named a =
    { a | name : String }


type alias OnlyNamed =
    Named {}


type alias Person =
    Named { address : String }


type alias Business =
    Named { employeeCount : Int }


type Change
    = PersonUpdate Person
    | BusinessUpdate Business


type Msg
    = UpdateCurrentChange Change


namedToString : Named a -> String
namedToString changeFields =
    changeFields.name


changeToString : (OnlyNamed -> String) -> Change -> String
changeToString stringFromNamed change =
    case change of
        PersonUpdate { name } ->
            stringFromNamed  { name = name }

        BusinessUpdate { name } ->
            stringFromNamed  { name = name }

Upvotes: 4

bdukes
bdukes

Reputation: 155935

I think the issue is that the function you're passing in takes Named a, and Elm doesn't know what the a refers to, so it ends up deciding it's a type mismatch. If you change the type annotation to (Named Business -> String) -> Change -> String it will compile and work.

Upvotes: 1

Related Questions