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 Pet
s and Sibling
s 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}
.