Underscore type params

· Allanderek's blog

A very small feature proposal for Elm's type parameters with a motivating situation.

In a case expression when you want to match anything you can use the special pattern _ which avoids giving a name to something you aren't going to use. However, you cannot do the same thing for type parameters. If you want to say "I accept any type here and I don't return anything containing that type" you just have to use an unused ttype variable name. I think you should be allowed to "say what you mean" and use underscore here for that situation. I'm going to give a motivating circumstance in which this would actually be useful.

To give a concrete example, suppose you wish to define the length function for lists:

1length : List a -> Int
2length l =
3    case l of
4        [] -> 0
5        _ :: rest -> 1 + length rest

I think you should be able to write the type signature here as List _ -> Int. Note that this only works because we're not returning any of the elements, we could not do: reverse : List _ -> List _. In just the same what that you cannot use _ as a variable name. So it is crucial that underscore does not simply introduce a new type variable name into scope which must be unified.

A particularly common place where you don't care about type parameter is when used within an extensible record. Suppose you have the following function:

 1viewSideBar : { a | now : Time} -> User -> Html msg
 2viewSideBar model =
 3    let
 4        showTime : Html msg
 5        ...
 6        showUser : Html msg
 7        ...
 8
 9    in
10    Html.div
11        [ Attributes.class "sidebar" ]
12        [ showTime 
13        , showUser 
14        ]
15

Now suppose the user may have a list of pets, as well as a list of siblings, ignoring the possibility that either of these two lists is empty:

 1viewSideBar : { a | now : Time} -> User -> Html msg
 2viewSideBar model =
 3    let
 4        showTime : Html msg
 5        ...
 6        showUser : Html msg
 7        ...
 8
 9        showPets : Html msg
10        showPets =
11            Html.ul
12                [ Attributes.class "user-pets" ]
13                [ List.map (\p -> Html.li [] [ Html.text p.name]) user.pets  ]
14
15        showSiblings : Html msg 
16        showSiblings =
17            Html.ul
18                [ Attributes.class "user-siblings" ]
19                [ List.map (\s -> Html.li [] [ Html.text s.name]) user.siblings  ]
20
21    in
22    Html.div
23        [ Attributes.class "sidebar" ]
24        [ showTime 
25        , showUser 
26        , Html.h2 [] [ Html.text "Pets" ]
27        , showPets
28        , Html.h2 [] [ Html.text "Siblings" ]
29        , showSiblings
30        ]
31

Now, a Pet is a different type from a Sibling but you realise that both are rendered the same, as both have a name field and that's all that is used here, so you factor that out:

 1viewSideBar : { a | now : Time} -> User -> Html msg
 2viewSideBar model =
 3    let
 4        showTime : Html msg
 5        ...
 6        showUser : Html msg
 7        ...
 8
 9        showNamedList : String -> List { a | name : String } -> Html list
10        showNamedList className nameables =
11            Html.ul
12                [ Attributes.class className ]
13                [ List.map (\n -> Html.li [] [ Html.text n.name]) nameables  ]
14
15
16        showPets : Html msg
17        showPets =
18            showNamedList "user-pets" user.pets
19
20        showSiblings : Html msg 
21        showSiblings =
22            showNamedList "user-siblings" user.siblings
23
24    in
25    Html.div
26        [ Attributes.class "sidebar" ]
27        [ showTime 
28        , showUser 
29        , Html.h2 [] [ Html.text "Pets" ]
30        , showPets
31        , Html.h2 [] [ Html.text "Siblings" ]
32        , showSiblings
33        ]
34

Can you spot the error? I've re-used the a as a type parameter, but that now means the type checker will insist that the uses of showNamedList are unified to the type in the main signature. It won't be able to do that because presumably the other fields of Pets and Siblings are different. Even if they are the same the {a | now : Time} would now be too lenient.

What you really intended here was { anything | now : Time}, and I think a reasonable way to write that is { _ | now : Time}.