Underscore

The underscore character is treated in almost all cases like a letter. It can therefore be used anywhere within an identifier. Although the conventional way to form an identifier from multiple words in Haskell is “camelCase”, you can define names in “snake_case” if you wish:

hello_world = "hello world!"
data File_Path =
  File_Path
    { path_dir :: String
    , path_file :: String
    }

But the underscore also has some special meanings. Often an underscore character in Haskell code represents something we don’t care about or don’t know about – whether it’s a wildcard, a hole, or a discarded result. Underscores appear both in types and in terms. Sometimes they have some special meaning in the language; other times they may be part of some informal naming convention.

In patterns

Standing alone

When the underscore appears alone in a pattern,This is perhaps the most common use of an underscore. it is a reserved identifier that acts as a wildcard: a pattern that matches anything A pattern that always succeeds is called an irrefutable pattern. but does not introduce a new variable.

“Pattern” includes function parameters. So when an underscore is used as one of the formal parameters of a function, it idiomatically conveys that this function does not care about its argument. Such a function is constant:

f _ = ["death", "taxes"]

The following definition of fmap for Maybe uses an underscore in the Nothing case because the function is irrelevant when there is nothing to apply it to:

fmap f (Just x) = Just (f x)
fmap _ Nothing = Nothing

An underscore often appears as the last entry in a case expression as a catch-all that matches when everything else has failed:

readRomanDigit x =
  case x of
    'I' -> Just 1
    'V' -> Just 5
    'X' -> Just 10
    'L' -> Just 50
    'C' -> Just 100
    'D' -> Just 500
    'M' -> Just 1000
    _   -> Nothing

As a prefix

The underscore character is treated like a lowercase letter. This means that it can be the first character of function names and variables, but not the first character of classes and types. See Identifiers and operators for a fuller discussion of the significance of capitalization. Beginning a name with an underscore is sometimes used to name variables we will not use – and thus don’t really care about – but care just enough about to give it a name to remind ourselves of what is being discarded.

greetCasually :: String -> String -> IO ()
greetCasually firstName _lastName =
    putStrLn ("Hi, " ++ firstName)

Unlike a standalone underscore, this underscore is not a keyword, and greetCasually does bind a variable named _lastName which you can make reference to. The only thing the underscore does is suppress a compiler warning about an unused variable.

In expressions

Standing alone

Underscores may appear alone to indicate a hole, at the type or term level, whose presence will elicit type information from the compiler.

λ> x = filter _ "abc"

error:
  • Found hole: _ :: Char -> Bool
  • In the first argument of ‘filter’, namely ‘_’

With the -fdefer-typed-holes GHC flag enabled, this is a warning instead of an error.

λ> :set -fdefer-typed-holes

λ> x = filter _ "abc"

warning: [-Wtyped-holes]
  • Found hole: _ :: Char -> Bool
  • In the first argument of ‘filter’, namely ‘_’

As a prefix

An identifier may begin with an underscore, and this carries no special significance. Doing so is atypical.

λ> _x = 5

λ> print _x
5

With -fdefer-typed-holes, if the identifier is undefined, then it is interpreted as a hole rather than a reference to an undefined variable.

λ> z = 4 + _y

warning: [-Wtyped-holes]
    • Found hole: _y :: a

As a suffix

There is a similar-but-different convention for putting an underscore at the end of a function name; functions like this typically come as part of a pair of similar functions, where the one with the underscore is simpler in some way, usually because it discards some value. A good example from base is traversal:Data.Traversable.traverse; Data.Foldable.traverse_

traverse  :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse_ :: (Foldable t, Applicative f)    => (a -> f b) -> t a -> f ()

The first applies a function to each element of a collection and returns a collection of the results. The second applies a function to each element of a collection, accumulating its “effects”, but does not collect the results.

In types

Standing alone

In a visible type application, an underscore is a type wildcard. It allows us to leave blanks for type parameters that we want to leave unspecified. For example, if we want to see how the semigroup operation specializes to lists and we don’t care what type the list elements are, we can leave it blank:

λ> :type (<>) @[_]
(<>) @[_] :: [w] -> [w] -> [w]

By default, wildcards are not allowed in type signatures.

λ> :{
 > f :: Integer -> _
 > f x = x + 1
 > :}

error:
  • Found type wildcard ‘_’ standing for ‘Integer’
    To use the inferred type, enable PartialTypeSignatures
  • In the type signature: f :: Integer -> _

But with the PartialTypeSignatures extension enabled, an underscore may appear within a type signature.

λ> :set -XPartialTypeSignatures

λ> :{
 > f :: Integer -> _
 > f x = x + 1
 > :}

warning: [-Wpartial-type-signatures]
  • Found type wildcard ‘_’ standing for ‘Integer’
  • In the type signature: f :: Integer -> _

The compiler warning informs us that GHC filled in the blank with Integer.

As a prefix

A name like “_a” is normally a valid name for a type variable. If you enable the NamedWildCards extension, then it becomes a hole instead.

λ> :set -XNamedWildCards

λ> :{
 > f :: Integer -> _result
 > f x = x + 1
 > :}

warning: [-Wpartial-type-signatures]
  • Found type wildcard ‘_result’ standing for ‘Integer’
  • In the type signature: f :: Integer -> _result

As a constraint

When using PartialTypeSignatures, GHC will not infer a constraint unless you add a wildcard to the constraint list.

λ> :{
 > f :: _ => _ Integer -> _ Integer
 > f x = fmap (+1) x
 > :}

error:
  • Found type wildcard ‘_’ standing for ‘Functor w’

In numeric literals

With the NumericUnderscores extension enabled, an underscore may appear anywhere within a numeric literal. These underscores are ignored by the compiler, but they may serve as useful visual guides for human readers.

For example, suppose we want to write a large number like one billion (1,000,000,000).

λ> oneBillion = 1000000000

If this definition erroneously contained the wrong number of zeros, it would be hard to notice the mistake. We may therefore prefer to write this number with underscores:

λ> :set -XNumericUnderscores

λ> oneBillion = 1_000_000_000

The number written with underscores has the same meaning as the number without them.

In the output of :sprint

When you use the :sprint command in GHCi, it uses an underscore to represent parts of the value that are not yet evaluated.

λ> x = [1..20] :: [Integer]

λ> x !! 2
3

λ> :sprint x
x = 1 : 2 : 3 : _

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.