100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Functions in Haskell

How to define, call, curry, and locally scope functions in Haskell, including lambdas and where/let bindings.

FoundationsBeginner9 min readJul 10, 2026
Analogies

Defining and Calling Functions

A Haskell function is defined with the function name, its parameters, an =, and an expression -- there is no return keyword, because the expression on the right-hand side *is* the result. greet name = "Hello, " ++ name ++ "!" defines a function greet that concatenates strings with the ++ operator; calling it looks like greet "Ada", with the argument placed directly after the function name and no parentheses required, unlike C-family languages where greet("Ada") is mandatory syntax.

🏏

Cricket analogy: Calling greet "Ada" with a plain space instead of parentheses is like calling out a fielding position -- 'cover point' -- without needing brackets around the words; Haskell's juxtaposition-based application is just as terse as cricket's shorthand calls.

haskell
greet :: String -> String
greet name = "Hello, " ++ name ++ "!"

main :: IO ()
main = putStrLn (greet "Ada")  -- Hello, Ada!

Currying and Partial Application

Functions with multiple parameters are curried by default: add :: Int -> Int -> Int; add x y = x + y is really a function that takes one Int and returns a new function Int -> Int, so add 3 alone is a perfectly valid, fully-typed value on its own -- a function waiting for its second argument -- and add 3 4 is shorthand for (add 3) 4. This is why the -> arrow in a type signature is right-associative and why partial application (calling a function with fewer arguments than its arity to produce a more specialized function) falls out naturally rather than needing any special syntax.

🏏

Cricket analogy: A net session where a bowling machine is first configured for pace (like add 3) and only later given the specific line-and-length setting (the second argument) mirrors currying -- a partially-configured function is itself a complete, usable value.

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

addThree :: Int -> Int
addThree = add 3       -- partial application

main :: IO ()
main = print (addThree 4)  -- 7

Because -> is right-associative, Int -> Int -> Int parses as Int -> (Int -> Int), which is exactly why supplying only one argument to add yields a perfectly valid function value rather than a type error.

Lambdas and Local Bindings: where vs let

Anonymous (lambda) functions are written with a backslash standing in for the Greek letter lambda -- \x -> x * 2 is an unnamed function equivalent to a named double x = x * 2 -- and are most often used as short, throwaway arguments to higher-order functions like map (\x -> x * 2) [1,2,3], which evaluates to [2,4,6]. Local helper definitions inside a function body use where (attached to the end of an equation, visible only to that equation) or let ... in ... (usable anywhere an expression is expected, including nested inside another expression), and choosing between them is mostly a matter of readability rather than a functional difference in what they can express.

🏏

Cricket analogy: A one-off improvised field placement called out just for a single ball -- 'short leg, this ball only' -- and never given a permanent name, mirrors a lambda \x -> x * 2 used inline for a single call to map rather than being named.

haskell
-- Anonymous lambda passed directly to map
doubled :: [Int]
doubled = map (\x -> x * 2) [1, 2, 3]  -- [2,4,6]

-- 'where' attaches a helper to the whole equation
circleArea :: Double -> Double
circleArea r = pi * rSquared
  where rSquared = r * r

-- 'let ... in ...' works as a nested expression anywhere
circleArea2 :: Double -> Double
circleArea2 r = let rSquared = r * r in pi * rSquared

A where clause is scoped to the entire equation it's attached to (including all of its guards), while let is a local expression usable inside any sub-expression -- mixing them up when a helper needs to be shared across multiple guards is a common early mistake.

  • Functions are defined as name params = expression, with no return keyword.
  • Function application uses juxtaposition -- greet "Ada" -- with no parentheses required.
  • All multi-parameter functions are curried by default: add :: Int -> Int -> Int is really Int -> (Int -> Int).
  • Partial application, like addThree = add 3, produces a new, more specific function.
  • Lambdas are written \x -> expr and are commonly passed inline to higher-order functions like map.
  • where attaches helper bindings to a whole equation, visible across all its guards.
  • let ... in ... introduces bindings as a nested expression usable anywhere an expression is expected.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#HaskellStudyNotes#FunctionsInHaskell#Functions#Haskell#Defining#Calling#StudyNotes#SkillVeris#ExamPrep