4 min read
On this page

Functions and Currying

Functions in Haskell are values. You can pass them around, store them in data structures, and return them from other functions, exactly like an Int or a String. This sounds obvious until you internalize the consequence: there is no real distinction between "calling a function" and "constructing a smaller function from it." Every function in Haskell takes exactly one argument. Multi-argument functions are an illusion built on currying.

If you've written JavaScript or Python, you're used to f(x, y, z) being a single call. In Haskell, f x y z is three calls. f x returns a function. That function takes y and returns another function. That one takes z and returns the final result. Once this clicks, partial application stops being a "feature" and becomes the natural shape of the language.

Defining Functions

The simplest definition uses an equals sign:

double :: Int -> Int
double x = x * 2

add :: Int -> Int -> Int
add x y = x + y

greet :: String -> String -> String
greet greeting name = greeting ++ ", " ++ name ++ "!"

The type signature for add reads as Int -> Int -> Int, but -> is right-associative, so this is really Int -> (Int -> Int). A function from Int to "a function from Int to Int." That parenthesization is what makes currying work.

You can also define functions without naming arguments, using lambdas:

double :: Int -> Int
double = \x -> x * 2

add :: Int -> Int -> Int
add = \x y -> x + y

Most code prefers the named-argument form. Lambdas show up most often when you're passing a one-off function to map or filter.

Currying in Practice

Because every function takes one argument, you can supply arguments one at a time:

add :: Int -> Int -> Int
add x y = x + y

addFive :: Int -> Int
addFive = add 5

-- addFive 10 == 15

add 5 doesn't error or wait for more arguments. It returns a perfectly usable Int -> Int. This is partial application, and it's idiomatic Haskell. You'll see it everywhere:

-- Multiply every element by 3
tripled :: [Int] -> [Int]
tripled = map (* 3)

-- Keep only positive numbers
positives :: [Int] -> [Int]
positives = filter (> 0)

-- Indent every line of a string
indent :: String -> String
indent = unlines . map ("  " ++) . lines

Compare this to a language where you'd write xs.map(x => x * 3) or map(lambda x: x * 3, xs). The Haskell version is shorter not because the syntax is denser, but because partial application removes the need for a throwaway variable.

Why "Every Function Takes One Argument" Matters

This isn't a quirky implementation detail. It changes how you design APIs. When you write getUser :: Database -> UserId -> IO User, the order of parameters matters. Put the thing that varies most often last:

-- Bad: hard to partially apply
fetchUser :: UserId -> Database -> IO User

-- Good: you can build a "user fetcher for this DB"
fetchUser :: Database -> UserId -> IO User

withDb :: Database -> IO ()
withDb db = do
  let getUser = fetchUser db
  alice <- getUser (UserId 1)
  bob   <- getUser (UserId 2)
  ...

Mercury's banking codebase, Standard Chartered's strats libraries, and Facebook's Sigma anti-abuse system all lean heavily on this. You'll see configuration, environment, and database handles bound first, then the per-call data threaded through partial application or ReaderT.

Infix Notation with Backticks

Any two-argument function can be called infix by surrounding its name with backticks:

-- Prefix
divResult = div 10 3

-- Infix
divResult = 10 `div` 3

-- elem checks list membership
isVowel c = c `elem` "aeiou"

This is purely syntactic. It doesn't change the function. Use it when it makes the code read naturally, especially for binary operations like mod, div, elem, intersect, or domain-specific verbs like userOwns or accountHas.

The flip side: any operator can be called prefix by wrapping it in parentheses:

sum1 = 1 + 2
sum2 = (+) 1 2

addAll :: [Int] -> Int
addAll = foldr (+) 0

Sections

Sections are partial applications of infix operators. There are two flavors:

-- Right section: (op2 1) means \x -> x op2 1
subtractOne :: Int -> Int
subtractOne = (subtract 1)  -- careful, see below

minusOne :: Int -> Int
minusOne = (`subtract` 1)   -- \x -> 1 - x, not what you want

-- Sections with +
addTen :: Int -> Int
addTen = (+ 10)             -- \x -> x + 10

-- Left section: (1 op2) means \x -> 1 op2 x
divideOneBy :: Double -> Double
divideOneBy = (1 /)         -- \x -> 1 / x

The minus sign is special. (- 1) is the literal -1, not a section. To get the function "subtract one from x," use subtract 1:

-- WRONG: this is just -1, the number
broken = map (- 1) [1, 2, 3]  -- type error

-- RIGHT
fixed = map (subtract 1) [1, 2, 3]  -- [0, 1, 2]

This is one of the few genuine warts in Haskell syntax. Memorize it and move on.

Sections are heavily used in pipeline-style code:

-- Normalize whitespace and uppercase
shout :: String -> String
shout = map toUpper . filter (/= ' ')

-- Keep ages above 21
adults :: [Person] -> [Person]
adults = filter ((> 21) . age)

A Real Example: Building a Query Filter

Here's a small slice of code that might appear in a real backend:

data User = User
  { userId    :: Int
  , userName  :: String
  , userAge   :: Int
  , userActive :: Bool
  }

-- Compose predicates using partial application and sections
isAdult :: User -> Bool
isAdult = (>= 18) . userAge

isActive :: User -> Bool
isActive = userActive

nameStartsWith :: Char -> User -> Bool
nameStartsWith c u = case userName u of
  (x:_) -> x == c
  _     -> False

-- Find active adults whose name starts with 'A'
findCandidates :: [User] -> [User]
findCandidates = filter isAdult
               . filter isActive
               . filter (nameStartsWith 'A')

nameStartsWith is curried: nameStartsWith 'A' is a User -> Bool, which is exactly what filter wants. No lambdas needed.

Common Pitfalls

Forgetting that f x y is (f x) y. When you write map f xs ys, the compiler reads it as ((map f) xs) ys, which is almost certainly a type error. If you mean to map a two-argument function, use zipWith f xs ys.

The minus-sign section trap. (- 1) is a number, not a function. Use subtract 1.

Argument order that fights partial application. If you write lookup :: Map -> Key -> Maybe Value you can build per-map lookups. If you write lookup :: Key -> Map -> Maybe Value you can build per-key lookups. Pick the order that matches how callers will reuse the function. The standard Data.Map.lookup chose Key -> Map -> Maybe Value because looking up many keys in one map is the common pattern, and lookup k reads naturally.

Over-eager partial application that hides intent. Just because you can write f = g . h . k . j doesn't mean you should. If readers have to mentally simulate three function compositions to understand what f does, name an intermediate value or write the arguments out.

Key Takeaways

Functions are values, full stop. Every function takes one argument and returns one result; multi-argument functions are syntactic sugar for nested one-argument functions. This is what makes partial application work without ceremony.

Argument order is API design. Put the thing that varies last so partial application produces useful intermediate functions. Backticks let any function be called infix; parentheses let any operator be called prefix. Sections like (+ 10) and (10 /) partially apply operators, but (- 1) is the number negative one, not a function.

Once currying feels native, you stop writing lambdas for trivial transforms and the code gets shorter without getting denser.