Beginner

Operators in Haskell

Explore arithmetic, comparison, boolean, list, and function operators in Haskell - where every operator is just a function in disguise

Operators in Haskell are not a special syntactic category - they are ordinary functions written in infix form. The expression 2 + 3 is really (+) 2 3, and you can define your own operators using any combination of symbols. This uniformity, combined with Haskell’s strong static type system and type classes, gives operators a clean and predictable behavior.

Because Haskell is purely functional, operators never mutate state. There are no compound assignment operators like += or ++ (in the C sense), and no increment or decrement. Every operator is a pure function that takes its arguments and returns a new value. Even the boolean operators && and || are functions - but with the helpful property that they short-circuit thanks to lazy evaluation.

This tutorial covers arithmetic, comparison, boolean, list, and function-level operators. You will see how to use familiar operators, how to turn any function into an operator with backticks, how to turn any operator into a function with parentheses, and how function composition (.) and application ($) form the backbone of idiomatic Haskell.

A Comprehensive Example

Create a file named operators.hs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module Main where

main :: IO ()
main = do
    -- Arithmetic operators
    putStrLn "--- Arithmetic ---"
    putStrLn ("7 + 3   = " ++ show (7 + 3))
    putStrLn ("7 - 3   = " ++ show (7 - 3))
    putStrLn ("7 * 3   = " ++ show (7 * 3))
    putStrLn ("7 / 3   = " ++ show (7 / 3 :: Double))
    putStrLn ("7 `div` 3 = " ++ show (7 `div` 3))
    putStrLn ("7 `mod` 3 = " ++ show (7 `mod` 3))
    putStrLn ("2 ^ 10  = " ++ show (2 ^ 10 :: Int))

    -- Comparison operators
    putStrLn "\n--- Comparison ---"
    putStrLn ("5 == 5: " ++ show (5 == 5))
    putStrLn ("5 /= 3: " ++ show (5 /= 3))
    putStrLn ("5 >  3: " ++ show (5 > 3))
    putStrLn ("5 <= 3: " ++ show (5 <= 3))

    -- Boolean operators (short-circuit via laziness)
    putStrLn "\n--- Boolean ---"
    putStrLn ("True  && False: " ++ show (True && False))
    putStrLn ("True  || False: " ++ show (True || False))
    putStrLn ("not True:       " ++ show (not True))

    -- List and string operators
    putStrLn "\n--- Lists & Strings ---"
    putStrLn ("\"Hello, \" ++ \"World!\" = " ++ ("Hello, " ++ "World!"))
    putStrLn ("1 : [2,3,4]   = " ++ show (1 : [2,3,4]))
    putStrLn ("[1,2] ++ [3,4]= " ++ show ([1,2] ++ [3,4]))
    putStrLn ("[10,20,30] !! 1 = " ++ show ([10,20,30] !! 1))

    -- Function composition and application
    putStrLn "\n--- Composition (.) and Application ($) ---"
    let addOneThenDouble = (*2) . (+1)
    putStrLn ("((*2) . (+1)) 3 = " ++ show (addOneThenDouble 3))
    putStrLn ("show $ 1 + 2    = " ++ (show $ 1 + 2))

This single program touches every major operator category. Notice that show converts values to their String representation so they can be concatenated with ++ and printed. The type annotations :: Double and :: Int exist only to pin down numeric types that would otherwise be ambiguous.

Operators as Functions, Functions as Operators

A defining feature of Haskell is that any operator is just a function with a symbolic name, and any function can be used in infix position.

Create a file named infix_demo.hs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Main where

-- Turn a function into an operator using backticks
-- Turn an operator into a function using parentheses

main :: IO ()
main = do
    -- (+) is a function; you can pass it around
    putStrLn ("(+) 4 5    = " ++ show ((+) 4 5))

    -- div is a function; use backticks to write it infix
    putStrLn ("div 17 5   = " ++ show (div 17 5))
    putStrLn ("17 `div` 5 = " ++ show (17 `div` 5))

    -- Sections: partially applied operators
    let addTen = (+ 10)
    let halve  = (/ 2)
    putStrLn ("addTen 5   = " ++ show (addTen 5))
    putStrLn ("halve 9    = " ++ show (halve (9 :: Double)))

    -- Operators are first-class: pass (+) to a higher-order function
    putStrLn ("foldr (+) 0 [1..5] = " ++ show (foldr (+) 0 [1..5 :: Int]))

The takeaways:

  • Wrap an operator in parentheses, like (+), to use it as a regular function.
  • Wrap a function name in backticks, like `div`, to use it in infix position.
  • A section like (+ 10) or (/ 2) is a partially applied operator - itself a function.

Operator Precedence and Associativity

Each operator has a precedence from 0 (lowest) to 9 (highest) and an associativity (left, right, or none). Function application has the highest binding strength of all, tighter than any operator. You can ask GHCi:

ghci> :info (+)
class Num a where
  (+) :: a -> a -> a
  ...
infixl 6 +

ghci> :info (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
infixr 9 .

A small mental model:

  • ^, ^^, ** (power): infixr 8
  • *, /, `div`, `mod`: infixl 7
  • +, -: infixl 6
  • :, ++ (list cons and concat): infixr 5
  • ==, /=, <, <=, >, >=: infix 4
  • &&: infixr 3
  • ||: infixr 2
  • $ (application): infixr 0 - the lowest, which is why it acts like “everything to my right is one argument”

The result: print $ 1 + 2 * 3 evaluates as print (1 + (2 * 3)), printing 7.

Running with Docker

1
2
3
4
5
6
7
8
# Pull the official Haskell image
docker pull haskell:9.6

# Run the main operators example
docker run --rm -v $(pwd):/app -w /app haskell:9.6 runghc operators.hs

# Run the infix/sections example
docker run --rm -v $(pwd):/app -w /app haskell:9.6 runghc infix_demo.hs

Expected Output

Output from runghc operators.hs:

--- Arithmetic ---
7 + 3   = 10
7 - 3   = 4
7 * 3   = 21
7 / 3   = 2.3333333333333335
7 `div` 3 = 2
7 `mod` 3 = 1
2 ^ 10  = 1024

--- Comparison ---
5 == 5: True
5 /= 3: True
5 >  3: True
5 <= 3: False

--- Boolean ---
True  && False: False
True  || False: True
not True:       False

--- Lists & Strings ---
"Hello, " ++ "World!" = Hello, World!
1 : [2,3,4]   = [1,2,3,4]
[1,2] ++ [3,4]= [1,2,3,4]
[10,20,30] !! 1 = 20

--- Composition (.) and Application ($) ---
((*2) . (+1)) 3 = 8
show $ 1 + 2    = 3

Output from runghc infix_demo.hs:

(+) 4 5    = 9
div 17 5   = 3
17 `div` 5 = 3
addTen 5   = 15
halve 9    = 4.5
foldr (+) 0 [1..5] = 15

Key Concepts

  • Operators are functions. Any operator can be used as a regular function by wrapping it in parentheses: (+) 2 3 equals 2 + 3.
  • Functions can be operators. Any named function can be called in infix style by wrapping it in backticks: 17 `div` 5.
  • / vs div vs mod. (/) requires a Fractional type (like Double); use div and mod for integer division and remainder on Integral types.
  • No mutation operators. Haskell has no +=, ++ (increment), or --. Values are immutable, so every operator returns a new value.
  • Boolean short-circuiting comes from laziness. && and || are ordinary functions, but lazy evaluation means the right operand is only evaluated if needed.
  • Sections create partial applications. (+ 10) is a function that adds 10; (10 -) is a function that subtracts its argument from 10. (Note: (- 10) is the number negative ten, not a section - use subtract 10 instead.)
  • $ for application, . for composition. f $ g x means f (g x) and removes parentheses; f . g builds a new function that runs g first, then f.
  • Precedence matters. Function application binds tighter than any operator, so f x + 1 means (f x) + 1, not f (x + 1).

Running Today

All examples can be run using Docker:

docker pull haskell:9.6
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining