In patterns, _
means "match anything here, I don't care about it".
Since underscores are valid characters in identifiers, you can almost treat _
as equivalent to any other variable name that you just happen not to use on the right hand side. (The only difference is when you write multiple _
in the same pattern; in that case each _
is treated as separately matching anything, whereas using a normal variable multiple times in a pattern gives an error about conflicting definitions for the variable)
Since you only use a single _
in each pattern, we can see that it'll do the same thing if we just invent a new variable and use that.
So tell (x:y:_) = ...
is the same as tell (x:y:z) = ...
.
OTOH tell [x, y, _] = ...
is the same as tell [x, y, z] = ...
. This is completely different than the above. In x:y:z
, the left argument of each :
is an item in the list and the right argument is a list containing the rest of the items1. But in [x, y, z]
, each of x
, y
, and z
are items in the list.
The square bracket list syntax [a, b, c, d, ...]
does not allow you to refer to the tail of list. Each of the comma-separated expressions are items contained in the list, and the list has exactly the number of elements you wrote. You cannot use square bracket list syntax to write a pattern that matches a list with an arbitrary number of elements.
So the pattern (x:y:_)
(same as (x:y:z)
) matches a list with at least 2 elements, with the _
matching the zero-or-more remaining elements of the list after the first 2.
The pattern [x, y, _]
matches a list with exactly 3 elements, with the _
matching the third element.
1 The :
operator is right associative, so x:y:z
means x:(y:z)
; x
and y
are both left arguments to different :
and thus items, while z
is a right argument to :
and thus a list.
To answer your question about the "global meaning of _
", it doesn't really have a global meaning. It means different things in different contexts, which can be a little confusing. They all generally relate to the idea of "don't know" or "don't care", but they're not really otherwise connected.
The above discussion was about the meaning of _
in patterns.
In expressions, you can use _
as a placeholder to indicate "I don't know what goes here yet". If you compile code containing _
as an expression, GHC will give you an error, but it will try to figure out the type of what could go there and give you some information about it in the error message. I very frequently start writing a function by writing out a basic "shape" full of _
, and then letting the compiler tell me what kinds of thing I need to come up with to fill in the gaps.
For example, compiling this:
mymap :: (a -> b) -> [a] -> [b]
mymap f [] = []
mymap f (x:xs) = _ : _
Produces this:
/tmp/foo.hs:4:18: error:
• Found hole: _ :: b
Where: ‘b’ is a rigid type variable bound by
the type signature for:
mymap :: forall a b. (a -> b) -> [a] -> [b]
at /tmp/foo.hs:2:1-31
• In the first argument of ‘(:)’, namely ‘_’
In the expression: _ : _
In an equation for ‘mymap’: mymap f (x : xs) = _ : _
• Relevant bindings include
xs :: [a] (bound at /tmp/foo.hs:4:12)
x :: a (bound at /tmp/foo.hs:4:10)
f :: a -> b (bound at /tmp/foo.hs:4:7)
mymap :: (a -> b) -> [a] -> [b] (bound at /tmp/foo.hs:3:1)
|
4 | mymap f (x:xs) = _ : _
| ^
/tmp/foo.hs:4:22: error:
• Found hole: _ :: [b]
Where: ‘b’ is a rigid type variable bound by
the type signature for:
mymap :: forall a b. (a -> b) -> [a] -> [b]
at /tmp/foo.hs:2:1-31
• In the second argument of ‘(:)’, namely ‘_’
In the expression: _ : _
In an equation for ‘mymap’: mymap f (x : xs) = _ : _
• Relevant bindings include
xs :: [a] (bound at /tmp/foo.hs:4:12)
x :: a (bound at /tmp/foo.hs:4:10)
f :: a -> b (bound at /tmp/foo.hs:4:7)
mymap :: (a -> b) -> [a] -> [b] (bound at /tmp/foo.hs:3:1)
Valid hole fits include
mempty :: forall a. Monoid a => a
with mempty @[b]
(imported from ‘Prelude’ at /tmp/foo.hs:1:1
(and originally defined in ‘GHC.Base’))
|
4 | mymap f (x:xs) = _ : _
| ^
Another use of _
is in type signatures. Here it means "I don't know what this is, you tell me". Since the compiler can infer types anyway most of the time, it will simply tell you in the error message what you should use to fill in the blank.
For example, compiling this:
foo :: Int -> _
foo x = Just x
Produces:
/tmp/foo.hs:2:15: error:
• Found type wildcard ‘_’ standing for ‘Maybe Int’
To use the inferred type, enable PartialTypeSignatures
• In the type signature: foo :: Int -> _
|
2 | foo :: Int -> _
| ^
(You can even use the PartialTypeSignatures
language extension to allow GHC to go ahead and use the type it infers to fill in the blanks, instead of treating it as an error)