Haskell Interview Questions with Answers
Most Asked Haskell Interview Questions for Software Engineer Roles
Haskell Interview Questions & Answers
This page provides a comprehensive collection of Haskell interview questions and answers, curated for software engineers, functional programming enthusiasts, backend developers, and candidates preparing for technical interviews that emphasise pure functional programming, strong static typing, and modern software design. Haskell is a **purely functional, statically typed programming language** renowned for its expressive type system, lazy evaluation, and elegant handling of side effects through monads. It is widely used in finance, blockchain, compiler construction, and data‑intensive applications where correctness, maintainability, and concurrency are paramount. Haskell’s emphasis on immutability and referential transparency leads to more predictable and testable code, making it a favourite for high‑assurance systems. This guide covers beginner, intermediate, and advanced Haskell topics – from basic syntax, algebraic data types, and pattern matching to monads, type families, GADTs, and real‑world performance considerations. Whether you're new to functional programming or a seasoned Haskeller, these questions will help you master the language and ace your next interview.
Why Learn Haskell?
- Purely functional paradigm – write side‑effect‑free, composable, and referentially transparent code
- Strong static type system with type inference – catch errors at compile time and enforce program correctness
- Lazy evaluation – enables infinite data structures, modularity, and efficient resource usage
- Concurrency and parallelism – lightweight threads, software transactional memory (STM), and async I/O
- Rich ecosystem – libraries for web development (Servant, Yesod), data science, finance, and more
- Used in industry – companies like Standard Chartered, Barclays, Facebook, and GitHub rely on Haskell
- Excellent for DSLs – elegant syntax and powerful type‑level programming for custom domain languages
Top 100 Haskell Interview Questions
Haskell is a purely functional, statically typed programming language with lazy evaluation and strong type inference.
- Purely Functional: Functions have no side effects
- Lazy Evaluation: Expressions evaluated only when needed
- Strong Static Typing: Type safety with type inference
- Immutable Data: Data cannot be modified after creation
- Pattern Matching: Elegant data destructuring
// Haskell Q1: What is Haskell and what are its key features?
"Haskell is a purely functional, statically typed programming language
with lazy evaluation and strong type inference. Key features include:
1. Purely Functional: Functions have no side effects
2. Lazy Evaluation: Expressions evaluated only when needed
3. Strong Static Typing: Type safety with type inference
4. Immutable Data: Data cannot be modified after creation
5. Pattern Matching: Elegant data destructuring
6. Type Classes: Ad-hoc polymorphism
7. Monads: Handling side effects and composition
8. Higher-Order Functions: Functions as first-class citizens
9. Algebraic Data Types: Sum and product types
10. GHC: Glasgow Haskell Compiler"Both 'let' and 'where' define local bindings, but differ in placement and scope.
- let: Appears before the expression, narrower scope
- where: Appears after the expression, can span multiple guards
- Readability: 'where' often more readable for pattern matching
- Composition: 'let' can be used anywhere expressions are allowed
// Haskell Q2: What is the difference between let and where in Haskell?
"Both 'let' and 'where' define local bindings, but differ in placement:
'let' is an expression:
let x = 5
y = 10
in x + y
'where' is a clause:
calculate x = result
where result = x * 2
Key Differences:
1. Placement: 'let' appears before the expression, 'where' after
2. Scope: 'let' has narrower scope, 'where' can span multiple guards
3. Readability: 'where' often more readable for pattern matching
4. Composition: 'let' can be used anywhere expressions are allowed
Examples:
-- let binding
add x y = let sum = x + y in sum * 2
-- where binding
add x y = result * 2
where result = x + y
-- Pattern matching with where
max x y | x > y = x
| otherwise = y
where diff = abs (x - y)"Algebraic Data Types (ADTs) combine product and sum types to create complex data structures.
- Product Types: Combine multiple values (AND)
- Sum Types: Choose between alternatives (OR)
- Pattern Matching: Deconstruct ADTs
- Type Safety: Compile-time guarantees
// Haskell Q3: What are algebraic data types?
"Algebraic Data Types (ADTs) combine product and sum types:
1. Product Types (AND): Combine multiple values
data Person = Person String Int -- Name and Age
2. Sum Types (OR): Choose between alternatives
data Bool = True | False
3. Both together:
data Shape = Circle Float | Rectangle Float Float
Key Features:
- Pattern matching for deconstruction
- Type safety at compile time
- Represent domain models clearly
Examples:
data Maybe a = Nothing | Just a
data Either a b = Left a | Right b
data Tree a = Empty | Node a (Tree a) (Tree a)
-- Using pattern matching
describeShape (Circle r) = "Circle with radius " ++ show r
describeShape (Rectangle w h) = "Rectangle " ++ show w ++ "x" ++ show h"Monads are a design pattern for handling side effects and sequencing computations in a pure functional way.
- Maybe: Handle possible failure
- List: Handle multiple values
- IO: Handle input/output
- State: Handle stateful computations
// Haskell Q4: What is a Monad?
"A Monad is a design pattern for handling side effects and sequencing:
Monad Laws:
1. Left identity: return a >>= f = f a
2. Right identity: m >>= return = m
3. Associativity: (m >>= f) >>= g = m >>= (\x -> f x >>= g)
Key Monads in Haskell:
1. Maybe: Handle possible failure
2. List: Handle multiple values
3. IO: Handle input/output
4. State: Handle stateful computations
5. Either: Handle exceptions
6. Reader: Handle configuration
7. Writer: Handle logging
8. Cont: Handle continuations
Basic Monad Operations:
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
Example:
-- Maybe Monad
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
-- Using do notation
calculate :: Int -> Int -> Maybe Int
calculate x y = do
z <- safeDiv x y
w <- safeDiv z 2
return w"Lazy evaluation means expressions are evaluated only when needed, enabling infinite data structures and improved modularity.
- Thunks: Delayed computations
- Memoization: Results cached after first evaluation
- Infinite Data: Can work with infinite lists
- Efficiency: Avoids unnecessary computations
// Haskell Q5: What is lazy evaluation?
"Lazy evaluation means expressions are evaluated only when needed:
Key Concepts:
1. Thunks: Delayed computations
2. Memoization: Results cached after first evaluation
3. Infinite Data: Can work with infinite lists
4. Efficiency: Avoids unnecessary computations
Benefits:
1. Performance: Only compute what's needed
2. Modularity: Separate generation from consumption
3. Infinite structures: Work with infinite data
Example:
-- Infinite list of numbers
ones = 1 : ones
-- Take first 5 elements (only evaluates what's needed)
take 5 ones -- [1,1,1,1,1]
-- Fibonacci sequence (infinite)
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs -- [0,1,1,2,3,5,8,13,21,34]
-- Only evaluates until condition met
firstEven = head (filter even [1..])
Drawbacks:
1. Space leaks: When thunks accumulate
2. Performance unpredictability
3. Debugging difficulty"Type classes provide ad-hoc polymorphism in Haskell, similar to interfaces in OOP languages.
- Eq: Equality operations
- Ord: Ordering operations
- Show: String conversion
- Read: Parsing from strings
- Num: Numeric operations
- Functor: Mapping operations
- Applicative: Sequential application
- Monad: Sequencing operations
// Haskell Q6: What are type classes?
"Type classes provide ad-hoc polymorphism:
Key Type Classes:
1. Eq: Equality (==)
2. Ord: Ordering (compare)
3. Show: String conversion (show)
4. Read: Parsing (read)
5. Num: Numeric operations
6. Functor: Map (fmap)
7. Applicative: Sequential application
8. Monad: Sequencing
Examples:
-- Defining a type class
class Printable a where
print :: a -> String
-- Instantiating a type class
instance Printable Int where
print n = "Int: " ++ show n
instance Printable Bool where
print True = "True"
print False = "False"
-- Using type class constraints
showPrintable :: Printable a => a -> String
showPrintable x = "Value: " ++ print x
-- Deriving type classes
data Person = Person String Int
deriving (Eq, Show, Ord)"Pattern matching deconstructs data structures and binds variables to values.
- Literal Patterns: Match specific values
- Variable Patterns: Bind to any value
- Wildcard Patterns: Match anything (ignored)
- Constructor Patterns: Match data constructors
- List Patterns: Match list structures
- Tuple Patterns: Match tuple structures
- As-patterns: Bind entire pattern
// Haskell Q7: What is pattern matching?
"Pattern matching deconstructs data structures:
Basic Patterns:
1. Literal: matches specific values
2. Variable: matches any value
3. Wildcard: matches any (ignored)
4. Constructor: matches data constructors
5. List: matches list patterns
6. Tuple: matches tuple patterns
7. As-pattern: binds entire pattern
Examples:
-- Literal pattern
isZero 0 = True
isZero _ = False
-- Constructor pattern
data Maybe a = Nothing | Just a
extractValue (Just x) = x
extractValue Nothing = error "Nothing"
-- List pattern
sumList [] = 0
sumList (x:xs) = x + sumList xs
-- Tuple pattern
addPair (x, y) = x + y
-- As-pattern
duplicateList lst@(x:xs) = lst ++ xs
-- Multiple patterns
describe x = case x of
0 -> "Zero"
1 -> "One"
_ -> "Other"foldl and foldr differ in evaluation order and associativity.
- foldl: Left fold, strict evaluation, can overflow stack
- foldr: Right fold, lazy evaluation, works with infinite lists
- foldl': Strict version of foldl (recommended)
- Performance: foldl' for numeric operations, foldr for construction
// Haskell Q8: What is the difference between foldl and foldr?
"foldl and foldr differ in evaluation order and associativity:
foldl (left fold):
foldl :: (b -> a -> b) -> b -> [a] -> b
- Associates to the left
- Strict evaluation
- Can cause stack overflow on large lists
foldr (right fold):
foldr :: (a -> b -> b) -> b -> [a] -> b
- Associates to the right
- Lazy evaluation
- Works with infinite lists
Examples:
-- foldl
foldl (+) 0 [1,2,3] -- (((0+1)+2)+3)
-- foldr
foldr (+) 0 [1,2,3] -- (1+(2+(3+0)))
-- Difference for large lists
-- foldl' is strict version of foldl (recommended)
foldl' (+) 0 [1..1000000] -- Efficient
-- Using foldr with infinite lists
take 5 (foldr (:) [] [1..]) -- [1,2,3,4,5] (works)
take 5 (foldl (flip (:)) [] [1..]) -- Doesn't terminate"Higher-order functions take functions as arguments or return functions.
- map: Apply function to each element
- filter: Select elements based on predicate
- foldl/foldr: Reduce list to single value
- composition: Function composition (.)
- application: Function application ($)
// Haskell Q9: What are higher-order functions?
"Higher-order functions take functions as arguments or return functions:
Common Higher-Order Functions:
1. map: Apply function to each element
2. filter: Select elements based on predicate
3. foldl/foldr: Reduce list to single value
4. (.) : Function composition
5. ($) : Function application
6. flip: Reverse function arguments
7. const: Constant function
8. id: Identity function
Examples:
-- map
map (*2) [1,2,3,4] -- [2,4,6,8]
-- filter
filter even [1..10] -- [2,4,6,8,10]
-- Function composition
compose = (.) :: (b -> c) -> (a -> b) -> a -> c
result = (sum . map (*2) . filter even) [1..10]
-- Creating functions
add = (+)
add5 = add 5
add5 10 -- 15
-- Returning functions
makeAdder :: Int -> (Int -> Int)
makeAdder x = \y -> x + y
add10 = makeAdder 10
add10 5 -- 15"State Monad handles stateful computations in a pure functional way.
- get: Get current state
- put: Set new state
- modify: Update state
- state: Create State computation
// Haskell Q10: What is the State Monad?
"The State Monad handles stateful computations in a pure functional way:
State Monad Definition:
newtype State s a = State { runState :: s -> (a, s) }
Key Functions:
1. get: Get current state
2. put: Set new state
3. modify: Update state
4. state: Create State computation
Examples:
-- Counter with State Monad
import Control.Monad.State
increment :: State Int Int
increment = do
count <- get
put (count + 1)
return count
-- Using State
runState increment 0 -- (0,1)
-- Complex state
data AppState = AppState { counter :: Int, values :: [Int] }
updateState :: State AppState ()
updateState = do
modify (\s -> s { counter = counter s + 1 })
modify (\s -> s { values = counter s : values s })
-- More practical example
fibonacci :: Int -> State Int Int
fibonacci 0 = return 0
fibonacci n = do
prev <- fibonacci (n-1)
current <- get
put (prev + current)
return current"Reader Monad provides a way to pass configuration or environment through computations.
- reader: Create Reader computation
- ask: Get the environment
- local: Modify environment
- Uses: Configuration management, dependency injection
// Haskell Q11: What is the Reader Monad?
"The Reader Monad provides a way to pass configuration or environment:
Reader Definition:
newtype Reader r a = Reader { runReader :: r -> a }
Key Functions:
1. reader: Create Reader computation
2. ask: Get the environment
3. local: Modify environment
Examples:
import Control.Monad.Reader
-- Configuration
data Config = Config { verbose :: Bool, logLevel :: Int }
-- Computation with config
logMessage :: String -> Reader Config ()
logMessage msg = do
config <- ask
if verbose config
then liftIO (putStrLn ("[LOG] " ++ msg))
else return ()
-- Environment function
calculate :: Reader Config Int
calculate = do
config <- ask
return (logLevel config * 2)
-- Using Reader
runReader calculate (Config True 3) -- 6
-- Composing Reader
withVerbose :: Reader Config a -> Reader Config a
withVerbose action = local (\c -> c { verbose = True }) action"Writer Monad collects output or logging alongside computation.
- writer: Create Writer computation
- tell: Add output
- listen: Listen to output
- pass: Modify output
- Uses: Logging, tracing, accumulating results
// Haskell Q12: What is the Writer Monad?
"The Writer Monad collects output or logging alongside computation:
Writer Definition:
newtype Writer w a = Writer { runWriter :: (a, w) }
Key Functions:
1. writer: Create Writer computation
2. tell: Add output
3. listen: Listen to output
4. pass: Modify output
Examples:
import Control.Monad.Writer
-- Logging with Writer
calculateSum :: [Int] -> Writer [String] Int
calculateSum [] = return 0
calculateSum (x:xs) = do
tell ["Adding " ++ show x]
rest <- calculateSum xs
return (x + rest)
-- Using Writer
let (result, log) = runWriter (calculateSum [1,2,3,4])
-- result = 10
-- log = ["Adding 1","Adding 2","Adding 3","Adding 4"]
-- Multiple writers
analysis :: Int -> Writer (String, [Int]) Int
analysis n = do
tell ("Processing: " ++ show n, [n])
return (n * 2)
-- Combining logs
process :: Int -> Writer [String] Int
process n = do
tell ["Step 1"]
let x = n * 2
tell ["Step 2: " ++ show x]
return x"Maybe Monad handles computations that might fail.
- Nothing: Represents failure
- Just: Represents success with value
- maybe: Pattern match on Maybe
- fromMaybe: Extract value with default
- Uses: Error handling, optional values
// Haskell Q13: What is the Maybe Monad?
"The Maybe Monad handles computations that might fail:
Maybe Definition:
data Maybe a = Nothing | Just a
Key Functions:
1. maybe: Pattern match on Maybe
2. fromMaybe: Extract value with default
3. isJust/isNothing: Check status
4. catMaybes: Filter out Nothings
5. mapMaybe: Map and filter
Examples:
-- Safe division
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
-- Chaining computations
calculate :: Int -> Int -> Maybe Int
calculate x y = do
a <- safeDiv x y
b <- safeDiv a 2
return b
-- Using maybe
maybeResult = maybe 0 id (Just 5) -- 5
-- Error handling with do notation
processValue :: Maybe Int -> Maybe Int -> Maybe Int
processValue mx my = do
x <- mx
y <- my
return (x + y)
-- Alternative to failure
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
-- Using Maybe in practice
fetchUser :: Int -> Maybe String
fetchUser 1 = Just "Alice"
fetchUser 2 = Just "Bob"
fetchUser _ = Nothing"Either Monad handles computations that can fail with an error message.
- Left: Represents error
- Right: Represents success
- either: Pattern match on Either
- Uses: Error handling with messages
// Haskell Q14: What is the Either Monad?
"The Either Monad handles computations that can fail with an error:
Either Definition:
data Either a b = Left a | Right b
Key Functions:
1. either: Pattern match on Either
2. isLeft/isRight: Check status
3. fromLeft/fromRight: Extract values
4. lefts/rights: Filter lists
Examples:
-- Error handling with Either
safeDiv :: Int -> Int -> Either String Int
safeDiv _ 0 = Left "Division by zero"
safeDiv x y = Right (x `div` y)
-- Chaining with do notation
calculate :: Int -> Int -> Either String Int
calculate x y = do
a <- safeDiv x y
b <- safeDiv a 2
return b
-- Using Either
processValue :: Either String Int -> Either String Int
processValue (Left err) = Left ("Error: " ++ err)
processValue (Right val) = Right (val * 2)
-- Validation
validateAge :: Int -> Either String Int
validateAge age
| age < 0 = Left "Negative age"
| age > 150 = Left "Too old"
| otherwise = Right age
-- Using either
handleResult = either (error . show) (*2) (Right 5)
-- Monad transformer for Either
type MyMonad = EitherT String IO"Applicative functors allow sequential application of functions in a context.
- pure: Lift value into applicative
- <*>: Apply function in context
- <$>: Functor map
- Uses: Validation, parsing, sequencing
// Haskell Q15: What are applicative functors?
"Applicative functors allow sequential application of functions:
Applicative Definition:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Key Functions:
1. pure: Lift value into applicative
2. <*>: Apply function in context
3. <$>: Functor map (same as fmap)
4. <$>: Alias for fmap
5. *>: Sequence ignoring left result
6. <*: Sequence ignoring right result
Examples:
-- Using Applicative with Maybe
justAdd = Just (+)
justAdd <*> Just 3 <*> Just 5 -- Just 8
-- Using Applicative with lists
(*) <$> [1,2,3] <*> [4,5,6] -- [4,5,6,8,10,12,...]
-- Validation with Applicative
data Person = Person { name :: String, age :: Int }
validateName :: String -> Maybe String
validateName name
| null name = Nothing
| otherwise = Just name
validateAge :: Int -> Maybe Int
validateAge age
| age < 0 || age > 150 = Nothing
| otherwise = Just age
createPerson :: String -> Int -> Maybe Person
createPerson name age = Person <$> validateName name <*> validateAge age
-- Applicative laws
-- Identity: pure id <*> v = v
-- Composition: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
-- Homomorphism: pure f <*> pure x = pure (f x)
-- Interchange: u <*> pure y = pure ($ y) <*> u"Functors represent containers or computations that can be mapped over.
- fmap: Apply function to inner value
- <$>: Infix version of fmap
- Laws: Identity and composition
- Uses: Mapping over data structures
// Haskell Q16: What are functors?
"Functors represent containers or computations that can be mapped:
Functor Definition:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Key Functions:
1. fmap: Apply function to inner value
2. (<$>): Infix version of fmap
3. ($>): Replace inner value
4. (<&>): Flipped version of fmap
Functor Laws:
1. Identity: fmap id = id
2. Composition: fmap (f . g) = fmap f . fmap g
Examples:
-- Functor instances
fmap (*2) [1,2,3] -- [2,4,6]
fmap (+1) (Just 5) -- Just 6
fmap show (Right 10) -- Right "10"
-- Using <$>
(*2) <$> [1,2,3] -- [2,4,6]
-- Functor composition
data MaybeList a = MaybeList (Maybe [a])
instance Functor MaybeList where
fmap f (MaybeList Nothing) = MaybeList Nothing
fmap f (MaybeList (Just xs)) = MaybeList (Just (map f xs))
-- Using functors in practice
doubleMaybe = fmap (*2) . Just -- Just 2
doubleMaybeList = fmap (fmap (*2)) -- Double values in nested structure
-- Deriving Functor
data Tree a = Leaf a | Branch (Tree a) (Tree a)
deriving (Functor, Show)"Monad transformers combine multiple monads into one.
- MaybeT: Adds Maybe behavior
- EitherT: Adds Either behavior
- ReaderT: Adds Reader behavior
- WriterT: Adds Writer behavior
- StateT: Adds State behavior
- lift: Lift computation to transformer
// Haskell Q17: What are monad transformers?
"Monad transformers combine multiple monads into one:
Common Transformers:
1. MaybeT: Adds Maybe behavior
2. EitherT: Adds Either behavior
3. ReaderT: Adds Reader behavior
4. WriterT: Adds Writer behavior
5. StateT: Adds State behavior
6. ExceptT: Adds Exception handling
Key Functions:
1. lift: Lift computation to transformer
2. liftIO: Lift IO computation
3. runXxxT: Run the transformer
Examples:
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
type AppM = ReaderT Config (MaybeT IO)
data Config = Config { env :: String }
runApp :: AppM a -> Config -> IO (Maybe a)
runApp app config = runMaybeT (runReaderT app config)
-- Using transformer
getConfig :: AppM String
getConfig = do
config <- ask
return (env config)
-- Lifting operations
logMessage :: String -> AppM ()
logMessage msg = liftIO (putStrLn msg)
-- Combining transformers
data AppState = AppState { counter :: Int }
type MyApp = StateT AppState (ReaderT Config (MaybeT IO))
-- Using multiple transformers
incrementCounter :: MyApp ()
incrementCounter = do
modify (\s -> s { counter = counter s + 1 })
count <- gets counter
liftIO (putStrLn ("Counter: " ++ show count))"IO functions have side effects, while pure functions don't.
- Pure: No side effects, referentially transparent
- IO: Can perform I/O, not referentially transparent
- Separation: Keep IO separate from pure logic
- Testing: Pure functions are easier to test
// Haskell Q18: What is the difference between IO and pure functions?
"IO functions have side effects, pure functions don't:
Pure Functions:
- No side effects
- Same input = same output
- Referentially transparent
- Can be reasoned about mathematically
- Easy to test
IO Functions:
- Can have side effects
- Can perform input/output
- Can read/write files
- Can interact with the world
- Not referentially transparent
Examples:
-- Pure function
add :: Int -> Int -> Int
add x y = x + y -- Always same result
-- IO function
getLine :: IO String -- Reads from stdin
putStrLn :: String -> IO () -- Writes to stdout
-- Combining pure and IO
readFileAndProcess :: FilePath -> IO Int
readFileAndProcess path = do
content <- readFile path
return (length content) -- Pure processing
-- Lifting pure functions into IO
main = do
content <- readFile "file.txt"
let result = pureFunction content -- Pure
putStrLn (show result)
pureFunction :: String -> Int
pureFunction = length">>= (bind) passes the result to a function, while >> (then) sequences actions ignoring the result.
- >>=: Used when you need the result
- >>: Used for side effects only
- Do notation: Syntactic sugar for both
// Haskell Q19: What is the difference between >>= and >>?
"Both are sequencing operators but with different purposes:
(>>=) (bind):
- Takes a monadic value and a function
- Passes the value to the function
- Used when you need the result
(>>) (then):
- Sequences two monadic actions
- Ignores the result of the first
- Used for side effects only
Examples:
-- Using bind
readNumber :: IO Int
readNumber = do
line <- getLine
return (read line)
-- Using bind with function
main = do
x <- readNumber
y <- readNumber
print (x + y)
-- Using then for side effects
main = putStrLn "Start" >> putStrLn "End"
-- Equivalent using do notation
main = do
putStrLn "Start"
putStrLn "End"
-- Complex example
processFile :: FilePath -> IO ()
processFile path = do
content <- readFile path
putStrLn "File read" >> putStrLn "Processing..." >> putStrLn "Done"
-- Desugared
processFile path =
readFile path >>= \content ->
putStrLn "File read" >>
putStrLn "Processing..." >>
putStrLn "Done"Three fold variants with different evaluation strategies.
- foldl: Lazy left fold, can overflow stack
- foldl': Strict left fold, memory efficient
- foldr: Lazy right fold, works with infinite lists
// Haskell Q20: What is the difference between foldl, foldl', and foldr?
"Three fold variants with different evaluation strategies:
foldl (left fold):
- Lazy left fold
- Can cause stack overflow
- Not recommended for large lists
foldl' (strict left fold):
- Strict left fold
- Memory efficient
- Recommended for large lists
foldr (right fold):
- Lazy right fold
- Can work with infinite lists
- Good for constructing data
Examples:
-- foldl (can overflow)
foldl (+) 0 [1..1000000] -- May stack overflow
-- foldl' (safe)
import Data.List (foldl')
foldl' (+) 0 [1..1000000] -- Safe
-- foldr with infinite list
foldr (:) [] [1..] -- Can work with infinite
-- Performance comparison
sumList1 = foldl (+) 0 -- Slow, can overflow
sumList2 = foldl' (+) 0 -- Fast, safe
sumList3 = foldr (+) 0 -- Slow for large lists
-- Type signatures
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl' :: (b -> a -> b) -> b -> [a] -> b
foldr :: (a -> b -> b) -> b -> [a] -> b
-- When to use each:
-- foldl': For numeric accumulations
-- foldr: For constructing data structures
-- foldl: Rarely (use foldl' instead)
-- foldl' vs foldr
product1 = foldl' (*) 1 [1..10] -- 3628800
product2 = foldr (*) 1 [1..10] -- Same result but different evaluation"Lazy evaluation provides several benefits including infinite data structures and improved modularity.
- Infinite Data: Can work with infinite lists
- Efficiency: Only compute what's needed
- Modularity: Separate generation from consumption
- Memoization: Results cached automatically
// Haskell Q21: What are the benefits of lazy evaluation?
"Lazy evaluation provides several benefits:
Benefits:
1. Infinite Data Structures: Can work with infinite lists
2. Efficiency: Only compute what's needed
3. Modularity: Separate generation from consumption
4. Memoization: Results cached automatically
5. Composition: Easier to compose functions
Examples:
-- Infinite list
nats = [1..]
take 10 nats -- [1,2,3,4,5,6,7,8,9,10]
-- Lazy processing
firstEven = head (filter even [1..]) -- 2
-- Memoization
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
fib n = fibs !! n -- O(n) after first calculation
-- Modular design
generateNumbers = [1..]
processNumbers = take 20 . filter odd . map (*2)
-- Only evaluates necessary elements
evaluate = processNumbers generateNumbers
-- Space efficiency
sumSquares = sum . map (^2) . takeWhile (<100) -- Only evaluates needed"Monad and Applicative differ in expressiveness and capabilities.
- Applicative: Sequential application, no dependencies
- Monad: Sequential composition with dependencies
- Every Monad is Applicative, but not vice versa
// Haskell Q22: What is the difference between monad and applicative?
"Monad and Applicative differ in expressiveness:
Applicative:
- Sequential application of pure functions
- No dependency between computations
- Cannot use previous results to determine next
Monad:
- Sequential composition with dependencies
- Can use results to determine next computation
- More powerful than Applicative
Examples:
-- Applicative style
validateAndCreate :: Maybe String -> Maybe Int -> Maybe Person
validateAndCreate name age = Person <$> name <*> age
-- Monad style
processUser :: Int -> Maybe String
processUser id = do
user <- fetchUser id
if user == "admin"
then Just "Admin access"
else Just "User access"
-- Applicative can't do this:
getAndProcess :: IO Int
getAndProcess = do
x <- getLine >>= return . read
y <- getLine >>= return . read
return (x + y)
-- Every Monad is Applicative, but not every Applicative is Monad
-- Monad provides: (>>=) :: m a -> (a -> m b) -> m b
-- Applicative provides: (<*>) :: f (a -> b) -> f a -> f b"Type synonyms provide alternative names for existing types.
- Readability: Domain-specific names
- Documentation: Self-documenting code
- Abstraction: Hide implementation details
// Haskell Q23: What are type synonyms?
"Type synonyms provide alternative names for existing types:
Type Synonym Syntax:
type Name = ExistingType
Benefits:
1. Improved readability
2. Domain-specific names
3. Documentation
4. Abstraction
Examples:
-- Basic type synonyms
type String = [Char]
type FilePath = String
type UserId = Int
-- More complex synonyms
type Name = String
type Age = Int
type Address = String
type Person = (Name, Age, Address)
-- Using type synonyms
getUserInfo :: UserId -> IO (String, String)
getUserInfo uid = do
name <- getUserName uid
email <- getUserEmail uid
return (name, email)
-- Nested synonyms
type NameList = [String]
type NameListList = [NameList]
-- Synonyms with parameters
type AssocList k v = [(k, v)]
type MapFunc k v = k -> v
-- Practical example
data Database = Database
type Query = String
type Result = [String]
executeQuery :: Database -> Query -> IO Result"Newtypes create new types with the same runtime representation.
- Type Safety: Different types for different purposes
- Zero Overhead: No runtime cost
- Different Instances: Different type class instances
// Haskell Q24: What are newtypes?
"Newtypes create new types with the same runtime representation:
Newtype Syntax:
newtype MyType = MyType ExistingType
Benefits:
1. Type safety without overhead
2. Different instances for same representation
3. Zero-cost abstraction
Examples:
-- Creating newtypes
newtype Username = Username String
newtype Password = Password String
newtype Age = Age Int
-- Type safety
login :: Username -> Password -> Bool
login (Username u) (Password p) = u == "admin" && p == "secret"
-- Different instances
newtype Identity a = Identity a
newtype Maybe a = Just a | Nothing
-- Deriving instances
newtype Age = Age Int
deriving (Eq, Ord, Show, Num)
-- Using newtypes for safety
newtype Meters = Meters Double
newtype Kilometers = Kilometers Double
toMeters :: Kilometers -> Meters
toMeters (Kilometers km) = Meters (km * 1000)
-- Newtype vs Type
type StringAlias = String -- Same type
newtype StringWrapper = StringWrapper String -- New type"Newtype and data have different characteristics and use cases.
- Newtype: One constructor, one field, zero overhead
- Data: Multiple constructors, multiple fields, runtime overhead
- Use: Newtype for type safety, Data for ADTs
// Haskell Q25: What is the difference between newtype and data?
"Newtype and data have different characteristics:
Newtype:
- Only one constructor
- Only one field
- Zero runtime overhead
- Can only be used for new types
Data:
- Multiple constructors
- Multiple fields
- Runtime overhead
- Can define algebraic data types
Examples:
-- Newtype (one constructor, one field)
newtype Age = Age Int
newtype Name = Name String
-- Data (multiple constructors, multiple fields)
data Person = Person Name Age
data Maybe a = Nothing | Just a
data Tree a = Leaf | Node a (Tree a) (Tree a)
-- Newtype with deriving
newtype Seconds = Seconds Int
deriving (Eq, Ord, Num, Show)
-- Data with deriving
data Color = Red | Green | Blue
deriving (Eq, Show)
-- Performance difference
newtype NewType = NT Int -- Compiles to Int
data DataType = DT Int -- Compiles to wrapper structure
-- When to use newtype vs data
-- Newtype: When you want type safety and the type has one field
-- Data: When you need multiple constructors or fields"Generalized Algebraic Data Types (GADTs) allow precise type specification.
- Type-safe DSLs: Domain specific languages
- Better Type Inference: More precise types
- Expressiveness: More powerful than regular ADTs
// Haskell Q26: What are GADTs?
"Generalized Algebraic Data Types (GADTs) allow precise type specification:
GADT Syntax:
data Gadt a where
Constructor :: Type -> Gadt Type
Benefits:
1. Type-safe DSLs
2. Better type inference
3. More expressive types
Examples:
-- Simple GADT
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
If :: Expr Bool -> Expr a -> Expr a -> Expr a
-- Type-safe evaluation
eval :: Expr a -> a
eval (IntLit n) = n
eval (BoolLit b) = b
eval (Add e1 e2) = eval e1 + eval e2
eval (If cond e1 e2) = if eval cond then eval e1 else eval e2
-- Using GADT for lists with types
data List a where
Nil :: List a
Cons :: a -> List a -> List a
-- Type-safe equality
data Equal a b where
Refl :: Equal a a
cast :: Equal a b -> a -> b
cast Refl x = x"Phantom types are type parameters not used in data constructors.
- Type Safety: Enforce constraints at compile time
- Documentation: Document intent
- Use Cases: Units, validation, state machines
// Haskell Q27: What are phantom types?
"Phantom types are type parameters not used in data constructors:
Phantom Type Definition:
data Phantom a = PhantomValue
Benefits:
1. Type safety at compile time
2. Enforce constraints
3. Document intent
Examples:
-- Phantom type for units
data Meters
data Kilometers
data Distance a = Distance Double
toMeters :: Distance Kilometers -> Distance Meters
toMeters (Distance km) = Distance (km * 1000)
-- Phantom type for validation
data Validated
data Unvalidated
data User a = User { name :: String, age :: Int }
validateUser :: User Unvalidated -> Maybe (User Validated)
validateUser user = if age user >= 18 then Just user else Nothing
-- Phantom type for state
data Unlocked
data Locked
data Door s = Door { isOpen :: Bool }
openDoor :: Door Locked -> Door Unlocked
openDoor (Door _) = Door True
-- Type-safe file operations
data Open
data Closed
data FileHandle s = FileHandle String
openFile :: String -> FileHandle Open
closeFile :: FileHandle Open -> FileHandle Closed"$ (function application) and . (function composition) serve different purposes.
- $: Applies function to argument, low precedence
- .: Composes two functions, high precedence
- Use: $ for chaining, . for composition
// Haskell Q28: What is the difference between $ and . in Haskell?
"$ (function application) and . (function composition) are different:
$ (application):
- Applies function to argument
- Low precedence, right associative
- Helps avoid parentheses
- Function: ($) :: (a -> b) -> a -> b
. (composition):
- Composes two functions
- High precedence, right associative
- Creates new function
- Function: (.) :: (b -> c) -> (a -> b) -> a -> c
Examples:
-- Without $ (needs parentheses)
sum (map (*2) (filter even [1..10]))
-- With $ (no parentheses)
sum $ map (*2) $ filter even [1..10]
-- Function composition
f = sqrt . abs . negate -- Composition
f 5 -- sqrt (abs (-5))
-- Combining $ and .
process = sum . map (*2) . filter even
result = process [1..10]
-- More examples
map (\x -> x + 1) [1,2,3] -- Without $
map (+1) [1,2,3] -- Using operator section
-- $ for chaining
doSomething = head . sort . map sqrt $ filter (>0) [1..10]
-- $ vs .
-- $: f $ x = f x
-- .: (f . g) x = f (g x)"Guards are a way to conditionally select function definitions.
- Conditional Logic: Multiple conditions
- Pattern Matching: With conditions
- otherwise: Default case
// Haskell Q29: What are guards?
"Guards are a way to conditionally select function definitions:
Guard Syntax:
functionName pattern
| condition1 = result1
| condition2 = result2
| otherwise = default
Benefits:
1. Clean conditional logic
2. Pattern matching with conditions
3. Better readability
Examples:
-- Basic guards
max :: Ord a => a -> a -> a
max x y
| x > y = x
| otherwise = y
-- Multiple guards
grade :: Int -> String
grade score
| score >= 90 = "A"
| score >= 80 = "B"
| score >= 70 = "C"
| score >= 60 = "D"
| otherwise = "F"
-- Guards with pattern matching
describeList :: [Int] -> String
describeList [] = "Empty"
describeList (x:xs)
| x == 0 = "Starts with zero"
| length xs > 3 = "Long list"
| otherwise = "Normal list"
-- Guards in do notation
processInput :: IO ()
processInput = do
line <- getLine
let n = read line
putStrLn $ case n of
_ | n < 0 -> "Negative"
| n == 0 -> "Zero"
| n > 0 -> "Positive"
-- Guards with where
discriminant :: Float -> Float -> Float -> Float
discriminant a b c = b^2 - 4*a*c
roots :: Float -> Float -> Float -> (Float, Float)
roots a b c
| d < 0 = error "No real roots"
| otherwise = ((-b + sqrt d) / (2*a), (-b - sqrt d) / (2*a))
where d = discriminant a b c"Modules organize code and control visibility.
- module: Define module
- import: Import module
- qualified: Qualified import
- hiding: Hide specific exports
// Haskell Q30: What are modules and how do you import them?
"Modules organize code and control visibility:
Module Syntax:
module ModuleName (exports) where
-- code
Import Syntax:
import ModuleName
import qualified ModuleName as Alias
import ModuleName (function1, function2)
Benefits:
1. Code organization
2. Name space management
3. Information hiding
4. Reusability
Examples:
-- Defining a module
module MyModule (myFunction, MyType(..)) where
data MyType = Constructor1 | Constructor2
myFunction = ...
-- Exporting only certain functions
module Math (add, multiply) where
add :: Int -> Int -> Int
add x y = x + y
multiply :: Int -> Int -> Int
multiply x y = x * y
-- Import examples
import Data.List -- Import everything
import qualified Data.Map as Map -- Qualified import
import Data.Text (pack, unpack) -- Specific imports
-- Common imports
import Control.Monad
import Control.Monad.State
import Data.Maybe
import System.IO
-- Module visibility
module A (publicFunction) where
privateFunction = ...
publicFunction = privateFunction + 1
-- Importing modules with same name
import qualified Data.Map as Map
import qualified Data.HashMap as HashMap
-- Using imported functions
main :: IO ()
main = do
let m = Map.fromList [(1,"one"), (2,"two")]
print (Map.lookup 1 m)"Lists and tuples serve different purposes in Haskell.
- Lists: Homogeneous, variable length
- Tuples: Heterogeneous, fixed length
- Operations: Different operations for each
// Haskell Q31: What is the difference between list and tuple?
"Lists and tuples serve different purposes:
Lists:
- Homogeneous (same type)
- Variable length
- Can be infinite
- Operations: head, tail, map, filter
- Syntax: [1,2,3]
Tuples:
- Heterogeneous (different types)
- Fixed length
- Finite
- Operations: fst, snd
- Syntax: (1, "hello", True)
Examples:
-- Lists
list1 = [1,2,3,4,5] -- All Ints
list2 = ["a","b","c"] -- All Strings
list3 = [1..] -- Infinite list
-- Tuples
tuple1 = (1, "hello", True) -- Different types
tuple2 = (2.5, 'a', "world") -- Different types
-- List operations
head [1,2,3] -- 1
tail [1,2,3] -- [2,3]
map (*2) [1,2,3] -- [2,4,6]
-- Tuple operations
fst (1, "hello") -- 1
snd (1, "hello") -- "hello"
-- Pattern matching
sumList (x:xs) = x + sumList xs
sumTuple (x, y) = x + y
-- List comprehensions
[x*2 | x <- [1..10], x `mod` 2 == 0] -- [4,8,12,16,20]
-- When to use each
-- List: Multiple items of same type, operations needed
-- Tuple: Fixed number of items of different types"List comprehensions provide a concise way to create lists.
- Generator: variable <- list
- Guards: Conditions
- Expression: What to produce
// Haskell Q32: What are list comprehensions?
"List comprehensions provide a concise way to create lists:
List Comprehension Syntax:
[expression | variable <- list, condition]
Components:
1. Generator: variable <- list
2. Guards: condition
3. Expression: What to produce
Benefits:
1. Concise syntax
2. Readable
3. Declarative style
Examples:
-- Basic comprehension
[x*2 | x <- [1..10]] -- [2,4,6,8,10,12,14,16,18,20]
-- With guards
[x*2 | x <- [1..10], x `mod` 2 == 0] -- [4,8,12,16,20]
-- Multiple generators
[(x,y) | x <- [1,2,3], y <- ['a','b']] -- [(1,'a'),(1,'b'),...]
-- Nested comprehensions
[[x*y | y <- [1..3]] | x <- [1..3]] -- [[1,2,3],[2,4,6],[3,6,9]]
-- Pattern matching
[(x,y) | (x,y) <- [(1,2),(3,4),(5,6)], x < y] -- [(1,2),(3,4),(5,6)]
-- Complex example
pythagoreanTriples :: [(Int, Int, Int)]
pythagoreanTriples = [(a,b,c) | a <- [1..20], b <- [a..20], c <- [b..20], a^2 + b^2 == c^2]
-- String processing
uppercaseLetters = [toUpper c | c <- "hello world", c /= ' '] -- "HELLOWORLD"
-- With local variables
squares = [x | x <- [1..10], let y = x^2, y > 20] -- [5,6,7,8,9,10]"map and fold serve different purposes in list processing.
- map: Applies function to every element
- fold: Reduces list to single value
- Use: map for transformation, fold for accumulation
// Haskell Q33: What is the difference between map and fold?
"map and fold serve different purposes:
map:
- Applies function to every element
- Preserves structure
- Returns same length list
- Type: (a -> b) -> [a] -> [b]
fold:
- Reduces list to single value
- Accumulates results
- Can change length
- Type: (b -> a -> b) -> b -> [a] -> b
Examples:
-- map examples
map (*2) [1,2,3,4] -- [2,4,6,8]
map (+1) [1,2,3] -- [2,3,4]
map (\x -> x > 3) [1,2,3,4] -- [False,False,False,True]
-- fold examples
foldl (+) 0 [1,2,3,4] -- 10
foldr (*) 1 [1,2,3,4] -- 24
foldl (\acc x -> acc ++ [x]) [] [1,2,3] -- [1,2,3]
-- Combining map and fold
sumOfSquares = foldl (+) 0 . map (^2)
sumOfSquares [1,2,3,4] -- 30
-- Different fold operations
-- foldl: left fold (lazy)
-- foldr: right fold (lazy)
-- foldl': left fold (strict)
-- Practical example
countEven = length . filter even
totalEvenSum = foldl (+) 0 . filter even
-- Map with multiple arguments
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (+) [1,2,3] [4,5,6] -- [5,7,9]"Infinite data structures represent potentially unbounded data using lazy evaluation.
- Lazy Evaluation: Enables infinite structures
- Only Compute: What's needed
- Elegant: Solutions for sequences
// Haskell Q34: What are infinite data structures?
"Infinite data structures represent potentially unbounded data:
Benefits:
1. Lazy evaluation enables infinite structures
2. Only compute what's needed
3. Elegant solutions for sequences
Examples:
-- Infinite list of ones
ones = 1 : ones
take 5 ones -- [1,1,1,1,1]
-- Infinite list of numbers
nats = [1..]
take 10 nats -- [1,2,3,4,5,6,7,8,9,10]
-- Fibonacci sequence
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs -- [0,1,1,2,3,5,8,13,21,34]
-- Infinite list of primes
primes = sieve [2..]
where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
take 10 primes -- [2,3,5,7,11,13,17,19,23,29]
-- Infinite tree
data Tree a = Node a (Tree a) (Tree a)
infiniteTree = Node 1 infiniteTree infiniteTree
-- Working with infinite lists
takeWhile (<100) [1..] -- [1..99]
filter even [1..] !! 100 -- 202
-- Lazy evaluation in action
firstMultiple = head [x | x <- [1..], x `mod` 7 == 0, x > 100] -- 105
-- Stream processing
stream = map (*2) [1..]
take 5 stream -- [2,4,6,8,10]"Different ways to represent emptiness in Haskell.
- null: Function that tests if list is empty
- empty: Not standard, used in custom types
- Empty List: [] represents empty list
// Haskell Q35: What is the difference between null, empty, and empty list?
"Different ways to represent emptiness:
null:
- Function that tests if a list is empty
- Type: [a] -> Bool
- Returns True for empty list
empty:
- Not a standard function in Haskell
- Sometimes used in custom data types
- Often replaced by null
Empty List ([]):
- Represents an empty list
- Used in pattern matching
- Type: [a]
Examples:
-- Using null
null [] -- True
null [1,2,3] -- False
null "hello" -- False
null "" -- True
-- Pattern matching with empty list
head' :: [a] -> a
head' (x:_) = x
head' [] = error "Empty list"
-- Checking length
isEmpty = (== 0) . length
isEmpty [] -- True
isEmpty [1] -- False
-- Using empty list in functions
sum' [] = 0
sum' (x:xs) = x + sum' xs
-- Maybe type with empty
data Maybe a = Nothing | Just a
fromMaybe :: a -> Maybe a -> a
fromMaybe default Nothing = default
fromMaybe _ (Just x) = x
-- Different representations of nothing
nothing1 = [] :: [Int]
nothing2 = Nothing :: Maybe Int
nothing3 = "" :: String
-- Checking emptiness in different contexts
caseMaybe :: Maybe a -> String
caseMaybe Nothing = "Nothing"
caseMaybe (Just _) = "Something"Type annotations explicitly specify types in Haskell.
- Documentation: Clear type signatures
- Type Safety: Better error messages
- Guideline: For type inference
// Haskell Q36: What are type annotations?
"Type annotations explicitly specify types:
Type Annotation Syntax:
expression :: Type
Benefits:
1. Documentation
2. Type safety
3. Better error messages
4. Guideline for type inference
Examples:
-- Basic type annotations
x :: Int
x = 5
add :: Int -> Int -> Int
add x y = x + y
-- Function type annotations
square :: Num a => a -> a
square x = x * x
-- Polymorphic type annotation
identity :: a -> a
identity x = x
-- With type constraints
max' :: Ord a => a -> a -> a
max' x y = if x > y then x else y
-- Complex type annotations
map' :: (a -> b) -> [a] -> [b]
map' _ [] = []
map' f (x:xs) = f x : map' f xs
-- Type annotations for clarification
process :: [Int] -> Int
process xs = foldl (+) 0 (filter (>0) xs)
-- Type annotations in where clauses
calculate x y = result
where
result :: Int
result = x + y
-- Type annotations with type classes
printValue :: Show a => a -> IO ()
printValue x = putStrLn (show x)
-- Scoped type variables
{-# LANGUAGE ScopedTypeVariables #-}
f :: forall a. [a] -> [a]
f (x:xs) = [x] ++ rest
where
rest :: [a]
rest = xs"Type families define type-level functions.
- Type-level Computation: Compute types
- Flexible: Type definitions
- Abstraction: Better abstraction
// Haskell Q37: What are type families?
"Type families define type-level functions:
Type Family Syntax:
type family Name a :: *
Benefits:
1. Type-level computation
2. Flexible type definitions
3. Better abstraction
Examples:
-- Type family declaration
type family Element t :: *
type instance Element [a] = a
type instance Element (Maybe a) = a
-- Using type families
getElement :: Element [Int] -> Int
getElement x = x
-- Associated type families
class Collection c where
type Element c
toList :: c -> [Element c]
instance Collection [a] where
type Element [a] = a
toList = id
-- Data type family
data family Vector a
data instance Vector Int = IntVector [Int]
data instance Vector Char = CharVector [Char]
-- Closed type families
type family IsString a :: Bool where
IsString [Char] = True
IsString a = False
-- Using closed family
showIfString :: IsString a ~ True => a -> String
showIfString x = show x
-- Type family with parameters
type family Add a b :: *
type instance Add Int Int = Int
type instance Add Double Double = Double
-- Type family in class
class Convert a b where
convert :: a -> b"Existential types hide internal implementation details.
- Information Hiding: Hide implementation
- Abstract Data Types: Abstract interfaces
- Heterogeneous Collections: Different types together
// Haskell Q38: What are existential types?
"Existential types hide internal implementation details:
Existential Type Syntax:
data Exists = forall a. Exists a
Benefits:
1. Information hiding
2. Abstract data types
3. Heterogeneous collections
Examples:
-- Basic existential
data Showable = forall a. Show a => Showable a
instance Show Showable where
show (Showable x) = show x
-- Using Showable
collection :: [Showable]
collection = [Showable 5, Showable "hello", Showable 3.14]
-- Heterogeneous list with operations
data AnyList = forall a. AnyList [a]
headAny :: AnyList -> Maybe AnyList
headAny (AnyList (x:xs)) = Just (AnyList [x])
headAny (AnyList []) = Nothing
-- Existential with constraints
data Shape = forall s. Shape s
where Shape :: (Drawable s, Area s) => s -> Shape
class Drawable s where
draw :: s -> String
class Area s where
area :: s -> Float
-- Using GADTs for existential
data Expr where
Expr :: (Show a) => a -> Expr
-- Practical example
data Employee = forall a. (Workable a, Payable a) => Employee a
class Workable a where
work :: a -> String
class Payable a where
salary :: a -> Double
-- List of different types
employees :: [Employee]
employees = [Employee manager, Employee developer, Employee designer]"Kinds are the 'types of types' in Haskell.
- *: Type of concrete types
- * -> *: Type constructor
- (* -> *) -> *: Higher-kinded type
// Haskell Q39: What are kind and sort?
"Kinds are the 'types of types':
Kind Hierarchy:
1. * : Type of concrete types
2. * -> * : Type constructor
3. (* -> *) -> * : Higher-kinded type
Examples:
-- Basic kinds
Int :: * -- Concrete type
Maybe :: * -> * -- Type constructor
-- Kind signatures
data Maybe a = Nothing | Just a -- Maybe :: * -> *
data Pair a b = Pair a b -- Pair :: * -> * -> *
-- Higher-kinded types
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- Functor :: (* -> *) -> Constraint
-- Kind annotations
data List a = Nil | Cons a (List a) -- List :: * -> *
-- Using kinds
data Applicative f = Applicative
(forall a b. (a -> b) -> f a -> f b)
(forall a. a -> f a)
-- Kind polymorphism
{-# LANGUAGE PolyKinds #-}
data Proxy (a :: k) = Proxy
-- Higher-kinded data
data Free f a = Pure a | Free (f (Free f a))
-- Kind inference
-- Without annotation:
data T a = T a -- T :: * -> *
-- With explicit kind
data T (a :: *) = T a
-- Kind of type classes
class Eq a where -- Eq :: * -> Constraint
(==) :: a -> a -> Bool"The hierarchy of abstraction from least to most powerful.
- Functor: Maps functions, no sequencing
- Applicative: Sequential application, no dependencies
- Monad: Sequential composition with dependencies
// Haskell Q40: What are the differences between monad, applicative, and functor?
"The hierarchy of abstraction from least to most powerful:
Functor:
- Maps functions over context
- fmap :: (a -> b) -> f a -> f b
- Preserves structure
- No sequencing
Applicative:
- Applies functions in context
- pure :: a -> f a
- (<*>) :: f (a -> b) -> f a -> f b
- Sequencing without dependency
Monad:
- Chains computations with dependency
- return :: a -> m a
- (>>=) :: m a -> (a -> m b) -> m b
- Sequencing with dependency
Examples:
-- Functor
fmap (*2) (Just 3) -- Just 6
fmap show [1,2,3] -- ["1","2","3"]
-- Applicative
Just (+3) <*> Just 5 -- Just 8
(*) <$> [1,2] <*> [3,4] -- [3,4,6,8]
-- Monad
Just 3 >>= \x -> Just (x + 5) -- Just 8
[1,2] >>= \x -> [x, x*2] -- [1,2,2,4]
-- Comparison
-- Functor: fmap f x = pure f <*> x = do { a <- x; return (f a) }
-- Applicative: pure f <*> x = do { f' <- pure f; f' <*> x }
-- Monad: x >>= f = join (fmap f x)
-- When to use which
-- Functor: Map over a value in context
-- Applicative: Apply multiple functions in context
-- Monad: Chain dependent computations"Prelude and Data.List provide different functionality.
- Prelude: Basic list functions
- Data.List: Extended list functions
- Import: Data.List must be imported
// Haskell Q41: What is the difference between Data.List and Prelude?
"Prelude and Data.List provide different functionality:
Prelude:
- Automatically imported
- Basic list functions
- Minimal functionality
- Designed for everyday use
Data.List:
- Must be imported
- Extended list functions
- Advanced operations
- More specialized functions
Examples:
-- Prelude functions
head, tail, init, last, map, filter, foldl, foldr, length, (++), (!!)
-- Data.List functions
import Data.List
-- Intercalate
intercalate ", " ["hello", "world"] -- "hello, world"
-- Group
group [1,1,2,2,3,3] -- [[1,1],[2,2],[3,3]]
-- Sort
sort [3,1,4,1,5,9] -- [1,1,3,4,5,9]
-- Nub (unique)
nub [1,2,1,3,2] -- [1,2,3]
-- Inits and tails
inits [1,2,3] -- [[],[1],[1,2],[1,2,3]]
tails [1,2,3] -- [[1,2,3],[2,3],[3],[]]
-- Strip prefixes
stripPrefix "hello" "helloworld" -- Just "world"
-- Subsequences
subsequences [1,2,3] -- [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
-- Permutations
permutations [1,2,3] -- [[1,2,3],[2,1,3],[3,1,2],[1,3,2],[2,3,1],[3,2,1]]
-- Partitions
partition (>3) [1..10] -- ([4,5,6,7,8,9,10],[1,2,3])
-- When to use each
-- Prelude: Basic operations
-- Data.List: Advanced operations"StateT combines State with another monad.
- runStateT: Run the transformer
- evalStateT: Get result only
- execStateT: Get state only
- lift: Lift base monad operations
// Haskell Q42: What is the StateT monad transformer?
"StateT combines State with another monad:
StateT Definition:
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
Key Functions:
1. runStateT: Run the transformer
2. evalStateT: Get result only
3. execStateT: Get state only
4. lift: Lift base monad operations
Examples:
import Control.Monad.State
import Control.Monad.Trans.State
-- Type definition
type StateM a = StateT AppState IO a
data AppState = AppState { counter :: Int, log :: [String] }
-- Basic operations
increment :: StateM ()
increment = do
modify (\s -> s { counter = counter s + 1 })
state <- get
liftIO (putStrLn ("Counter: " ++ show (counter state)))
-- Complex operation
process :: Int -> StateM Int
process n = do
increment
state <- get
let newState = state { log = ("Processing " ++ show n) : log state }
put newState
liftIO (putStrLn ("Logged: " ++ show (log state)))
return (n * 2)
-- Running StateT
main :: IO ()
main = do
let initialState = AppState { counter = 0, log = [] }
result <- runStateT (process 10) initialState
print result
-- Using with MaybeT
type MyMonad = StateT Int (MaybeT IO)
-- Using with ReaderT
type AppMonad = StateT AppState (ReaderT Config IO)
-- Lifting operations
liftIO :: IO a -> StateT s IO a
liftIO = Control.Monad.Trans.State.liftIO
-- Pattern matching in StateT
runAll :: StateT Int IO ()
runAll = do
n <- get
when (n > 0) $ do
put (n - 1)
liftIO (print n)
runAll"MonadFail handles failure, MonadIO lifts IO operations.
- MonadFail: Pattern match failures
- MonadIO: Lift IO operations
- Use: Monad transformers
// Haskell Q43: What are MonadFail and MonadIO?
"MonadFail handles failure in monads, MonadIO lifts IO operations:
MonadFail:
- Handles pattern match failures
- Provides fail function
- Used in do notation
MonadIO:
- Lifts IO operations
- Provides liftIO function
- Used with transformers
Examples:
import Control.Monad.Fail
import Control.Monad.IO.Class
-- MonadFail instance
data MyMonad a = Success a | Failure String
instance MonadFail MyMonad where
fail msg = Failure msg
-- Using MonadFail in do notation
safeDiv :: MonadFail m => Int -> Int -> m Int
safeDiv _ 0 = fail "Division by zero"
safeDiv x y = return (x `div` y)
-- MonadIO example
type AppM = ReaderT Config (StateT State IO)
logMessage :: MonadIO m => String -> m ()
logMessage msg = liftIO (putStrLn msg)
-- Using MonadIO
process :: (MonadIO m, MonadState AppState m) => m ()
process = do
liftIO (putStrLn "Starting")
modify (\s -> s { counter = counter s + 1 })
liftIO (putStrLn "Incremented")
-- MonadFail with transformers
type App = ReaderT Config (StateT State (ExceptT String IO))
failExample :: App Int
failExample = do
config <- ask
if config == "debug"
then return 42
else fail "Invalid config"
-- Combining MonadIO and MonadFail
safeIO :: (MonadIO m, MonadFail m) => String -> m String
safeIO path = do
content <- liftIO (readFile path)
if null content
then fail "Empty file"
else return content"Arrows generalize functions and monads.
- Generalization: More general than monads
- Circuit-like: Computations
- Static Analysis: Possible
// Haskell Q44: What are arrows?
"Arrows generalize functions and monads:
Arrow Definition:
class Category a => Arrow a where
arr :: (b -> c) -> a b c
first :: a b c -> a (b, d) (c, d)
Benefits:
1. More general than monads
2. Circuit-like computations
3. Static analysis possible
Examples:
import Control.Arrow
-- Basic arrow operations
addOne :: Arrow a => a Int Int
addOne = arr (+1)
-- Combining arrows
process = arr (+1) >>> arr (*2)
-- Arrow composition
f = arr (^2) >>> arr (+1) -- f x = x^2 + 1
-- Arrow with first
swap = arr (\ (x,y) -> (y,x))
addPairs = arr (\ (x,y) -> x + y)
-- Arrow loop
loopExample = loop (arr (\ (x,y) -> (y, x+y)))
-- Kliesli arrows
type Kliesli m a b = a -> m b
-- Arrow with state
newtype StateArrow s a b = StateArrow ((s,a) -> (s,b))
instance Arrow (StateArrow s) where
arr f = StateArrow (\ (s,x) -> (s, f x))
first (StateArrow f) = StateArrow (\ (s, (x,y)) ->
let (s', x') = f (s,x)
in (s', (x',y)))
-- Practical arrow example
data Circuit a b = Circuit (a -> (Circuit a b, b))
instance Arrow Circuit where
arr f = Circuit (\x -> (arr f, f x))
first (Circuit f) = Circuit (\ (x,y) ->
let (c, x') = f x
in (first c, (x', y)))
-- Using circuit
counter :: Circuit Int Int
counter = Circuit go
where go x = (counter, x + 1)"Comonads are the dual of monads.
- extract: Get value from context
- duplicate: Duplicate context
- extend: Extend context
- Dual: Opposite of monads
// Haskell Q45: What are comonads?
"Comonads are the dual of monads:
Comonad Definition:
class Functor w => Comonad w where
extract :: w a -> a
duplicate :: w a -> w (w a)
extend :: (w a -> b) -> w a -> w b
Key Laws:
1. extract . duplicate = id
2. fmap extract . duplicate = id
3. duplicate . duplicate = fmap duplicate . duplicate
Examples:
import Control.Comonad
-- List comonad (non-empty list)
data NonEmpty a = a :| [a]
instance Functor NonEmpty where
fmap f (x :| xs) = f x :| map f xs
instance Comonad NonEmpty where
extract (x :| _) = x
duplicate xs = xs :| tails xs
-- Using NonEmpty
tails :: NonEmpty a -> [NonEmpty a]
tails (x :| xs) = (x :| xs) : case xs of
[] -> []
(y:ys) -> tails (y :| ys)
-- Stream comonad
data Stream a = Cons a (Stream a)
instance Functor Stream where
fmap f (Cons x xs) = Cons (f x) (fmap f xs)
instance Comonad Stream where
extract (Cons x _) = x
duplicate (Cons x xs) = Cons (Cons x xs) (duplicate xs)
-- Using streams
nats = Cons 1 (fmap (+1) nats)
-- Store comonad
data Store s a = Store (s -> a) s
instance Functor (Store s) where
fmap f (Store g s) = Store (f . g) s
instance Comonad (Store s) where
extract (Store f s) = f s
duplicate (Store f s) = Store (\s' -> Store f s') s
-- Practical example
movingAverage :: Stream Int -> Stream Int
movingAverage = extend (\s -> sum (take 3 (toList s)) `div` 3)
where toList (Cons x xs) = x : toList xs"Free monads create monads from functors.
- DSLs: Build domain-specific languages
- Syntax/Semantics: Separate from interpretation
- Composition: Compose effects
// Haskell Q46: What are free monads?
"Free monads create monads from functors:
Free Monad Definition:
data Free f a = Pure a | Free (f (Free f a))
Benefits:
1. Build DSLs
2. Separate syntax from semantics
3. Composition of effects
Examples:
import Control.Monad.Free
-- Simple DSL
data Teletype a = PutStrLn String a | GetLine (String -> a)
instance Functor Teletype where
fmap f (PutStrLn s x) = PutStrLn s (f x)
fmap f (GetLine g) = GetLine (f . g)
type Program = Free Teletype
-- Smart constructors
putStrLn' :: String -> Program ()
putStrLn' s = liftF (PutStrLn s ())
getLine' :: Program String
getLine' = liftF (GetLine id)
-- Example program
hello :: Program ()
hello = do
putStrLn' "What's your name?"
name <- getLine'
putStrLn' ("Hello, " ++ name)
-- Interpreter
runTeletype :: Program () -> IO ()
runTeletype (Pure ()) = return ()
runTeletype (Free (PutStrLn s next)) = do
putStrLn s
runTeletype next
runTeletype (Free (GetLine f)) = do
line <- getLine
runTeletype (f line)
-- Free monad for state
data StateF s a = Get (s -> a) | Put s a
instance Functor (StateF s) where
fmap f (Get g) = Get (f . g)
fmap f (Put s a) = Put s (f a)
type StateFree s = Free (StateF s)
get :: StateFree s s
get = liftF (Get id)
put :: s -> StateFree s ()
put s = liftF (Put s ())
modify :: (s -> s) -> StateFree s ()
modify f = do
s <- get
put (f s)"Monad laws ensure consistent behavior.
- Left Identity: return a >>= f = f a
- Right Identity: m >>= return = m
- Associativity: (m >>= f) >>= g = m >>= (x -> f x >>= g)
// Haskell Q47: What are monad laws?
"Monad laws ensure consistent behavior:
Three Laws:
1. Left Identity: return a >>= f = f a
2. Right Identity: m >>= return = m
3. Associativity: (m >>= f) >>= g = m >>= (\x -> f x >>= g)
Examples:
-- Testing monad laws with Maybe
leftIdentity :: (a -> Maybe a) -> a -> Bool
leftIdentity f a = (return a >>= f) == f a
rightIdentity :: Maybe a -> Bool
rightIdentity m = (m >>= return) == m
associativity :: Maybe a -> (a -> Maybe b) -> (b -> Maybe c) -> Bool
associativity m f g =
((m >>= f) >>= g) == (m >>= (\x -> f x >>= g))
-- List monad laws
leftIdentityList f a = [a] >>= f == f a
rightIdentityList xs = xs >>= return == xs
associativityList xs f g =
(xs >>= f) >>= g == xs >>= (\x -> f x >>= g)
-- Why laws matter
-- Without laws, code would be unpredictable
-- Laws enable reasoning about code
-- Laws support refactoring
-- Monad instance checking
-- Maybe monad violates? No, follows all laws
-- List monad follows all laws
-- IO monad follows all laws (in theory)
-- Practical implications
-- You can refactor: do { x <- m; f x } to m >>= f
-- You can reorder: return x >>= f = f x"foldr and foldl differ in evaluation strategy.
- foldr: Lazy, works with infinite lists
- foldl: Strict, can overflow stack
- foldl': Strict left fold (recommended)
// Haskell Q48: What is the difference between foldr and foldl in terms of laziness?
"Foldr and foldl differ in evaluation strategy:
foldr:
- Lazy evaluation
- Works with infinite lists
- Can short-circuit
- Right associative
foldl:
- Lazy evaluation but strict in accumulator
- Doesn't work with infinite lists
- Can cause stack overflow
- Left associative
Examples:
-- foldr with infinite list
foldr (:) [] [1..] -- Works (lazy)
foldr (+) 0 [1..] -- Doesn't terminate (strict operation)
-- foldl with infinite list
foldl (+) 0 [1..] -- Never terminates
foldl (flip (:)) [] [1..] -- Never terminates
-- Short-circuiting with foldr
anyEven = foldr (\x acc -> even x || acc) False
anyEven [1..] -- True (stops at first even)
-- foldr with lazy operation
or' = foldr (||) False
or' (False : repeat True) -- True (lazy)
-- foldl can't short-circuit
orL = foldl (||) False
orL (False : repeat True) -- Never terminates
-- Memory usage
-- foldr: O(n) stack for lazy operations
-- foldl': O(1) stack for strict operations
-- Choosing the right fold
-- foldr: For lazy operations, infinite lists, constructing data
-- foldl': For strict accumulations, numeric operations
-- foldl: Rarely (use foldl' instead)
-- foldr with strict operation needs strictness
foldr (+) 0 [1..1000000] -- Possible stack overflow
foldl' (+) 0 [1..1000000] -- Safe"Data constructors create values of algebraic data types.
- Nullary: No arguments
- Product: Multiple arguments
- Record: Named fields
- Recursive: References to same type
// Haskell Q49: What are data constructors?
"Data constructors create values of algebraic data types:
Types of Constructors:
1. Nullary: No arguments
2. Product: Multiple arguments
3. Record: Named fields
4. Recursive: References to same type
Examples:
-- Nullary constructor
data Bool = False | True
-- Product constructor
data Person = Person String Int
-- Record syntax
data Employee = Employee
{ name :: String
, age :: Int
, salary :: Double
}
-- Recursive constructor
data List a = Nil | Cons a (List a)
data Tree a = Leaf a | Node (Tree a) (Tree a)
-- Multiple constructors with different arguments
data Shape = Circle Float | Rectangle Float Float | Triangle Float Float Float
-- Pattern matching with constructors
area :: Shape -> Float
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle a b c) = a * b / 2
-- Using record constructors
createEmployee :: Employee
createEmployee = Employee
{ name = "Alice"
, age = 25
, salary = 50000
}
-- Constructor functions
mkPerson :: String -> Int -> Person
mkPerson = Person
-- Data constructor vs type constructor
-- Type constructor: Maybe (takes a type)
-- Data constructor: Just (takes a value)
-- Newtype constructor
newtype Age = Age Int
-- Type aliases vs data constructors
type Name = String -- No constructor
data Name' = Name String -- Has constructor"Record syntax provides named fields and automatic accessors.
- Named Fields: Field names
- Accessors: Automatic getter functions
- Update Syntax: Record update
- Pattern Matching: With named fields
// Haskell Q50: What are record syntax and field accessors?
"Record syntax provides named fields and automatic accessors:
Record Syntax:
data Record = Record
{ field1 :: Type1
, field2 :: Type2
}
Benefits:
1. Named fields
2. Automatic accessors
3. Update syntax
4. Pattern matching
Examples:
-- Defining record
data Person = Person
{ name :: String
, age :: Int
, address :: String
} deriving (Show)
-- Creating record
alice = Person
{ name = "Alice"
, age = 25
, address = "123 Main St"
}
-- Accessing fields
getName :: Person -> String
getName = name
getAge :: Person -> Int
getAge = age
-- Updating records
bob = alice { name = "Bob", age = 30 }
-- Pattern matching with records
printPerson :: Person -> String
printPerson Person { name = n, age = a } =
"Name: " ++ n ++ ", Age: " ++ show a
-- Field update with function
incrementAge :: Person -> Person
incrementAge p = p { age = age p + 1 }
-- Multiple updates
updatePerson :: Person -> Person
updatePerson p = p
{ age = age p + 1
, address = address p ++ " (updated)"
}
-- Record with complex fields
data Employee = Employee
{ empName :: String
, empDetails :: Person
, empSalary :: Double
}
-- Field access composition
getEmployeeName :: Employee -> String
getEmployeeName = name . empDetails
-- Empty record syntax
data Empty = Empty {}
-- Record with phantom type
data User a = User
{ userName :: String
, userAge :: Int
}"Type variables enable polymorphic functions.
- Parametric: Same behavior for all types
- Ad-hoc: Different behavior per type
- Rank-N: Higher-rank polymorphism
// Haskell Q51: What are type variables and polymorphism?
"Type variables enable polymorphic functions:
Type Variables:
- Lowercase letters: a, b, c, etc.
- Can be any type
- Enable generic programming
Polymorphism Types:
1. Parametric: Same behavior for all types
2. Ad-hoc: Different behavior per type (type classes)
3. Rank-N: Higher-rank polymorphism
Examples:
-- Parametric polymorphism
identity :: a -> a
identity x = x
-- List functions
length :: [a] -> Int
map :: (a -> b) -> [a] -> [b]
-- Polymorphic data types
data Maybe a = Nothing | Just a
data Either a b = Left a | Right b
-- Ad-hoc polymorphism with type classes
add :: Num a => a -> a -> a
add x y = x + y
-- Comparing values
compare :: Ord a => a -> a -> Ordering
-- Rank-2 polymorphism
{-# LANGUAGE RankNTypes #-}
apply :: (forall a. a -> a) -> Int -> Int
apply f x = f x
-- Type variables in constraints
process :: (Show a, Read a) => a -> String
process x = show x
-- Scoped type variables
{-# LANGUAGE ScopedTypeVariables #-}
f :: forall a. [a] -> [a]
f (x:xs) = [x] ++ (f xs :: [a])
-- Type variable naming conventions
-- a, b, c: Generic types
-- m: Monad
-- f: Functor
-- t: Type
-- s: State"Constraints and contexts specify type requirements.
- Eq: Equality
- Ord: Ordering
- Num: Numeric operations
- Show: String conversion
// Haskell Q52: What are constraints and contexts?
"Constraints and contexts specify type requirements:
Constraint Syntax:
function :: (Constraint1 a, Constraint2 a) => a -> a
Common Constraints:
1. Eq: Equality
2. Ord: Ordering
3. Num: Numeric operations
4. Show: String conversion
5. Read: Parsing
6. Functor: Mapping
7. Applicative: Sequencing
8. Monad: Chaining
Examples:
-- Multiple constraints
sort :: Ord a => [a] -> [a]
process :: (Show a, Read a) => String -> a
process s = read s
-- Context in data declaration
data Eq a => Set a = Set [a]
-- Constraint in type synonym
type MyType a = (Num a, Show a) => a
-- Constraint in class instance
instance Ord a => Eq (Maybe a) where
(Just x) == (Just y) = x == y
_ == _ = False
-- Undecidable instances
{-# LANGUAGE UndecidableInstances #-}
instance (Eq a, Eq b) => Eq (a, b) where
(x1, y1) == (x2, y2) = x1 == x2 && y1 == y2
-- Constraint kinds
-- Eq a where a has kind *
-- Functor f where f has kind * -> *
-- Using constraints in functions
map' :: Functor f => (a -> b) -> f a -> f b
-- Constraint implication
{-# LANGUAGE ConstraintKinds #-}
type NumLike a = (Num a, Eq a)
add' :: NumLike a => a -> a -> a
add' x y = x + y"Functional dependencies express relationships between types.
- Type Inference: Better inference
- Avoid Ambiguity: Clear relationships
- Type-level: Relationships
// Haskell Q53: What are functional dependencies?
"Functional dependencies express relationships between types:
Functional Dependency Syntax:
class Class a b | a -> b where ...
Meaning: a determines b
Benefits:
1. Type inference
2. Avoid ambiguity
3. Type-level relationships
Examples:
-- Basic functional dependency
class Convert a b | a -> b where
convert :: a -> b
instance Convert Int String where
convert = show
instance Convert String Int where
convert = read
-- Multi-parameter type class with fundeps
class Collection c e | c -> e where
empty :: c
insert :: e -> c -> c
toList :: c -> [e]
instance Collection [e] e where
empty = []
insert x xs = x : xs
toList = id
-- Using functional dependencies
class HasKey k v | k -> v where
getValue :: k -> v
-- Functional dependency in practice
class StateMonad m s | m -> s where
get :: m s
put :: s -> m ()
-- Multiple dependencies
class Relation a b c | a -> b, a -> c where
combine :: a -> (b, c)
-- Ambiguity resolution
class Read a where
readsPrec :: Int -> ReadS a
-- Using fundeps for type-level computation
class Add a b c | a b -> c where
add :: a -> b -> c
instance Add Int Int Int where
add = (+)
instance Add Double Double Double where
add = (+)"Both solve similar problems with different approaches.
- Type Families: Type-level functions
- Functional Dependencies: Express relationships
- Use: Type Families for computation, Fundeps for relationships
// Haskell Q54: What are type families vs functional dependencies?
"Both solve similar problems with different approaches:
Type Families:
- Define type-level functions
- More flexible
- Better for large projects
Functional Dependencies:
- Express relationships
- Older approach
- Simpler for small projects
Examples:
-- Type Families
type family Element t :: *
type instance Element [a] = a
class Collection c where
type Elem c
empty :: c
insert :: Elem c -> c -> c
instance Collection [a] where
type Elem [a] = a
empty = []
insert x xs = x : xs
-- Functional Dependencies
class Collection' c e | c -> e where
empty' :: c
insert' :: e -> c -> c
instance Collection' [a] a where
empty' = []
insert' x xs = x : xs
-- Key differences
-- Type Families: Can compute types
type family Add a b where
Add Int Int = Int
Add Double Double = Double
-- Functional Dependencies: Express constraints
class Convert a b | a -> b, b -> a
-- Type Families: Better for open world
type family F a :: *
type instance F Int = Bool
type instance F String = Int
-- Functional Dependencies: Better for closed world
class C a b | a -> b
-- When to use each
-- Type Families: Complex type-level computations
-- Functional Dependencies: Simple type relationships"Associated types are type families inside type classes.
- Cleaner Syntax: Type-level computation
- Better Abstraction: In classes
- Use: Collection types, data structures
// Haskell Q55: What are associated types?
"Associated types are type families inside type classes:
Associated Type Syntax:
class Class where
type AssociatedType a :: *
Benefits:
1. Cleaner syntax
2. Type-level computation
3. Better abstraction
Examples:
-- Basic associated type
class Collection c where
type Element c :: *
empty :: c
insert :: Element c -> c -> c
instance Collection [a] where
type Element [a] = a
empty = []
insert x xs = x : xs
-- Multiple associated types
class Map m where
type Key m :: *
type Value m :: *
emptyMap :: m
insertMap :: Key m -> Value m -> m -> m
instance Map [(k,v)] where
type Key [(k,v)] = k
type Value [(k,v)] = v
emptyMap = []
insertMap k v m = (k,v) : m
-- Associated type with default
class Container c where
type Item c :: *
type Item c = c -- Default
-- Associated type in GADT
class Show a where
type ShowS a :: *
-- Using associated types
class HasName a where
type Name a :: *
getName :: a -> Name a
instance HasName Person where
type Name Person = String
getName = personName
-- Associated type with constraints
class Serializable a where
type Serialized a :: *
serialize :: a -> Serialized a
deserialize :: Serialized a -> a"Closed type families have a fixed set of instances.
- Exhaustive: Pattern matching
- Type-level: Computation
- No Overlap: Issues
// Haskell Q56: What are closed type families?
"Closed type families have a fixed set of instances:
Closed Type Family Syntax:
type family Name a where
Name Type1 = Result1
Name Type2 = Result2
Benefits:
1. Exhaustive pattern matching
2. Type-level computation
3. No overlapping issues
Examples:
-- Basic closed family
type family IsString a where
IsString [Char] = True
IsString a = False
-- Type-level arithmetic
type family Add a b where
Add Zero b = b
Add (Succ a) b = Succ (Add a b)
-- Peano numbers
data Zero = Zero
data Succ n = Succ n
-- Type-level comparison
type family Compare a b where
Compare Zero Zero = EQ
Compare Zero (Succ a) = LT
Compare (Succ a) Zero = GT
Compare (Succ a) (Succ b) = Compare a b
-- Type-level boolean operations
type family And a b where
And True True = True
And a b = False
type family Or a b where
Or False False = False
Or a b = True
-- Type-level lists
type family Concat xs ys where
Concat '[] ys = ys
Concat (x ': xs) ys = x ': Concat xs ys
-- Type-level length
type family Length xs where
Length '[] = Zero
Length (x ': xs) = Succ (Length xs)
-- Closed family with overlapping
-- Earlier patterns take precedence
type family Priority a where
Priority Int = 1
Priority a = 0"Data families define type-indexed data types.
- Type-indexed: Data indexed by type
- Flexible: Different representations
- Type Safety: Compile-time guarantees
// Haskell Q57: What are data families?
"Data families define type-indexed data types:
Data Family Syntax:
data family Name a :: *
Benefits:
1. Type-indexed data
2. Flexible representations
3. Type safety
Examples:
-- Basic data family
data family Vector a
data instance Vector Int = IntVector [Int]
data instance Vector Char = CharVector [Char]
data instance Vector Bool = BoolVector [Bool]
-- Using vector
processInt :: Vector Int -> Int
processInt (IntVector xs) = sum xs
-- Multiple parameters
data family Tree a b
data instance Tree Int String = Node String (Tree Int String)
-- GADT-style data family
data instance Maybe a where
Nothing :: Maybe a
Just :: a -> Maybe a
-- Associated data families
class Collection c where
data Elem c :: *
empty :: c
insert :: Elem c -> c -> c
instance Collection [a] where
data Elem [a] = Elem a
empty = []
insert (Elem x) xs = x : xs
-- Data family with constraints
data family Ord a => Set a
data instance Ord a => Set a = Set [a]
-- Using data families
data Family a where
Family :: (Show a) => a -> Family a
-- Pattern matching on data families
processFamily :: Family a -> String
processFamily (Family x) = show x"GADTs extend Haskell's data type syntax.
- GADTs: General ADTs
- GADTSyntax: Alternate syntax
- ExistentialQuantification: Existential types
// Haskell Q58: What are GADT syntax extensions?
"GADTs extend Haskell's data type syntax:
GADT Syntax:
data MyType a where
Constructor :: Type -> MyType Type
Extensions:
1. GADTs: General ADTs
2. GADTSyntax: Alternate syntax
3. ExistentialQuantification: Existential types
Examples:
-- Basic GADT
{-# LANGUAGE GADTs #-}
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
If :: Expr Bool -> Expr a -> Expr a -> Expr a
-- Type-safe eval
eval :: Expr a -> a
eval (IntLit n) = n
eval (BoolLit b) = b
eval (Add e1 e2) = eval e1 + eval e2
eval (If cond e1 e2) = if eval cond then eval e1 else eval e2
-- GADT for list with type info
data List a where
Nil :: List a
Cons :: a -> List a -> List a
-- Type-safe equality
data EqProof a b where
Refl :: EqProof a a
cast :: EqProof a b -> a -> b
cast Refl x = x
-- GADT with constraints
data Showable where
Showable :: (Show a) => a -> Showable
-- GADT for typed AST
data Typed a where
TInt :: Int -> Typed Int
TBool :: Bool -> Typed Bool
TIf :: Typed Bool -> Typed a -> Typed a -> Typed a
-- GADT for state machines
data State a where
Init :: State Init
Running :: State Running
Done :: State Done
-- Using GADT for DSL
data DSL a where
Print :: String -> DSL ()
Read :: DSL String
Bind :: DSL a -> (a -> DSL b) -> DSL b"GADTs naturally support existential types.
- Type Hiding: Hide implementation
- Abstract Data Types: Abstract interfaces
- Heterogeneous Collections: Different types together
// Haskell Q59: What are existential types in GADTs?
"GADTs naturally support existential types:
Existential Pattern:
data Exists = forall a. Exists a
GADT Style:
data Exists where
Exists :: a -> Exists
Benefits:
1. Type hiding
2. Abstract data types
3. Heterogeneous collections
Examples:
-- Basic existential with GADT
{-# LANGUAGE GADTs #-}
data Showable where
Showable :: Show a => a -> Showable
instance Show Showable where
show (Showable x) = show x
-- Using Showable
showableList :: [Showable]
showableList = [Showable 5, Showable "hello", Showable 3.14]
-- GADT with multiple constraints
data Printable where
Printable :: (Show a, Read a) => a -> Printable
-- Existential with methods
data Algebra where
Algebra :: (Num a, Show a) => a -> Algebra
-- Using existential in functions
processShowable :: Showable -> String
processShowable (Showable x) = show x
-- List of different types
data AnyList where
AnyList :: [a] -> AnyList
-- Type-safe heterogeneous list
data HList where
HNil :: HList
HCons :: a -> HList -> HList
-- Using GADT for heterogeneous lists
data HList' a where
Nil' :: HList' '[]
Cons' :: a -> HList' as -> HList' (a ': as)
-- Existential with type families
data Expr where
Expr :: (Typeable a) => a -> Expr
-- Using existential for dynamic typing
fromDynamic :: Typeable a => Expr -> Maybe a
fromDynamic (Expr x) = cast x"RankNTypes allow higher-rank polymorphism.
- Rank-1:
forall a. a -> a - Rank-2:
(forall a. a -> a) -> Int -> Int - Expressiveness: More expressive types
// Haskell Q60: What are RankNTypes?
"RankNTypes allow higher-rank polymorphism:
Rank-1 Polymorphism:
forall a. a -> a
Rank-2 Polymorphism:
(forall a. a -> a) -> Int -> Int
Benefits:
1. More expressive types
2. Better abstraction
3. Type-safe APIs
Examples:
{-# LANGUAGE RankNTypes #-}
-- Rank-2 type
apply :: (forall a. a -> a) -> Int -> Int
apply f x = f x
-- Using apply
result = apply id 5 -- Works
-- result = apply (+1) 5 -- Doesn't work ((+1) isn't polymorphic)
-- Rank-2 for ST
runST :: (forall s. ST s a) -> a
-- Rank-2 in data types
data Box = Box (forall a. a -> a)
-- Rank-2 in records
data API = API
{ getId :: forall a. a -> a
, getName :: forall a. Show a => a -> String
}
-- Higher-rank types in class methods
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- Rank-N types
-- Rank-3: (forall a. (forall b. a -> b) -> a) -> Int
-- Using higher-rank for type safety
run :: (forall a. Monad m => m a) -> IO a
run m = m
-- Rank-2 with constraints
f :: (forall a. (Show a, Num a) => a -> a) -> Int -> Int
f g x = g x
-- Practical example
type Application = forall m. MonadIO m => m ()"ScopedTypeVariables bring type variables into scope.
- Type Annotations: In functions
- Type Variables: In patterns
- Better Type Inference: Improved inference
// Haskell Q61: What are ScopedTypeVariables?
"ScopedTypeVariables bring type variables into scope:
Extension:
{-# LANGUAGE ScopedTypeVariables #-}
Benefits:
1. Type annotations in functions
2. Type variables in patterns
3. Better type inference
Examples:
-- Without scoped variables
f :: [a] -> [a]
f (x:xs) = [x] ++ (g xs) -- Can't specify type of g
-- With scoped variables
f :: forall a. [a] -> [a]
f (x:xs) = [x] ++ (g xs :: [a])
-- Type signatures in where clauses
process :: forall a. Show a => a -> String
process x = result
where
result :: String
result = show x ++ ":" ++ show x
-- Pattern matching with scoped variables
g :: forall a. (a -> a) -> [a] -> [a]
g f (x:xs) = (f x :: a) : g f xs
-- Nested scopes
h :: forall a b. (a -> b) -> [a] -> [b]
h f xs = map (\x -> f x :: b) xs
-- Scoped variables in data types
data MyType a = MyType a
instance Functor MyType where
fmap :: forall a b. (a -> b) -> MyType a -> MyType b
fmap f (MyType x) = MyType (f x :: b)
-- Multiple type variables
i :: forall a b. (a -> b) -> (b -> a) -> a -> b
i f g x = f x
-- Scoped variables in constraints
j :: forall a. (Num a, Show a) => a -> String
j x = show (x + 1)"MultiParamTypeClasses allow classes with multiple parameters.
- Expressiveness: More expressive classes
- Type Relationships: Better relationships
- Abstraction: Better abstraction
// Haskell Q62: What are MultiParamTypeClasses?
"MultiParamTypeClasses allow classes with multiple parameters:
Extension:
{-# LANGUAGE MultiParamTypeClasses #-}
Benefits:
1. More expressive class definitions
2. Type relationships
3. Better abstraction
Examples:
-- Basic multi-param class
class Convert a b where
convert :: a -> b
instance Convert Int String where
convert = show
instance Convert String Int where
convert = read
-- Collection class with two params
class Collection c e where
empty :: c
insert :: e -> c -> c
toList :: c -> [e]
instance Collection [a] a where
empty = []
insert x xs = x : xs
toList = id
-- Key-value relationship
class HasKey k v where
getValue :: k -> v
-- Multiple params with constraints
class (Eq a, Ord b) => Relate a b where
compareValues :: a -> a -> b
-- Using multi-param class
class Showable a where
show :: a -> String
class Readable a where
read :: String -> a
class Convertible a b where
convert :: a -> b
-- Multi-param with default methods
class Contains a b where
contains :: a -> b -> Bool
contains _ _ = False
-- Type relations
class Map m k v where
lookup :: k -> m -> Maybe v
insert :: k -> v -> m -> m"FlexibleInstances allow more flexible instance declarations.
- Complex Instances: More complex instances
- Type Families: In instances
- Expressiveness: Better expressiveness
// Haskell Q63: What are FlexibleInstances?
"FlexibleInstances allow more flexible instance declarations:
Extension:
{-# LANGUAGE FlexibleInstances #-}
Benefits:
1. More complex instances
2. Type families in instances
3. Better expressiveness
Examples:
-- Without FlexibleInstances
instance Eq a => Eq [a] where ...
-- With FlexibleInstances
instance Eq a => Eq (Maybe a) where ...
-- Complex instance
instance (Eq a, Eq b) => Eq (Either a b) where
(Left x) == (Left y) = x == y
(Right x) == (Right y) = x == y
_ == _ = False
-- Instance with type function
instance Eq (a, b) where
(x1, y1) == (x2, y2) = x1 == x2 && y1 == y2
-- Flexible instance with constraints
instance (Show a, Show b) => Show (a -> b) where
show _ = "<function>"
-- Instance with newtype
newtype Age = Age Int
instance Eq Age where
(Age x) == (Age y) = x == y
-- Nested instances
instance (Eq a, Eq b, Eq c) => Eq (a, b, c) where
(x1, y1, z1) == (x2, y2, z2) = x1 == x2 && y1 == y2 && z1 == z2
-- Instance with type families
type family Key a
type family Value a
instance Eq (Key a) => Eq (Value a) where
(Value x) == (Value y) = x == y"TypeOperators allow operator symbols in types.
- Custom Operators: Type-level operators
- Readability: Readable notation
- DSL Design: Domain-specific languages
// Haskell Q64: What are TypeOperators?
"TypeOperators allow operator symbols in types:
Extension:
{-# LANGUAGE TypeOperators #-}
Benefits:
1. Custom type operators
2. Readable type-level notation
3. DSL design
Examples:
-- Custom type operator
type a :+: b = Either a b
type a :*: b = (a, b)
-- Using type operators
type Person = String :*: Int
type Result = String :+: Int
-- Function with type operators
process :: Person -> Result
process (name, age) = if age > 0 then Right age else Left name
-- Type-level list operator
data a ::: b = a ::: b
infixr 5 :::
-- Type operator for maps
type k :-> v = (k, v)
-- Type-level function
type family (a :: *) :+: (b :: *) where
(a :: *) :+: (b :: *) = Either a b
-- Operator with kind
data (a :*: b) = Product a b
-- Type operator for functions
type a :-> b = a -> b
-- Type operator for constraints
type (a :&: b) = (a, b)
-- Using type operators in classes
class (a :-> b) where
apply :: a -> b
-- Type operator examples
type Vector a = [a]
type Matrix a = [[a]]
type Point a = (a, a)
type Color = (Int, Int, Int)
-- Type operator for state
type State s a = s -> (a, s)
-- Type operator for effects
type Eff a = IO a"TypeFamilies enable type-level computation.
- Open Families: Extensible
- Closed Families: Fixed
- Associated Families: In classes
// Haskell Q65: What are TypeFamilies and their use cases?
"TypeFamilies enable type-level computation:
TypeFamily Types:
1. Open families: Extensible
2. Closed families: Fixed
3. Associated families: In classes
Use Cases:
1. Generic programming
2. Type-level computation
3. DSL design
Examples:
-- Open type family
type family Element a where
type instance Element [a] = a
type instance Element (Maybe a) = a
-- Closed type family
type family Add a b where
Add Zero b = b
Add (Succ a) b = Succ (Add a b)
-- Associated type family
class Container c where
type Value c :: *
empty :: c
insert :: Value c -> c -> c
instance Container [a] where
type Value [a] = a
empty = []
insert x xs = x : xs
-- Type family for collections
type family Collection a where
Collection [a] = a
Collection (Maybe a) = a
-- Type-level computation
type family Length xs where
Length '[] = Zero
Length (x ': xs) = Succ (Length xs)
-- Type family for constraints
type family IsString a where
IsString [Char] = True
IsString a = False
-- Type family with multiple parameters
type family Merge a b where
Merge (Left a) (Left b) = Left (a,b)
Merge (Right a) (Right b) = Right (a,b)"FlexibleContexts allow more flexible context specifications.
- Complex Constraints: More complex constraints
- Type Families: In contexts
- Expressiveness: Better expressiveness
// Haskell Q66: What are FlexibleContexts?
"FlexibleContexts allow more flexible context specifications:
Extension:
{-# LANGUAGE FlexibleContexts #-}
Benefits:
1. Complex constraints
2. Type families in contexts
3. Better expressiveness
Examples:
-- Complex context
sort :: (Ord a, Show a) => [a] -> [a]
-- Context with type family
process :: (Elem a ~ Int, Collection a) => a -> Int
-- Constraint with multiple params
f :: (Foo a b, Bar b c) => a -> b -> c
-- Context in data declaration
data Eq a => Set a = Set [a]
-- Context in type synonym
type NumClass a = (Num a, Show a, Ord a)
-- Constraint with type operator
g :: (a ~ b, Show a) => a -> b -> String
-- Using FlexibleContexts with GADTs
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
eval :: (Num a, Ord a) => Expr a -> a
-- Context with newtype
newtype (Ord a) => Sorted a = Sorted [a]
-- Multiple contexts
h :: (Num a, Eq b, Show c) => a -> b -> c -> String
-- Context with functional dependencies
class Collection c e | c -> e where
empty :: c
insert :: e -> c -> c
-- Context with associated types
class Collection c where
type Elem c
empty :: c
insert :: Elem c -> c -> c"Derivation extensions automatically generate instances.
- DeriveLift: Template Haskell
- DeriveFunctor: Functor instances
- DeriveFoldable: Foldable instances
- DeriveTraversable: Traversable instances
// Haskell Q67: What are DeriveLift and other derivation extensions?
"Derivation extensions automatically generate instances:
Common Extensions:
1. DeriveLift: Template Haskell
2. DeriveFunctor: Functor instances
3. DeriveFoldable: Foldable instances
4. DeriveTraversable: Traversable instances
5. DeriveGeneric: Generic instances
Examples:
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveGeneric #-}
-- DeriveFunctor
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving (Functor)
-- DeriveFoldable
data List a = Nil | Cons a (List a)
deriving (Foldable)
-- DeriveTraversable
data Maybe a = Nothing | Just a
deriving (Traversable)
-- DeriveGeneric
data Person = Person String Int
deriving (Generic)
-- Using derived instances
sumTree :: Tree Int -> Int
sumTree = sum
-- DeriveLift with Template Haskell
data MyData = MyData Int String
deriving (Lift)
-- Multiple derivations
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving (Show, Eq, Ord, Functor, Foldable, Traversable)
-- Custom deriving
newtype Age = Age Int
deriving (Num, Eq, Ord, Show)
-- DeriveAnyClass
{-# LANGUAGE DeriveAnyClass #-}
data User = User String Int
deriving (Show, Eq, Generic, MyClass)
-- Deriving strategies
{-# LANGUAGE DerivingStrategies #-}
data MyType = MyType Int
deriving newtype (Num)
deriving stock (Show, Eq)"StandaloneDeriving declares instances separately.
- GADTs: Derive for GADTs
- Control Location: Where instances appear
- Work Around: Restrictions
// Haskell Q68: What are StandaloneDeriving?
"StandaloneDeriving declares instances separately:
Extension:
{-# LANGUAGE StandaloneDeriving #-}
Benefits:
1. Derive instances for GADTs
2. Control instance location
3. Work around restrictions
Examples:
-- GADT with deriving
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
deriving instance Show (Expr Int)
deriving instance Show (Expr Bool)
-- Standalone deriving for newtype
newtype Age = Age Int
deriving instance Num Age
deriving instance Show Age
-- Complex instance
data MyData a = MyData a
deriving instance (Show a) => Show (MyData a)
-- Standalone with multiple constraints
data Maybe a = Nothing | Just a
deriving instance (Eq a) => Eq (Maybe a)
-- GADT with multiple instances
data List a where
Nil :: List a
Cons :: a -> List a -> List a
deriving instance Show a => Show (List a)
deriving instance Eq a => Eq (List a)
-- Standalone for existential
data Showable where
Showable :: Show a => a -> Showable
deriving instance Show Showable
-- Using standalone with type families
data Family a where
Family :: a -> Family a
deriving instance (Show a) => Show (Family a)
-- Standalone for recursive types
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving instance (Show a) => Show (Tree a)"OverlappingInstances resolve ambiguous instances.
- More Specific: Specific instances
- Resolution Control: Control resolution
- Flexible Design: Flexible type class design
// Haskell Q69: What are OverlappingInstances?
"OverlappingInstances resolve ambiguous instances:
Extension:
{-# LANGUAGE OverlappingInstances #-}
Benefits:
1. More specific instances
2. Instance resolution control
3. Flexible type class design
Examples:
-- General instance
instance Show a where
show x = "Unknown"
-- More specific instance
instance Show Int where
show x = "Int: " ++ show x
-- Overlapping with constraints
instance (Show a) => Show [a] where
show xs = "List: " ++ show xs
instance Show [Char] where
show xs = "String: " ++ xs
-- Using overlapping with type families
class MyClass a where
myShow :: a -> String
instance MyClass a where
myShow _ = "Default"
instance MyClass Int where
myShow x = "Int: " ++ show x
instance MyClass [Int] where
myShow xs = "Int List: " ++ show xs
-- Overlapping in practice
class Convert a b where
convert :: a -> b
instance Convert a a where
convert x = x
instance Convert Int String where
convert = show
-- Avoiding overlap with INCOHERENT
{-# LANGUAGE IncoherentInstances #-}
-- Best practices
-- Use OverlappingInstances carefully
-- Prefer FlexibleInstances
-- Consider using type families instead"DefaultSignatures provide default method implementations.
- Reduce Boilerplate: Less code
- Type Class Design: Better design
- Generic Programming: Generic implementations
// Haskell Q70: What are DefaultSignatures?
"DefaultSignatures provide default method implementations:
Extension:
{-# LANGUAGE DefaultSignatures #-}
Benefits:
1. Default implementations
2. Reduce boilerplate
3. Type class design
Examples:
-- Basic default signature
class Show a where
show :: a -> String
default show :: (Generic a, GShow (Rep a)) => a -> String
show x = genericShow x
-- Default with constraints
class Eq a where
(==) :: a -> a -> Bool
default (==) :: (Generic a, GEq (Rep a)) => a -> a -> Bool
x == y = genericEq x y
-- Multiple defaults
class ToJSON a where
toJSON :: a -> String
default toJSON :: (GToJSON (Rep a)) => a -> String
toJSON = gToJSON
-- Default with type family
class Serialize a where
serialize :: a -> String
default serialize :: (Generic a, GSerialize (Rep a)) => a -> String
serialize = gSerialize
-- Using default signatures
data Person = Person String Int
deriving (Generic)
instance Show Person
instance ToJSON Person
instance Serialize Person
-- Default with constraints
class MyClass a where
myMethod :: a -> String
default myMethod :: Show a => a -> String
myMethod x = show x
-- Multiple default methods
class ClassWithDefaults a where
method1 :: a -> String
default method1 :: Show a => a -> String
method1 x = "Default1: " ++ show x
method2 :: a -> Int
default method2 :: Num a => a -> Int
method2 x = fromIntegral x"GeneralizedNewtypeDeriving derives instances for newtypes.
- Instance Reuse: Reuse instances
- Zero-cost: Abstraction
- Type Safety: Safe newtypes
// Haskell Q71: What are GeneralizedNewtypeDeriving?
"GeneralizedNewtypeDeriving derives instances for newtypes:
Extension:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
Benefits:
1. Instance reuse
2. Zero-cost abstraction
3. Type safety
Examples:
-- Newtype with deriving
newtype Age = Age Int
deriving (Show, Eq, Ord, Num, Enum, Real, Integral)
-- Using derived instances
age1 = Age 25
age2 = Age 30
age3 = age1 + age2 -- Age 55
-- Newtype with custom class
class Printable a where
print :: a -> String
instance Printable Int where
print n = "Int: " ++ show n
newtype MyInt = MyInt Int
deriving (Printable)
-- Newtype with Functor
newtype Identity a = Identity a
deriving (Functor)
-- Multiple derivations
newtype Name = Name String
deriving (Show, Eq, Ord, Read)
-- Deriving with constraints
newtype State s a = State { runState :: s -> (a, s) }
deriving (Functor, Applicative, Monad)
-- Newtype with Monad
newtype MyMonad a = MyMonad (State Int a)
deriving (Functor, Applicative, Monad, MonadState Int)
-- Using newtype for type safety
newtype Meters = Meters Double
deriving (Num, Fractional, Show)
newtype Kilometers = Kilometers Double
deriving (Num, Fractional, Show)
toMeters :: Kilometers -> Meters
toMeters (Kilometers km) = Meters (km * 1000)
-- Newtype with multiple classes
newtype Email = Email String
deriving (Show, Eq, Ord, Read, IsString)"EmptyDataDecls allow data types with no constructors.
- Phantom Types: Type-level programming
- Uninhabited Types: Types with no values
- Type Markers: Mark types at compile time
// Haskell Q72: What are EmptyDataDecls?
"EmptyDataDecls allow data types with no constructors:
Extension:
{-# LANGUAGE EmptyDataDecls #-}
Benefits:
1. Phantom types
2. Type-level programming
3. Uninhabited types
Examples:
-- Empty data declaration
data Void
-- Phantom types
data Meter
data Second
data Quantity a = Quantity Double
toMeters :: Quantity Meter -> Quantity Meter
toMeters x = x
-- Using empty data for safety
data Unvalidated
data Validated
data User a = User { name :: String, age :: Int }
validateUser :: User Unvalidated -> Maybe (User Validated)
validateUser user =
if age user > 0
then Just (User (name user) (age user))
else Nothing
-- Empty data for type-level state
data Locked
data Unlocked
data Door s = Door { isOpen :: Bool }
openDoor :: Door Locked -> Door Unlocked
openDoor (Door _) = Door True
-- Empty data for type-level flags
data Debug
data Production
data App a = App
runApp :: App Production -> IO ()
runApp _ = putStrLn "Production mode"
-- Empty data with kind annotation
{-# LANGUAGE DataKinds #-}
data Size = Small | Large
-- Using empty data for type-level computation
data Zero
data Succ n
type family Add a b where
Add Zero b = b
Add (Succ a) b = Succ (Add a b)
-- Empty data as type markers
data Public
data Private
data API a = API
-- Using empty data for type safety
data IntList
data CharList
newtype MyList a = MyList [Int]
toIntList :: MyList a -> MyList IntList
toIntList (MyList xs) = MyList xs"DataKinds promotes data types to kinds.
- Type-level: Programming
- More Precise: Types
- Dependent-like: Dependent types
// Haskell Q73: What are DataKinds?
"DataKinds promotes data types to kinds:
Extension:
{-# LANGUAGE DataKinds #-}
Benefits:
1. Type-level programming
2. More precise types
3. Dependent-like types
Examples:
-- Promoting data to kind
data Nat = Zero | Succ Nat
-- Using promoted types
data Vector (n :: Nat) a where
VNil :: Vector Zero a
VCons :: a -> Vector n a -> Vector (Succ n) a
-- Type-safe vector operations
vHead :: Vector (Succ n) a -> a
vHead (VCons x _) = x
-- Promoting list to kind
data NList a = NList
type family Length (xs :: [a]) :: Nat where
Length '[] = Zero
Length (x ': xs) = Succ (Length xs)
-- Using promoted bool
type family If (b :: Bool) (a :: *) (b :: *) where
If True a _ = a
If False _ b = b
-- Promoted tuple
type family Fst (x :: (a,b)) :: a where
Fst '(a,b) = a
-- Using DataKinds with GADTs
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
-- Type-safe list
data List' (n :: Nat) a where
Nil' :: List' Zero a
Cons' :: a -> List' n a -> List' (Succ n) a
-- Appending type-safe lists
append :: List' n a -> List' m a -> List' (Add n m) a
append Nil' ys = ys
append (Cons' x xs) ys = Cons' x (append xs ys)
-- Promoted Maybe
data Maybe' a = Nothing' | Just' a
type family IsJust (x :: Maybe' a) :: Bool where
IsJust (Just' _) = True
IsJust Nothing' = False"KindSignatures explicitly specify kinds.
- Documentation: Type-level documentation
- Enforce: Kind correctness
- Better Errors: Better error messages
// Haskell Q74: What are KindSignatures?
"KindSignatures explicitly specify kinds:
Extension:
{-# LANGUAGE KindSignatures #-}
Benefits:
1. Type-level documentation
2. Enforce kind correctness
3. Better error messages
Examples:
-- Explicit kind signature
data Maybe (a :: *) = Nothing | Just a
-- Kind signature for type constructor
data Pair (a :: *) (b :: *) = Pair a b
-- Kind signature for higher-kinded type
data Functor (f :: * -> *) = Functor
-- Kind signature for data kind
data Nat = Zero | Succ Nat
data Vector (n :: Nat) (a :: *) where
VNil :: Vector Zero a
VCons :: a -> Vector n a -> Vector (Succ n) a
-- Kind signature for type class
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
-- Kind signature for type family
type family Add (a :: Nat) (b :: Nat) :: Nat where
Add Zero b = b
Add (Succ a) b = Succ (Add a b)
-- Kind signature for GADT
data Expr (a :: *) where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
-- Kind signature with constraints
data Valid (a :: *) where
Valid :: (Show a, Read a) => a -> Valid a
-- Kind signature for type synonym
type State (s :: *) = s
-- Kind signature for data family
data family Vector (a :: *)
-- Kind signature for associated type
class Collection c where
type Elem (c :: *) :: *"TypeApplications allows explicit type application.
- Resolve Ambiguity: Clear types
- Control Instance: Instance selection
- Type-level: Programming
// Haskell Q75: What are TypeApplications?
"TypeApplications allows explicit type application:
Extension:
{-# LANGUAGE TypeApplications #-}
Benefits:
1. Resolve ambiguity
2. Control instance selection
3. Use type-level programming
Examples:
-- Basic type application
read :: Read a => String -> a
read @Int "5" -- 5
read @Bool "True" -- True
-- Type application with functions
id :: a -> a
id @Int 5 -- 5
id @String "hello" -- "hello"
-- Type application with polymorphic functions
show @Int 5 -- "5"
show @Bool True -- "True"
-- Type application with type variables
f :: forall a b. a -> b -> a
f @Int @String 5 "hello" -- 5
-- Type application in patterns
g :: forall a. Show a => a -> String
g @Int x = show (x + 1)
g @String x = x ++ "!"
-- Type application with partial application
h :: forall a b. a -> b -> a
hInt = h @Int
hInt 5 "hello" -- 5
-- Type application with constraints
foo :: forall a. (Num a, Show a) => a -> String
foo = show . (+1)
foo @Int 5 -- "6"
-- Type application with data types
maybe :: forall a. a -> Maybe a
maybe @Int 5 -- Just 5
-- Type application with type classes
sum :: Num a => [a] -> a
sum @Int [1,2,3] -- 6
-- Type application with visible type application
bar :: forall a. a -> (forall b. b -> a) -> a
bar x f = f x
bar @Int 5 @String "hello" -- 5"TypeInType allows types in types.
- Dependent Types: Type-level programming
- More Powerful: Types
- Type-level: Computation
// Haskell Q76: What are TypeInType?
"TypeInType allows types in types:
Extension:
{-# LANGUAGE TypeInType #-}
Benefits:
1. Dependent types
2. Type-level programming
3. More powerful types
Examples:
-- Type-level numbers
data Nat = Zero | Succ Nat
type family Add (a :: Nat) (b :: Nat) :: Nat where
Add Zero b = b
Add (Succ a) b = Succ (Add a b)
-- Dependent vector
data Vector (n :: Nat) a where
VNil :: Vector Zero a
VCons :: a -> Vector n a -> Vector (Succ n) a
-- Type-level function
type family Map (f :: a -> b) (xs :: [a]) :: [b] where
Map f '[] = '[]
Map f (x ': xs) = f x ': Map f xs
-- Type-level proof
data Equal a b where
Refl :: Equal a a
-- Type-level singleton
data SNat (n :: Nat) where
SZero :: SNat Zero
SSucc :: SNat n -> SNat (Succ n)
-- Using in function
vCons :: a -> Vector n a -> Vector (Succ n) a
vCons x xs = VCons x xs
-- Type-level computation
type family Length (xs :: [a]) :: Nat where
Length '[] = Zero
Length (x ': xs) = Succ (Length xs)
-- Type-level append
type family Append (xs :: [a]) (ys :: [a]) :: [a] where
Append '[] ys = ys
Append (x ': xs) ys = x ': Append xs ys
-- Type-level reverse
type family Reverse (xs :: [a]) :: [a] where
Reverse '[] = '[]
Reverse (x ': xs) = Append (Reverse xs) '[x]"ConstraintKinds promotes constraints to kinds.
- Type-level: Constraints
- Generic Programming: Generic programming
- Constraint Composition: Compose constraints
// Haskell Q77: What are ConstraintKinds?
"ConstraintKinds promotes constraints to kinds:
Extension:
{-# LANGUAGE ConstraintKinds #-}
Benefits:
1. Type-level constraints
2. Generic programming
3. Constraint composition
Examples:
-- Constraint kind
type NumConstraint a = (Num a, Show a)
-- Function with constraint kind
process :: NumConstraint a => a -> String
process x = show (x + 1)
-- Type synonym for constraint
type ToJSON a = Serialize a
-- Constraint in data type
data Dict (c :: Constraint) where
Dict :: c => Dict c
-- Using Dict
dInt :: Dict (Num Int)
dInt = Dict
-- Constraint composition
type Person a = (Show a, Read a, Eq a)
-- Constraint kind in classes
class (Show a, Read a) => Serializable a
-- Constraint with type families
type family IsString a :: Constraint where
IsString [Char] = ()
IsString a = TypeError (Text "Not a string")
-- Using constraints in GADTs
data Showable where
Showable :: (Show a) => a -> Showable
-- Constraint kind in type families
type family ConstraintFrom a where
ConstraintFrom Int = NumConstraint Int
ConstraintFrom String = Show String
-- Constraint kind with type operators
type (c :&: d) a = (c a, d a)
-- Constraint composition example
type EqShow a = (Eq a, Show a)
-- Constraint kind in newtypes
newtype (c a) => Wrapped a = Wrapped a"PolyKinds allows polymorphic kinds.
- Kind Polymorphism: Generic type-level programming
- Reusable: Code
- Generic: Programming
// Haskell Q78: What are PolyKinds?
"PolyKinds allows polymorphic kinds:
Extension:
{-# LANGUAGE PolyKinds #-}
Benefits:
1. Kind polymorphism
2. Generic type-level programming
3. More reusable code
Examples:
-- Kind polymorphic data
data Proxy (a :: k) = Proxy
-- Kind polymorphic function
proxy :: Proxy a -> Proxy a
proxy x = x
-- Kind polymorphic type family
type family F (a :: k) :: *
-- Kind polymorphic class
class C (a :: k) where
method :: a -> String
-- Kind polymorphic GADT
data G (a :: k) where
GInt :: G Int
GString :: G String
GMaybe :: G (Maybe a)
-- Using PolyKinds
data Nat = Zero | Succ Nat
data Vector (n :: Nat) (a :: *) = Vector
-- Kind polymorphic singleton
data Sing (a :: k) where
SInt :: Sing Int
SString :: Sing String
-- Kind polymorphic type synonym
type Kinded (a :: k) = a
-- Kind polymorphic data family
data family DF (a :: k)
-- Kind polymorphic in class
class Class (a :: k) where
type ClassType (a :: k) :: *
-- Using PolyKinds for generic programming
type family Rep (a :: k) :: *
type instance Rep Int = IntRep
type instance Rep Bool = BoolRep
-- Kind polymorphic with DataKinds
data MyKind = A | B
data KindedData (a :: MyKind) = KindedData"Generics provide generic programming.
- Generic Class: type Rep a
- Generic Functions: Generic programming
- Reduce Boilerplate: Less code
// Haskell Q79: What are DeriveGeneric and Generics?
"Generics provide generic programming:
Generic Class:
class Generic a where
type Rep a :: *
from :: a -> Rep a
to :: Rep a -> a
Benefits:
1. Generic functions
2. Reduce boilerplate
3. Type-safe metaprogramming
Examples:
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
-- Deriving Generic
data Person = Person String Int
deriving (Generic)
-- Generic to JSON
class ToJSON a where
toJSON :: a -> String
instance ToJSON Person where
toJSON = gToJSON
-- Generic serialization
class Serialize a where
serialize :: a -> String
instance (Generic a, GSerialize (Rep a)) => Serialize a where
serialize = gSerialize
-- Generic representation
data User = User
{ userName :: String
, userAge :: Int
} deriving (Generic)
-- Generic function for Show
class GShow (f :: * -> *) where
gshow :: f a -> String
instance (GShow f, GShow g) => GShow (f :*: g) where
gshow (a :*: b) = gshow a ++ gshow b
instance (Show c) => GShow (K1 i c) where
gshow (K1 x) = show x
-- Using Generic
instance (Generic a, GShow (Rep a)) => Show a where
show x = gshow (from x)
-- Generic equality
class GEq (f :: * -> *) where
geq :: f a -> f a -> Bool
instance (GEq f, GEq g) => GEq (f :*: g) where
geq (a1 :*: b1) (a2 :*: b2) = geq a1 a2 && geq b1 b2
instance (Eq c) => GEq (K1 i c) where
geq (K1 x) (K1 y) = x == y
-- Using Generic for default implementations
instance (Generic a, GEq (Rep a)) => Eq a where
(==) = geq (from x) (from y)"DeriveAnyClass automatically derives instances.
- Automatic Generation: Instances
- Reduce Boilerplate: Less code
- Generic Programming: Generic programming
// Haskell Q80: What are DeriveAnyClass?
"DeriveAnyClass automatically derives instances:
Extension:
{-# LANGUAGE DeriveAnyClass #-}
Benefits:
1. Automatic instance generation
2. Reduce boilerplate
3. Generic programming
Examples:
-- Define a class with default methods
class MyClass a where
method :: a -> String
default method :: (Show a) => a -> String
method = show
-- Derive instance
data Person = Person String Int
deriving (MyClass)
-- Using derived instance
person = Person "Alice" 25
print (method person) -- "Person "Alice" 25"
-- Class with multiple methods
class JSON a where
toJSON :: a -> String
fromJSON :: String -> Maybe a
default toJSON :: (Generic a, GToJSON (Rep a)) => a -> String
toJSON = gToJSON
-- Derive JSON
data User = User String Int
deriving (Generic, JSON)
-- Class with constraints
class ShowPretty a where
pretty :: a -> String
default pretty :: (Show a) => a -> String
pretty = show
-- Derive with multiple classes
data MyData = MyData Int String
deriving (Show, Eq, Ord, MyClass, JSON)
-- Using DeriveAnyClass with GHC.Generics
class Serialize a where
serialize :: a -> String
instance (Generic a, GSerialize (Rep a)) => Serialize a where
serialize = gSerialize
-- Derive Serialize
data Person = Person String Int
deriving (Generic, Serialize)
-- DeriveAnyClass for type classes
class Default a where
def :: a
default def :: (Generic a, GDefault (Rep a)) => a
def = to gdef
-- Derive Default
data Config = Config Bool Int String
deriving (Generic, Default)"BangPatterns force strict evaluation.
- Performance: Optimization
- Space Leak: Prevention
- Strict Evaluation: Control
// Haskell Q81: What are BangPatterns?
"BangPatterns force strict evaluation:
Extension:
{-# LANGUAGE BangPatterns #-}
Benefits:
1. Performance optimization
2. Space leak prevention
3. Strict evaluation control
Examples:
-- Strict function
sum' :: [Int] -> Int
sum' xs = go 0 xs
where
go !acc [] = acc
go !acc (x:xs) = go (acc + x) xs
-- Strict pattern matching
strictCase :: Maybe Int -> Int
strictCase !x = case x of
Nothing -> 0
Just n -> n
-- Strict in data types
data StrictData = StrictData !Int !String
-- Strict tuple
strictTuple :: (Int, String)
strictTuple = (1, "hello")
-- Strict in where clauses
calculate x y = result
where
!result = x + y
-- Strictness in function arguments
f !x !y = x + y
-- Strict list
data StrictList a = SNil | SCons !a !(StrictList a)
-- Pattern matching with bang
head' :: [a] -> a
head' (x:_) = x
head' [] = error "Empty list"
-- Using strictness for performance
foldl' :: (b -> a -> b) -> b -> [a] -> b
foldl' f !acc [] = acc
foldl' f !acc (x:xs) = foldl' f (f acc x) xs
-- Strictness in do notation
main = do
!x <- getLine
putStrLn x"StrictData makes data types strict by default.
- Performance: Improvement
- Space Leak: Prevention
- Predictable: Evaluation
// Haskell Q82: What are StrictData?
"StrictData makes data types strict by default:
Extension:
{-# LANGUAGE StrictData #-}
Benefits:
1. Performance improvement
2. Space leak prevention
3. Predictable evaluation
Examples:
-- Strict data type
data Person = Person
{ name :: String
, age :: Int
}
-- Fields are strict
-- Person { name = undefined, age = 5 } would fail
-- Using with lazy fields
data LazyData = LazyData
{ lazyField :: ~String -- Lazy
, strictField :: Int -- Strict
}
-- Strict record syntax
data Config = Config
{ configFile :: !FilePath
, configDebug :: !Bool
, configLazy :: ~String -- Explicitly lazy
}
-- Strict data with multiple constructors
data Maybe a = Nothing | Just !a
-- Strict in pattern matching
caseMaybe :: Maybe Int -> String
caseMaybe (Just !x) = show x
caseMaybe Nothing = "Nothing"
-- Strict newtype
newtype Age = Age !Int
-- Strict in type synonyms
type StrictPair a b = (a, b) -- Fields strict
-- Using StrictData with BangPatterns
data Foo = Foo !Int !String -- Both strict
-- Strict data in practice
data Stack a = Empty | Push !a !(Stack a)
-- Lazy fields in strict data
data Mixed = Mixed
{ strict :: !Int
, lazy :: ~String
}"Strict makes entire module strict.
- Performance: Optimization
- Predictable: Evaluation
- Space Leak: Prevention
// Haskell Q83: What are Strict language extension?
"Strict makes entire module strict:
Extension:
{-# LANGUAGE Strict #-}
Benefits:
1. Performance optimization
2. Predictable evaluation
3. Space leak prevention
Examples:
-- Entire module is strict
{-# LANGUAGE Strict #-}
-- All functions are strict by default
add x y = x + y -- Strict
-- Explicit lazy
lazyAdd x y = x + y
where
~(a,b) = (x,y)
-- Strict data types
data Person = Person String Int -- Fields strict
-- Strict list
data List a = Nil | Cons a (List a) -- Fields strict
-- Strict function with lazy pattern
strictFunction :: Int -> Int
strictFunction x = x + 1
-- Lazy pattern matching
lazyMatch (x:xs) = x -- Lazy
-- Strict in do notation
main = do
x <- getLine -- Strict
putStrLn x
-- Overriding strictness
data LazyData = LazyData ~String -- Lazy field
-- Strictness in type classes
class MyClass a where
method :: a -> a -- Strict by default
-- Combining with other extensions
{-# LANGUAGE Strict, BangPatterns #-}
-- Strictness and patterns
caseValue :: Maybe Int -> String
caseValue (Just !x) = show x
caseValue Nothing = "Nothing"UnboxedTuples provide unboxed tuple types.
- Performance: Optimization
- Memory Efficiency: Efficient
- Low-level: Control
// Haskell Q84: What are UnboxedTuples?
"UnboxedTuples provide unboxed tuple types:
Extension:
{-# LANGUAGE UnboxedTuples #-}
Benefits:
1. Performance optimization
2. Memory efficiency
3. Low-level control
Examples:
-- Unboxed tuple
(# Int, String #)
-- Function returning unboxed tuple
returnTwo :: Int -> (# Int, Int #)
returnTwo x = (# x, x + 1 #)
-- Using unboxed tuple
process (# a, b #) = a + b
-- Unboxed tuple in IO
getTwo :: IO (# Int, Int #)
getTwo = do
x <- getInt
y <- getInt
return (# x, y #)
-- Unboxed tuple with type variables
f :: a -> (# a, a #)
f x = (# x, x #)
-- Unboxed tuple in patterns
g (# x, y #) = x + y
-- Using unboxed tuples for performance
sumPairs :: [(Int, Int)] -> (Int, Int)
sumPairs xs = (# sumX, sumY #)
where
(# sumX, sumY #) = foldr add (# 0, 0 #) xs
add (x,y) (# sx, sy #) = (# sx + x, sy + y #)
-- Unboxed tuple with strictness
h (# !x, !y #) = x + y
-- Unboxed tuple in data types
data MyData = MyData (# Int, String #)
-- Unboxed tuple with type applications
i :: forall a. a -> (# a, a #)
i x = (# x, x #)
j = i @Int 5
-- Unboxed tuple in class
class MyClass a where
method :: a -> (# a, String #)"MagicHash provides access to primitive operations.
- Low-level: Operations
- Performance: Optimization
- FFI: Integration
// Haskell Q85: What are MagicHash and unboxed types?
"MagicHash provides access to primitive operations:
Extension:
{-# LANGUAGE MagicHash #-}
Benefits:
1. Low-level operations
2. Performance optimization
3. FFI integration
Examples:
-- Unboxed integers
data Int# = Int#
-- Unboxed operations
addInt# :: Int# -> Int# -> Int#
addInt# x y = x +# y
-- Using unboxed types
{-# LANGUAGE MagicHash #-}
import GHC.Prim
-- Primitive operations
toInt :: Int# -> Int
toInt (I# x) = I# x
-- Unboxed character
data Char# = Char#
-- Unboxed float
data Float# = Float#
-- Unboxed operations
floatAdd :: Float# -> Float# -> Float#
floatAdd x y = x +## y
-- Unboxed double
data Double# = Double#
-- Word operations
data Word# = Word#
-- Adding with unboxed types
addWrapper :: Int -> Int -> Int
addWrapper (I# x) (I# y) = I# (x +# y)
-- Unboxed boolean
data Int# = Int#
-- Using unboxed types in FFI
foreign import ccall "add" c_add :: Int# -> Int# -> Int#
-- Unboxed tuple with MagicHash
data MyData = MyData (# Int#, String #)
-- Primitive array
data ByteArray# = ByteArray#
-- Unboxed operations for performance
fastSum :: [Int] -> Int
fastSum xs = I# (go 0# xs)
where
go :: Int# -> [Int] -> Int#
go acc [] = acc
go acc (I# x:xs) = go (acc +# x) xs"OverloadedStrings allows string literals for any type.
- String-like: Types
- Text Optimization: Efficient text
- DSL Design: Domain-specific languages
// Haskell Q86: What are OverloadedStrings?
"OverloadedStrings allows string literals for any type:
Extension:
{-# LANGUAGE OverloadedStrings #-}
Benefits:
1. String-like types
2. Text optimization
3. DSL design
Examples:
-- Using with Text
import Data.Text (Text)
import qualified Data.Text as T
text :: Text
text = "hello" -- Overloaded string
-- Using with ByteString
import Data.ByteString (ByteString)
bs :: ByteString
bs = "world"
-- Using with custom type
data MyString = MyString String
instance IsString MyString where
fromString = MyString
myStr :: MyString
myStr = "hello world"
-- Overloaded strings in JSON
import Data.Aeson
data Person = Person { name :: String, age :: Int }
instance FromJSON Person where
parseJSON = withObject "Person" $ \v -> Person
<$> v .: "name"
<*> v .: "age"
-- Using with SQL
data SQL = SQL String
instance IsString SQL where
fromString = SQL
query :: SQL
query = "SELECT * FROM users"
-- Overloaded strings in HTML
data HTML = HTML String
instance IsString HTML where
fromString = HTML
html :: HTML
html = "<div>Hello</div>"
-- Using with URI
data URI = URI String
instance IsString URI where
fromString = URI
uri :: URI
uri = "http://example.com"
-- Overloaded strings with type annotation
text2 :: Text
text2 = "annotated"
-- Using with template Haskell
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
string = [q|"hello"|]"OverloadedLists allows list literals for any type.
- List-like: Types
- Custom Collections: Collections
- DSL Design: Domain-specific languages
// Haskell Q87: What are OverloadedLists?
"OverloadedLists allows list literals for any type:
Extension:
{-# LANGUAGE OverloadedLists #-}
Benefits:
1. List-like types
2. Custom collections
3. DSL design
Examples:
-- Using with Vector
import Data.Vector (Vector, fromList)
vec :: Vector Int
vec = [1,2,3,4,5] -- Overloaded list
-- Using with Set
import Data.Set (Set, fromList)
set :: Set Int
set = [1,2,3,4,5]
-- Using with custom type
data MyList a = MyList [a]
instance IsList (MyList a) where
type Item (MyList a) = a
fromList = MyList
toList (MyList xs) = xs
myList :: MyList Int
myList = [1,2,3]
-- Using with Map
import Data.Map (Map, fromList)
map :: Map Int String
map = [(1,"one"), (2,"two")]
-- Using with Text
import Data.Text (Text, pack)
text :: Text
text = ["hello", "world"] -- Not supported by default
-- Using with HashMap
import Data.HashMap.Strict (HashMap, fromList)
hashmap :: HashMap Int String
hashmap = [(1,"one"), (2,"two")]
-- Using with custom monoid
newtype Count = Count Int
instance IsList Count where
type Item Count = Int
fromList xs = Count (sum xs)
toList (Count x) = [x]
count :: Count
count = [1,2,3,4,5] -- Count 15
-- Using with Seq
import Data.Sequence (Seq, fromList)
seq :: Seq Int
seq = [1,2,3]
-- Overloaded lists in pattern matching
match (x:xs) = x + sum xs"ViewPatterns allows pattern matching with functions.
- Cleaner Code: More readable
- Expressiveness: More expressive patterns
- Computation: Pattern matching with computation
// Haskell Q88: What are ViewPatterns?
"ViewPatterns allows pattern matching with functions:
Extension:
{-# LANGUAGE ViewPatterns #-}
Benefits:
1. Pattern matching with computation
2. Cleaner code
3. More expressive patterns
Examples:
-- Basic view pattern
f (length -> 0) = "Empty"
f (length -> 1) = "Single"
f (length -> n) = show n
-- Using view patterns with Maybe
g (Just . read -> Just n) = n + 1
g _ = 0
-- View pattern with multiple variables
h (x -> a, y -> b) = a + b
-- Complex view pattern
parse (words -> ["add", x, y]) = read x + read y
parse (words -> ["mul", x, y]) = read x * read y
parse _ = 0
-- View pattern with records
data Person = Person { name :: String, age :: Int }
isAdult (age -> a) = a >= 18
-- Nested view patterns
process (reverse -> (head -> x)) = x
-- View pattern with where
isEven (\n -> n `mod` 2 == 0 -> True) = "Even"
isEven _ = "Odd"
-- View pattern with list comprehension
evens (filter even -> xs) = sum xs
-- View pattern in case expression
case "123" of
(read -> n) -> n + 1
-- View pattern with complex function
toUpperAll (map toUpper -> s) = s
-- View pattern with type class
showIt (show -> s) = s ++ "!"PatternSynonyms allow creating custom patterns.
- Custom Patterns: Create patterns
- Abstract Data Types: Hide implementation
- Bidirectional: Bidirectional patterns
// Haskell Q89: What are PatternSynonyms?
"PatternSynonyms allow creating custom patterns:
Extension:
{-# LANGUAGE PatternSynonyms #-}
Benefits:
1. Custom pattern matching
2. Abstract data types
3. Bidirectional patterns
Examples:
-- Basic pattern synonym
pattern Cons x xs = x : xs
-- Using pattern synonym
head' (Cons x _) = x
-- Bidirectional pattern
pattern Nil = []
-- Pattern with constraints
pattern Even n <- (n `mod` 2 == 0 -> True)
where Even n = n * 2
-- Using pattern
isEven (Even _) = True
isEven _ = False
-- Pattern with multiple arguments
pattern Pair a b = (a, b)
-- Pattern with type
pattern Str s = (s :: String)
-- Pattern with GADT
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
pattern Lit n = IntLit n
-- Pattern with record
data Person = Person { name :: String, age :: Int }
pattern P n a = Person { name = n, age = a }
-- Pattern with view pattern
pattern RightString s <- Right (s :: String)
-- Pattern with existential
data Showable = forall a. Show a => Showable a
pattern Showable' x = Showable x
-- Using pattern synonyms
f (P n a) = n ++ show a
-- Pattern with list
pattern Head x = x : _
-- Pattern with tuple
pattern Triple a b c = (a, (b, c))
-- Pattern with Maybe
pattern Just' x = Just x
pattern Nothing' = Nothing"RebindableSyntax allows rebinding built-in syntax.
- Custom DSLs: Domain-specific languages
- Alternative Interpretations: Different meanings
- Control: Over syntactic sugar
// Haskell Q90: What are RebindableSyntax?
"RebindableSyntax allows rebinding built-in syntax:
Extension:
{-# LANGUAGE RebindableSyntax #-}
Benefits:
1. Custom DSLs
2. Alternative interpretations
3. Control over syntactic sugar
Examples:
-- Rebind if-then-else
myIf :: Bool -> a -> a -> a
myIf True x _ = x
myIf False _ y = y
-- Using rebindable syntax
if True then 5 else 6 -- Uses myIf
-- Rebind do notation
myBind :: Maybe a -> (a -> Maybe b) -> Maybe b
myBind (Just x) f = f x
myBind Nothing _ = Nothing
myReturn :: a -> Maybe a
myReturn = Just
-- Using do with myBind
do
x <- Just 5
return (x + 1)
-- Rebind list comprehension
myMap :: (a -> b) -> [a] -> [b]
myMap = map
myFilter :: (a -> Bool) -> [a] -> [a]
myFilter = filter
-- Using list comprehension
[x | x <- [1..10], even x]
-- Rebind arithmetic
myAdd :: Int -> Int -> Int
myAdd x y = x + y
-- Using arithmetic
5 + 6 -- Uses myAdd
-- Rebind fromInteger
myFromInteger :: Integer -> Int
myFromInteger = fromInteger
-- Using numeric literals
5 -- Uses myFromInteger
-- Rebind fail
myFail :: String -> Maybe a
myFail _ = Nothing
-- Using fail in do
do
x <- Just 5
fail "error"
-- Rebind mdo
{-# LANGUAGE RebindableSyntax, RecursiveDo #-}
myMdo :: m a -> m a
myMdo = id"DoAndIfThenElse enables custom do and if syntax.
- Custom DSLs: Domain-specific languages
- Alternative Monads: Different monads
- More Control: Over syntax
// Haskell Q91: What are DoAndIfThenElse?
"DoAndIfThenElse enables custom do and if syntax:
Extension:
{-# LANGUAGE DoAndIfThenElse #-}
Benefits:
1. Custom DSLs
2. Alternative monads
3. More control
Examples:
-- Custom if
myIf :: Bool -> a -> a -> a
myIf True x _ = x
myIf False _ y = y
-- Using if
if 5 > 3 then "Yes" else "No"
-- Custom do
data MyMonad a = MyMonad a
myBind :: MyMonad a -> (a -> MyMonad b) -> MyMonad b
myBind (MyMonad x) f = f x
myReturn :: a -> MyMonad a
myReturn = MyMonad
-- Using do
do
x <- MyMonad 5
return (x + 1)
-- Custom fail
myFail :: String -> MyMonad a
myFail _ = MyMonad undefined
-- Using fail in do
do
x <- MyMonad 5
fail "error"
return x
-- Custom guard
myGuard :: Bool -> MyMonad ()
myGuard True = MyMonad ()
myGuard False = fail "guard"
-- Using guard
do
guard (5 > 3)
return True
-- Custom mdo
{-# LANGUAGE RecursiveDo #-}
myMdo :: m a -> m a
myMdo = id
-- Using mdo
mdo
x <- return 5
return x
-- Custom arrow
myArr :: (a -> b) -> a -> b
myArr f x = f x
-- Using arrow syntax
{-# LANGUAGE Arrows #-}
proc x -> returnA -< x + 1"TemplateHaskell enables compile-time metaprogramming.
- Code Generation: Generate code
- Compile-time: Computation
- DSL Embedding: Domain-specific languages
// Haskell Q92: What are TemplateHaskell and QuasiQuotes?
"TemplateHaskell enables compile-time metaprogramming:
Extensions:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
Benefits:
1. Code generation
2. Compile-time computation
3. DSL embedding
Examples:
-- Template Haskell basics
import Language.Haskell.TH
-- Generating function
double :: Q [Dec]
double = do
let name = mkName "double"
let expr = [| \x -> x * 2 |]
return [FunD name [Clause [VarP (mkName "x")] (NormalB expr) []]]
$(double)
-- Using generated function
result = double 5 -- 10
-- QuasiQuotes
data SQL = SQL String
sql :: QuasiQuoter
sql = QuasiQuoter
{ quoteExp = \s -> [| SQL s |]
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
-- Using quasi quote
query = [sql|SELECT * FROM users|]
-- Template Haskell for deriving instances
deriveJSON :: Name -> Q [Dec]
deriveJSON name = do
-- Implementation
return []
$(deriveJSON ''Person)
-- QuasiQuotes for regular expressions
import Text.Regex.PCRE.Heavy
regex :: QuasiQuoter
regex = ...
-- Using regex
matches = [re|^[a-z]+$|] "hello"
-- Template Haskell for debugging
$(print "Generating code at compile time")
-- QuasiQuotes for HTML
html :: QuasiQuoter
html = ...
-- Using HTML
page = [html|<div>Hello</div>|]
-- Template Haskell for performance
$(do
putStrLn "Generating optimized code"
return [])
-- QuasiQuotes for JSON
json :: QuasiQuoter
json = ...
-- Using JSON
data = [json|{"name": "Alice", "age": 25}|]QuasiQuotes enable domain-specific languages.
- Custom Syntax: Domain-specific syntax
- Type-safe: Embedding
- Compile-time: Validation
// Haskell Q93: What are QuasiQuotes for DSLs?
"QuasiQuotes enable domain-specific languages:
Benefits:
1. Custom syntax
2. Type-safe embedding
3. Compile-time validation
Examples:
-- SQL QuasiQuoter
{-# LANGUAGE QuasiQuotes #-}
sql :: QuasiQuoter
sql = QuasiQuoter
{ quoteExp = \s -> do
-- Validate SQL syntax
-- Generate typed expression
return [| SQL s |]
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
-- Using SQL
query :: SQL
query = [sql|SELECT name, age FROM users WHERE age > 18|]
-- Regex QuasiQuoter
regex :: QuasiQuoter
regex = QuasiQuoter
{ quoteExp = \s -> do
-- Compile regex at compile time
return [| compileRegex s |]
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
-- Using regex
isEmail = [re|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$|]
-- HTML QuasiQuoter
html :: QuasiQuoter
html = QuasiQuoter
{ quoteExp = \s -> do
-- Parse HTML at compile time
return [| HTML s |]
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
-- Using HTML
page :: HTML
page = [html|<div class="container"><h1>Hello</h1></div>|]
-- JSON QuasiQuoter
json :: QuasiQuoter
json = QuasiQuoter
{ quoteExp = \s -> do
-- Parse JSON at compile time
return [| parseJSON s |]
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
-- Using JSON
person :: Person
person = [json|{"name":"Alice","age":25}|]
-- XML QuasiQuoter
xml :: QuasiQuoter
xml = ...
-- YAML QuasiQuoter
yaml :: QuasiQuoter
yaml = ...
-- Custom DSL for arithmetic
arith :: QuasiQuoter
arith = ...TypeSynonymInstances allows instances for type synonyms.
- Flexible: Instance declarations
- Type-level: Abstraction
- Type Synonyms: In instances
// Haskell Q94: What are TypeSynonymInstances?
"TypeSynonymInstances allows instances for type synonyms:
Extension:
{-# LANGUAGE TypeSynonymInstances #-}
Benefits:
1. Type synonyms in instances
2. Flexible instance declarations
3. Type-level abstraction
Examples:
-- Type synonym
type StringList = [String]
-- Instance with type synonym
instance Show StringList where
show xs = "StringList: " ++ show xs
-- Complex type synonym
type Map k v = [(k, v)]
instance Show (Map String Int) where
show m = "Map: " ++ show m
-- Type synonym with constraints
type NumList a = [a]
instance Num a => Show (NumList a) where
show xs = "NumList: " ++ show xs
-- Type synonym in class
type JSON a = a
instance Show (JSON Person) where
show p = "JSON: " ++ show p
-- Type synonym with multiple parameters
type Pair a b = (a, b)
instance (Show a, Show b) => Show (Pair a b) where
show (x,y) = "Pair: " ++ show x ++ " " ++ show y
-- Type synonym in newtype
type Age = Int
newtype Person = Person (String, Age)
-- Using type synonym in instances
instance Show Age where
show (Age x) = show x
-- Type synonym with higher-kinded types
type Functor' f a = f a
instance Functor (Functor' Maybe) where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
-- Type synonym in context
type NumShow a = (Num a, Show a)
instance NumShow Int where
-- Implementation
-- Type synonym in data declaration
data Container a = Container (Vector a)
type Vector a = [a]UndecidableInstances allows complex instance resolution.
- Advanced: Type-level programming
- Recursive Instances: Recursion
- Type-level: Computation
// Haskell Q95: What are UndecidableInstances?
"UndecidableInstances allows complex instance resolution:
Extension:
{-# LANGUAGE UndecidableInstances #-}
Benefits:
1. Advanced type-level programming
2. Recursive instances
3. Type-level computation
Examples:
-- Recursive instance
instance (Num a, Show a) => Show [a] where
show xs = "List: " ++ show xs
-- Nested instances
instance (Show a, Show b) => Show (Either a b) where
show (Left x) = "Left: " ++ show x
show (Right x) = "Right: " ++ show x
-- Instance with type families
type family Elem a where
Elem [a] = a
instance (Show (Elem a)) => Show a where
show x = "Element: " ++ show (x :: Elem a)
-- Recursive constraints
class MyClass a where
method :: a -> String
instance (MyClass a, MyClass b) => MyClass (Either a b) where
method (Left x) = "Left: " ++ method x
method (Right x) = "Right: " ++ method x
-- Complex instance resolution
class Convert a b where
convert :: a -> b
instance (Convert a b, Convert b c) => Convert a c where
convert = convert . convert
-- Instance with functional dependencies
class Collection c a | c -> a
instance Collection [a] a
instance (Collection c a) => Collection (Maybe c) a
-- Instance with overlapping
instance {-# OVERLAPPABLE #-} Show a => Show [a]
instance {-# OVERLAPPING #-} Show [Int]
-- Instance with incoherent
instance {-# INCOHERENT #-} Num a => Show a
-- Using undecidable instances for type-level lists
instance (Show a, Show (List a)) => Show (List a) where
show (Cons x xs) = show x ++ ", " ++ show xs
show Nil = ""Both enable advanced type-level programming.
- FlexibleContexts: Complex contexts
- UndecidableInstances: Recursive instances
- Type-level: Computation
// Haskell Q96: What are FlexibleContexts and UndecidableInstances?
"Both enable advanced type-level programming:
FlexibleContexts:
- Allows complex context specifications
- Better expressiveness
- Type safety
UndecidableInstances:
- Allows recursive instances
- Type-level computation
- Complex instance resolution
Examples:
-- FlexibleContexts
class MyClass a where
method :: a -> String
instance (MyClass a, Show a) => MyClass [a] where
method xs = "List: " ++ show xs
-- UndecidableInstances
instance (MyClass a, MyClass b) => MyClass (a, b) where
method (x,y) = method x ++ ", " ++ method y
-- Combining both
class Convert a b where
convert :: a -> b
instance (Convert a b, Convert b c) => Convert a c where
convert = convert . convert
-- Type-level computation
type family Add a b where
Add Zero b = b
Add (Succ a) b = Succ (Add a b)
-- Instance with type family
instance (Num a, Num (Add a b)) => Num (Add a b) where
-- Implementation
-- Recursive constraints
class Collection c a | c -> a
instance (Collection c a) => Collection (Maybe c) a
-- Complex context
f :: (Show a, Eq a, Num a, Ord a) => a -> String
f x = show (x + 1)
-- Flexible context with type families
process :: (Elem a ~ Int, Show a) => a -> String
-- Undecidable instances for type-level lists
instance (Show a, Show (List a)) => Show (List a)
-- Instance with multiple constraints
instance (Show a, Show b, Show c) => Show (a, b, c)Combining these extensions enables advanced polymorphism.
- Higher-rank: Polymorphism
- Scoped Type Variables: Type variables in scope
- Precise Type: Control
Benefits:
1. Higher-rank polymorphism
2. Scoped type variables
3. Precise type control
Examples:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- Rank-2 with scoped variables
f :: forall a. (forall b. b -> b) -> a -> a
f g x = g x
-- Higher-rank in data types
data Box = Box (forall a. a -> a)
-- Scoped variables in rank-2
runST :: forall a. (forall s. ST s a) -> a
-- Rank-2 with constraints
apply :: forall a. (forall b. Show b => b -> String) -> a -> String
apply f x = f x
-- Scoped variables in higher-rank
g :: forall a. (forall b. (Num b) => b -> b) -> a -> a
g f x = f x
-- Rank-N in class methods
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- Scoped variables with constraints
h :: forall a b. (Show a, Show b) => (forall c. Show c => c -> String) -> a -> b -> String
h f x y = f x ++ " " ++ f y
-- Higher-rank in records
data API = API
{ getId :: forall a. a -> a
, getName :: forall a. Show a => a -> String
}
-- Scoped variables in pattern matching
process :: forall a. Show a => a -> String
process x = show (x :: a)
-- Rank-2 with type applications
run :: forall a. (forall s. ST s a) -> a
run st = runST st
-- Combining with other extensions
{-# LANGUAGE RankNTypes, ScopedTypeVariables, TypeApplications #-}
-- Using type applications with rank-N
foo :: forall a. (forall b. b -> a) -> a
foo f = f @Int 5"Best practices for effective Haskell development.
- Code Organization: Modules and structure
- Type Safety: Newtypes, ADTs
- Performance: Strictness, profiling
- Testing: QuickCheck, unit tests
// Haskell Q98: What are the best practices for Haskell development?
"Best practices for Haskell development:
1. Code Organization:
- Use modules effectively
- Export minimal API
- Separate concerns
- Use type synonyms for clarity
2. Type Safety:
- Use newtypes for safety
- Leverage algebraic data types
- Use phantom types
- Prefer total functions
3. Performance:
- Use strict annotations
- Profile code
- Use appropriate data structures
- Leverage laziness appropriately
4. Testing:
- Write property tests
- Use QuickCheck
- Write unit tests
- Benchmark code
5. Documentation:
- Write Haddock comments
- Document types
- Include examples
- Explain invariants
6. Build Tools:
- Use Stack or Cabal
- Manage dependencies
- Use GHC warnings
- Enable language extensions judiciously
7. Code Style:
- Follow Haskell style guide
- Use consistent naming
- Write readable code
- Comment complex code
8. Error Handling:
- Use appropriate monads
- Handle errors gracefully
- Use Either for errors
- Leverage exceptions sparingly
Examples:
-- Good practice
newtype UserId = UserId Int
deriving (Show, Eq, Ord)
-- Type safe functions
createUser :: UserId -> UserName -> UserEmail -> User
-- Documentation
-- | Creates a new user with given ID, name, and email
-- >>> createUser (UserId 1) (UserName "Alice") (UserEmail "alice@example.com")
-- Property tests
prop_inverse :: [Int] -> Bool
prop_inverse xs = reverse (reverse xs) == xs
-- Performance optimization
sum' :: [Int] -> Int
sum' = foldl' (+) 0"Common pitfalls to avoid in Haskell interviews.
- Laziness Issues: Space leaks
- Type Errors: Ambiguous types
- Monad Confusion: IO vs pure
- Performance: Inefficient data structures
1. Laziness Issues:
- Space leaks from thunks
- Unexpected memory usage
- Use strictness annotations
2. Type Errors:
- Ambiguous types
- Missing type signatures
- Use explicit type annotations
3. Monad Confusion:
- Using IO when pure is possible
- Monad transformer complexity
- Start with simple monads
4. Performance:
- Inefficient data structures
- Unnecessary allocations
- Use appropriate data types
5. Pattern Matching:
- Non-exhaustive patterns
- Partial functions
- Use total functions
6. Type Classes:
- Ambiguous instances
- Overlapping instances
- Use safe instance declarations
7. Error Handling:
- Ignoring errors
- Partial functions
- Use total error handling
8. Module Management:
- Orphan instances
- Circular dependencies
- Clean module structure
Examples:
-- Bad: Partial function
head' [] = error "Empty"
-- Good: Total function
headMaybe :: [a] -> Maybe a
headMaybe (x:_) = Just x
headMaybe [] = Nothing
-- Bad: Space leak
badSum = foldl (+) 0 [1..1000000]
-- Good: Strict fold
goodSum = foldl' (+) 0 [1..1000000]
-- Bad: Ambiguous type
read "5"
-- Good: Explicit type
read "5" :: Int
-- Bad: IO when pure
readFileAndProcess path = do
content <- readFile path
return (process content)
-- Good: Separate IO and pure
processContent = process content
where content = readFile pathAdvanced Haskell concepts enable sophisticated type-level programming and effect management.
- Type-Level Programming: DataKinds, Type Families, GADTs
- Effect Systems: Monad Transformers, Algebraic Effects
- Generic Programming: DeriveGeneric, Type-safe Serialization
- Dependent Types: Singletons, Type-level Proofs
1. Type-Level Programming:
- DataKinds for type-level values
- Type families for computation
- GADTs for precise types
2. Effect Systems:
- Monad transformers
- Effect libraries (freer-simple, effectful)
- Algebraic effects
3. Generic Programming:
- DeriveGeneric
- Generic representations
- Type-safe serialization
4. Dependent Types:
- Singletons
- Type-level proofs
- Smart constructors
5. Concurrency:
- Async and par
- Software transactional memory
- Distributed Haskell
6. Performance Optimization:
- Unboxed types
- Stream fusion
- Rewrite rules
7. Metaprogramming:
- Template Haskell
- QuasiQuotes
- Type providers
8. Advanced Patterns:
- Free monads
- Coeffects
- Type-level state machines
Examples:
-- Type-level vector
data Vector (n :: Nat) a where
VNil :: Vector Zero a
VCons :: a -> Vector n a -> Vector (Succ n) a
-- Type-level safe append
append :: Vector n a -> Vector m a -> Vector (Add n m) a
append VNil ys = ys
append (VCons x xs) ys = VCons x (append xs ys)
-- Generic programming
instance (Generic a, GToJSON (Rep a)) => ToJSON a where
toJSON = gToJSON
-- Effect system
type App = Eff
'[ Reader Config
, State AppState
, Log String
, IOE ]
-- Concurrent programming
parallelMap :: (a -> b) -> [a] -> [b]
parallelMap f = runPar $ do
results <- parMap f xs
return results
-- Template Haskell for optimizations
$(deriveFunctor ''MyType)
$(deriveFoldable ''MyType)
$(deriveTraversable ''MyType)