Haskell is a functional programming language known for its high-level abstractions, immutability, and strong type system. While its elegance and power can seem intimidating to beginners, it comes with a rich set of basic functions that make it easier to get started with. Whether you’re performing simple arithmetic, manipulating lists, or exploring higher-order functions, understanding these foundational functions is essential for mastering Haskell.
In this article, we’ll walk through a list of some of the most commonly used basic functions in Haskell. From mathematical operations to list handling, tuples, and I/O functions, these tools will help you become more proficient and comfortable in the Haskell environment.
Type Signatures in Haskell
Before diving into basic functions, it helps to understand Type Signatures & Typeclasses. In Haskell, type signatures define the types of inputs and outputs for functions. They provide a way to specify what types a function operates on and what type it will return. Type signatures are optional in most cases because Haskell can infer types, but adding them explicitly can help improve code clarity and prevent certain errors.
A type signature is usually written above the function definition and follows this structure:
functionName :: inputType -> outputType
For example, the type signature of a function that adds two integers is:
add :: Int -> Int -> Int
This tells us that add
takes two arguments of type Int
and returns a result of type Int
.
Basic Structure of a Type Signature:
->
: Separates input types and output types. Ina -> b
,a
is the input type, andb
is the return type.- Parentheses: (not shown here) Used when a function takes another function as an argument or for clarity in complex types.
- :: is read as “has type of”
Type Signatures (Explain Like I’m 5)
Think of a type signature as a label that tells you what kind of input a function expects and what kind of result it will give you. It’s like a recipe: if you follow the instructions and use the right ingredients, you’ll get the right dish!
For example:
add :: Int -> Int -> Int
This type signature says:
- add is a function that takes two numbers (
Int
andInt
) and gives back another number (also anInt
).
So, the type signature is like the ingredients and result list in a recipe: “Give me two integers, and I’ll give you one integer back!”
Typeclasses in Haskell
A typeclass in Haskell is a way of defining a set of behaviors (functions) that different types can implement. Typeclasses provide a form of polymorphism, allowing a function to work with different types that implement the same set of operations.
For example, the Eq
typeclass defines types that support equality comparison. If a type is an instance of Eq
, you can use ==
or /=
to compare values of that type.
Common Typeclasses:
Eq
: Provides equality (==
) and inequality (/=
) comparison.
(==) :: Eq a => a -> a -> Bool
(/=) :: Eq a => a -> a -> Bool
-- Checking equality and inequality for Ints
5 == 5 -- Result: True
5 /= 10 -- Result: True
-- Checking equality for lists (lists are instances of Eq if their elements are)
[1, 2, 3] == [1, 2, 3] -- Result: True
[1, 2, 3] /= [1, 2] -- Result: True
-- Checking equality for custom data types by deriving Eq
data Color = Red | Green | Blue deriving (Eq)
Red == Green -- Result: False
Red /= Blue -- Result: True
In the Color
example, we derived Eq
for our custom data type, allowing us to compare Color
values directly with (==)
and (/=)
.
Ord
: For types that have an ordering. Provides operators like >
, <
, >=
, and <=
.
(>) :: Ord a => a -> a -> Bool
(<) :: Ord a => a -> a -> Bool
-- Comparing Ints
10 > 5 -- Result: True
10 <= 5 -- Result: False
-- Comparing characters (based on alphabetical order)
'a' < 'b' -- Result: True
-- Comparing lists lexicographically
[1, 2, 3] > [1, 2] -- Result: True
-- Custom data types with Ord
data Rating = Poor | Average | Good | Excellent deriving (Eq, Ord)
Excellent > Good -- Result: True
Poor <= Average -- Result: True
The Rating
example derives Ord
, so values like Excellent
and Good
can be compared in terms of ordering.
Show
: The Show
type class is used for converting values to strings. It defines the function show
, which converts a value into a String
.
show :: Show a => a -> String
-- Converting numbers to strings
show 123 -- Result: "123"
show 3.14 -- Result: "3.14"
-- Converting lists to strings
show [1, 2, 3] -- Result: "[1,2,3]"
-- Custom data type with Show
data Status = Success | Failure deriving (Show)
show Success -- Result: "Success"
show Failure -- Result: "Failure"
With Status
, we derive Show
, allowing us to convert Success
and Failure
values into their String
representations.
Read
: Sort of the opposite typeclass of Show. The read function takes a string and returns a type which is a member of Read.
read :: (Read a) => String -> a
-- Parsing an integer from a string
read "123" :: Int -- Result: 123
-- Parsing a float from a string
read "3.14" :: Float -- Result: 3.14
-- Parsing a list from a string
read "[1, 2, 3]" :: [Int] -- Result: [1,2,3]
-- Parsing a custom data type
data Color = Red | Green | Blue deriving (Show, Read)
read "Red" :: Color -- Result: Red
*Important Note with read
The read
function requires an explicit type annotation because Haskell needs to know what type to parse the string into. Without a type annotation, read
will cause a type ambiguity error.
Num
: For numeric types that support operations like +
, -
, and *
.
(+) :: Num a => a -> a -> a
(*) :: Num a => a -> a -> a
-- Basic arithmetic with Ints
5 + 10 -- Result: 15
7 * 3 -- Result: 21
15 - 8 -- Result: 7
-- Arithmetic with floating-point numbers
3.5 + 2.5 -- Result: 6.0
4.2 * 2 -- Result: 8.4
-- Using fromIntegral to convert Int to a Num type (like Float)
let x = fromIntegral (5 :: Int) * 2.5 :: Float
-- Result: 12.5
In this example, fromIntegral
is used to convert an Int
to a Float
so we can multiply it by a floating-point number.
enum
: Enum members are sequentially ordered types — they can be enumerated. The main advantage of the Enum typeclass is that we can use its types in list ranges. They also have defined successors and predecessors, which you can get with the succ and pred functions.
Types in this class: (), Bool, Char, Ordering, Int, Integer, Float and Double.
-- Successor and predecessor of integers
succ 5 -- Result: 6
pred 5 -- Result: 4
-- Successor and predecessor of characters
succ 'a' -- Result: 'b'
pred 'b' -- Result: 'a'
-- Generating a list of numbers with range syntax
[1..5] -- Result: [1,2,3,4,5]
-- Generating a list of characters
['a'..'e'] -- Result: "abcde"
-- Enum for days of the week (using custom data type)
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Show, Read, Enum, Bounded)
succ Monday -- Result: Tuesday
pred Sunday -- Result: Saturday
[Monday .. Friday] -- Result: [Monday, Tuesday, Wednesday, Thursday, Friday]
Custom Typeclass example: You can create your own type classes in Haskell and define instances for them. Here’s an example with a custom Describable
type class.
-- Define the Describable type class
class Describable a where
describe :: a -> String
-- Implement Describable for Int
instance Describable Int where
describe x
| x < 0 = "Negative number"
| x == 0 = "Zero"
| otherwise = "Positive number"
-- Implement Describable for Bool
instance Describable Bool where
describe True = "This is true!"
describe False = "This is false."
-- Usage
describe (-5) -- Result: "Negative number"
describe 0 -- Result: "Zero"
describe True -- Result: "This is true!"
In this example:
- We created a
Describable
type class with adescribe
function. - We implemented
Describable
forInt
andBool
, allowing us to calldescribe
on values of these types.
Typeclasses (Explain Like I’m 5)
A typeclass is like a set of rules or skills that certain types (things like numbers or text) can follow or have. Think of it like being part of a club: if something (a number or a list) follows the rules of the club, it gets to use certain abilities.
For example:
Eq
Club: Members of this club can be compared for equality (==
). If a type is in theEq
club, you can ask, “Are these two things equal?”Ord
Club: Members of this club know how to be ordered, so you can say, “Is this bigger or smaller than that?”Num
Club: This club is for numbers. If a type is in theNum
club, you can add, subtract, or multiply it.
Here’s an example:
(==) :: Eq a => a -> a -> Bool
This means that the function ==
works for any type a
, as long as a
is part of the Eq
club (it can be compared for equality). If a type isn’t in the club, you can’t use ==
on it.
1. Mathematical Functions
Haskell offers a range of mathematical functions that allow you to perform arithmetic and work with numbers efficiently. You’ll use these frequently, whether it’s simple addition or more advanced operations like exponentiation.
Haskell provides many built-in functions to work with numbers:
+
: Addition.
2 + 3 -- Result: 5
-
: Subtraction.
5 - 2 -- Result: 3
*
: Multiplication.
4 * 3 -- Result: 12
/
: Division (for floating-point numbers).
10 / 2 -- Result: 5.0
div
: Integer division.
10 `div` 3 -- Result: 3
mod
: Modulus (remainder after division).
10 `mod` 3 -- Result: 1
abs
: Absolute value.
abs (-5) -- Result: 5
signum
: Sign of a number (-1
, 0
, or 1
).
In Haskell, the signum
function is used to determine the sign of a number. It returns an indicator of whether the number is negative, zero, or positive. The function is defined in the Num
type class, so it works with any numeric type that belongs to this class (like Int
, Integer
, Float
, Double
, etc.).
Behavior of signum
- If the number is negative,
signum
returns-1
. - If the number is zero,
signum
returns0
. - If the number is positive,
signum
returns1
.
signum (-5) -- Result: -1
signum 0 -- Result: 0
signum 5 -- Result: 1
^
: Exponentiation (for integers).
2 ^ 3 -- Result: 8
sqrt
: Square root (for floating-point numbers).
sqrt 16 -- Result: 4.0
min
: Takes two values that can be compared (types that belong to the Ord
type class) and returns the smaller of the two.
min 3 5 -- Result: 3
min 'a' 'z' -- Result: 'a'
max
: Is similar to min
, but it returns the larger of two comparable values.
max 3 5 -- Result: 5
max 'a' 'z' -- Result: 'z'
succ
: Takes a value and returns its “successor” (the next value). It works with any type that is an instance of the Enum
type class, such as numbers or characters.
succ 5 -- Result: 6
succ 'a' -- Result: 'b'
pred
: Is the opposite of succ
, returning the “predecessor” (previous value) of a given value.
pred 5 -- Result: 4
pred 'b' -- Result: 'a'
fromIntegral
: Takes an integral number and turns it into a more general number. That’s useful when you want integral and floating point types to work together nicely.
fromIntegral :: (Num b, Integral a) => a -> b
fromIntegral (length [1,2,3,4]) + 3.2 -- Result: 7.2
2. Boolean Functions
When working with conditions and logic, Haskell’s boolean functions are essential. Whether you’re checking equality, combining conditions, or negating expressions, these functions simplify the logic in your programs.
Functions that operate on or return Bool
values (True
or False
):
&&
: Logical AND.
True && False -- Result: False
||
: Logical OR.
True || False -- Result: True
not
: Logical NOT.
not True -- Result: False
==
: Equality check.
5 == 5 -- Result: True
/=
: Inequality check.
5 /= 3 -- Result: True
>
: Greater than.
5 > 3 -- Result: True
<
: Less than.
3 < 5 -- Result: True
>=
: Greater than or equal to.
5 >= 5 -- Result: True
<=
: Less than or equal to.
3 <= 5 -- Result: True
3. List Functions
Lists are one of the most common data structures in Haskell. The language provides a robust set of functions for manipulating lists, such as getting the head or tail of a list, reversing it, or filtering elements based on a condition.
Haskell provides many functions for working with lists, one of the most commonly used data structures:
head
: Returns the first element of a list.
head [1, 2, 3] -- Result: 1
tail
: Returns the list without its first element.
tail [1, 2, 3] -- Result: [2, 3]
length
: Returns the length of a list.
length [1, 2, 3] -- Result: 3
null
: Checks if a list is empty.
null [] -- Result: True
null [1, 2] -- Result: False
reverse
: Reverses a list.
reverse [1, 2, 3] -- Result: [3, 2, 1]
take
: Returns the first n elements of a list.
take 2 [1, 2, 3, 4] -- Result: [1, 2]
drop
: Drops the first n elements of a list.
drop 2 [1, 2, 3, 4] -- Result: [3, 4]
sum
: Returns the sum of elements in a list.
sum [1, 2, 3] -- Result: 6
product
: Returns the product of elements in a list.
product [1, 2, 3] -- Result: 6
elem
: Checks if an element is in a list.
elem 3 [1, 2, 3, 4] -- Result: True
elem 5 [1, 2, 3, 4] -- Result: False
notElem
: Is the opposite of elem
. It returns True
if the value is not in the list, otherwise it returns False
.
notElem 3 [1, 2, 4] -- Result: True
init
: Takes a list and returns everything except its last element.
ghci> init [5,4,3,2,1]
[5,4,3,2]
cycle
: Takes a list and cycles it into an infinite list. If you just try to display the result, it will go on forever so you have to slice it off somewhere.
take 10 (cycle [1,2,3])
-- Result: [1,2,3,1,2,3,1,2,3,1]
take 12 (cycle "LOL ")
-- Result: "LOL LOL LOL "
repeat
: Takes an element and produces an infinite list of just that element. It’s like cycling a list with only one element.
take 10 (repeat 5)
-- Result: [5,5,5,5,5,5,5,5,5,5]
zip
: Takes two lists and then zips them together into one list by joining the matching elements into pairs.
zip [1,2,3,4,5] [5,5,5,5,5]
-- Result: [(1,5),(2,5),(3,5),(4,5),(5,5)]
zip [1 .. 5] ["one", "two", "three", "four", "five"]
-- Result:[(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]
Texas Ranges
What if we want a list of all numbers between 1 and 20? Sure, we could just type them all out but obviously that’s not a solution for gentlemen who demand excellence from their programming languages. Instead, we’ll use ranges. Ranges are a way of making lists that are arithmetic sequences of elements that can be enumerated. Numbers can be enumerated. One, two, three, four, etc. Characters can also be enumerated. The alphabet is an enumeration of characters from A to Z. Names can’t be enumerated. What comes after “John”? I don’t know.
To make a list containing all the natural numbers from 1 to 20, you just write [1..20].
That is the equivalent of writing [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] and there’s no difference between writing one or the other except that writing out long enumeration sequences manually is stupid. – Learn You a Haskell
[1..20]
-- Result: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
['a'..'z'] -- Result: "abcdefghijklmnopqrstuvwxyz"
['K'..'Z'] -- Result: "KLMNOPQRSTUVWXYZ"
4. Tuple Functions
Haskell’s tuples allow you to group values of different types together. Functions like fst
and snd
help you extract the individual components of a tuple.
Functions that operate on pairs of values (tuples):
fst
: Returns the first element of a pair.
fst (1, 2) -- Result: 1
snd
: Returns the second element of a pair.
snd (1, 2) -- Result: 2
5. Higher-Order Functions
Higher-order functions, which can take other functions as arguments or return them as results, are a hallmark of functional programming. Haskell provides powerful higher-order functions like map
, filter
, and foldl
to process lists in a functional style.
Functions that take other functions as arguments or return them:
map
: Applies a function to each element of a list.
map (+1) [1, 2, 3] -- Result: [2, 3, 4]
filter
: Filters elements of a list based on a predicate.
filter (>2) [1, 2, 3, 4] -- Result: [3, 4]
foldl
: Reduces a list from the left using a binary function.
foldl (+) 0 [1, 2, 3] -- Result: 6
foldr
: Reduces a list from the right using a binary function.
foldr (+) 0 [1, 2, 3] -- Result: 6
6. Input/Output Functions
Although Haskell emphasizes pure functions, it also supports basic input and output (I/O) operations. These functions allow you to interact with users or external systems by reading inputs or printing results.
Functions for basic I/O in Haskell:
print
: Outputs a value to the console.
print 5 -- Prints: 5
putStrLn
: Outputs a string followed by a newline.
putStrLn "Hello, World!" -- Prints: Hello, World!
getLine
: Reads a line of input from the user.
name <- getLine -- Reads user input and stores it in `name`
7. Function Composition
Function composition is a key concept in Haskell, allowing you to combine multiple functions into a single function. This results in cleaner, more expressive code by chaining operations together.
You can use .
to compose functions. This means combining multiple functions into one.
.
: Function composition operator.
(map (*2) . filter (>3)) [1, 2, 3, 4, 5] -- Result: [8, 10]
Conclusion
Understanding the basic functions of Haskell is the first step toward harnessing the full power of this functional programming language. With a solid grasp of mathematical operations, list manipulations, boolean logic, and higher-order functions, you can write more efficient, elegant, and maintainable code. The power of Haskell lies in its expressiveness and ability to abstract complex ideas into simple functions, making it a unique and rewarding language to learn.
By mastering these basic functions, you’ll be well on your way to exploring more advanced topics in Haskell, such as type classes, monads, and pure functional programming principles. So dive into these fundamentals, experiment with them in the GHCI, and see how they can simplify and improve your approach to problem-solving!
Leave a Reply