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.
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.
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.
-- 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 noreturnkeyword. - Function application uses juxtaposition --
greet "Ada"-- with no parentheses required. - All multi-parameter functions are curried by default:
add :: Int -> Int -> Intis reallyInt -> (Int -> Int). - Partial application, like
addThree = add 3, produces a new, more specific function. - Lambdas are written
\x -> exprand are commonly passed inline to higher-order functions likemap. whereattaches 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
1. How is a function called in Haskell, e.g. calling `greet` with `"Ada"`?
2. What is the real type of `add :: Int -> Int -> Int`?
3. What does `addThree = add 3` produce?
4. What is the syntax for an anonymous lambda function that doubles its input?
5. What is the key scoping difference between `where` and `let ... in ...`?
Was this page helpful?
You May Also Like
Values and Types in Haskell
How Haskell's static type system, primitive types, immutable value bindings, and type inference work together, illustrated with GHCi examples.
Higher-Order Functions in Haskell
Learn how Haskell treats functions as first-class values, and how map, filter, foldr, currying, and composition let you build programs out of small, reusable pieces.
Your First Haskell Program
Writing, compiling, and running a first Haskell program, from a one-line Hello World to a small interactive do-notation example.
Pattern Matching in Haskell
Learn how Haskell lets you destructure values directly in function definitions and case expressions to write clear, exhaustive branching logic.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics