Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

Unit 1: Tuples, Lists, Input/Output & Control Structures

Lesson 4 of 17 in the free Functional & Logic Programming notes on Siksha Sarovar, written by Rohit Jangra.

3.1 Tuples — fixed-size, mixed-type packages

A tuple groups a fixed number of values that may have different types:

(3, "三", True)        :: (Int, String, Bool)
("Asha", 19)            :: (String, Int)

For pairs, the standard projection functions are fst and snd:

ghci> fst ("Asha", 19)
"Asha"
ghci> snd ("Asha", 19)
19

Tuples are how a Haskell function "returns multiple values":

divMod' :: Int -> Int -> (Int, Int)
divMod' a b = (a `div` b, a `mod` b)

3.2 Lists — variable length, single type

A list holds any number of values of the same type:

[1, 2, 3, 4]          :: [Int]
['h', 'i']            :: [Char]      -- same as "hi"
[(1,"one"), (2,"two")] :: [(Int, String)]
[[1,2],[3]]           :: [[Int]]

The most important fact about lists: every list is built from two constructors —

  • [] — the empty list ("nil");
  • x : xs — "cons": element x stuck on the front of list xs.

So [1,2,3] is sugar for 1 : (2 : (3 : [])). This structure is exactly what pattern matching (Unit 2) takes apart.

Handy built-ins to know now: head, tail, length, reverse, ++ (append), take, drop, elem, sum, product, and ranges like [1..10] and [2,4..20].

3.3 Control structures, the functional way

There are no for/while loops. Haskell's "control structures" are:

NeedHaskell tool
Two-way branchif cond then e1 else e2 (an expression!)
Multi-way branchguards `cond = ...`
Branch by shape of datapattern matching / case ... of
Repetitionrecursion
"Do this to every element"higher-order functions: map, filter, foldr

case expression example:

describe :: Int -> String
describe n = case n of
               0 -> "zero"
               1 -> "one"
               _ -> "many"      -- _ is the wildcard pattern

3.4 Input / Output — why it needs special treatment

A pure function cannot "read the keyboard": its result would then depend on something other than its arguments, breaking referential transparency. Haskell's solution: I/O actions are values of the special type IO adescriptions of side-effecting actions that produce an a when executed.

ActionTypeMeaning
putStr s, putStrLn sString -> IO ()print a string (with newline)
getLineIO Stringread one line from the keyboard
print xShow a => a -> IO ()print any showable value

Actions are sequenced with do notation:

main :: IO ()
main = do
  putStrLn "What is your name?"
  name <- getLine                       -- run getLine, bind result to name
  putStrLn ("Hello, " ++ name ++ "!")
  putStrLn "Enter a number:"
  s <- getLine
  let n = read s :: Int                 -- read: String -> Int conversion
  print (n * n)

Key points the examiner checks:

  • name <- getLine runs an action and captures its result; let n = ... is just a local pure definition (no <-).
  • read converts a String to a number; show converts a value to a String.
  • The pure world (Int -> Int) and the impure world (IO) are separated by the type system itself — you can always tell from a function's type whether it can perform I/O. This is the famous Haskell slogan: purity is enforced, not requested.

3.5 Putting it together — the evaluation pipeline

Well-designed Haskell programs keep a thin I/O shell around a large pure core — the same architecture (ports-and-adapters) that modern backend engineering recommends.

3.6 Tuples vs lists — the comparison the exam expects

PropertyTupleList
Lengthfixed at compile timeany length, even infinite
Element typesmay all differmust all be the same
Type examples(Int, String), (Bool, Char, Int)[Int], [String]
Length is part of the type?yes — (1,2) and (1,2,3) have different typesno — [1,2] and [1,2,3] share [Int]
Taken apart byfst/snd or pattern (x, y)head/tail or pattern (x:xs)
Typical useone record-like valuea collection to iterate over

Rule of thumb: a tuple answers "and" (a name and an age); a list answers "many" (many ages).

3.7 More list machinery you should recognise on sight

ghci> [1..6]                  -- arithmetic sequence
[1,2,3,4,5,6]
ghci> [1,3..11]               -- with a step
[1,3,5,7,9,11]
ghci> ['a'..'e']
"abcde"
ghci> take 3 (repeat 0)       -- repeat: infinite copies
[0,0,0]
ghci> replicate 4 'x'
"xxxx"
ghci> splitAt 2 [1,2,3,4]
([1,2],[3,4])
ghci> words "to be or not"
["to","be","or","not"]
ghci> unwords ["to","be"]
"to be"
ghci> lines "one\ntwo"
["one","two"]

words/unwords and lines/unlines are the bridge between raw input text and list processing — almost every I/O exercise uses them.

3.8 A complete interactive program — worked example

"Read n, then read n numbers, print their average." The pure part is one line; everything else is shell:

average :: [Double] -> Double                  -- PURE core
average xs = sum xs / fromIntegral (length xs)

main :: IO ()
main = do
  putStrLn "How many numbers?"
  s <- getLine
  let n = read s :: Int
  xs <- getNumbers n                            -- impure shell
  putStrLn ("Average = " ++ show (average xs))

getNumbers :: Int -> IO [Double]
getNumbers 0 = return []                        -- return wraps a pure value in IO
getNumbers n = do
  s  <- getLine
  rest <- getNumbers (n - 1)
  return (read s : rest)

Two points the examiner checks here:

  • return is not a jump! It is an ordinary function return :: a -> IO a that builds an action producing a value and doing nothing else. Code after it still runs.
  • Repetition in IO is done by recursion on a counter (getNumbers), exactly like pure recursion — there is still no loop statement.

3.9 case vs guards vs patterns — choosing the right branch tool

SituationBest tool
Decide by the shape/constructor of datapattern matching in equations
Decide by a boolean condition on valuesguards
Pattern-match in the middle of an expressioncase ... of
Simple two-way value choiceif ... then ... else

These compile to the same thing; the choice is purely about readability — but exams do ask you to rewrite one form into another, so practise converting the grade example between guards and if chains.

Exam pointers

  • "Why does I/O need special treatment in a pure language?" — answer with: referential transparency (§1.7 of the paradigm lesson) would break; therefore actions are values of type IO a, sequenced by do; purity is visible in types.
  • "Differentiate tuple and list" — reproduce the table in §3.6 with one example each.
  • A do-block program reading input and printing a computed result (like §3.8) is a standard 5-mark write-up; keep the pure function separate to collect design marks.

Self-check

  1. What is the type of ("roll", 42, True)? And of [("a",1), ("b",2)]?
  2. Why is name <- getLine not the same as let name = getLine?
  3. Write swap :: (a, b) -> (b, a) using a pattern.
  4. Desugar [1,2,3] into cons applications.
  5. What does return () do inside a do block — and what does it not do?