puts "Hello, World!" main :: IO ()
main = putStrLn "Hello, World!" Every Haskell program's entry point is main :: IO (). The IO () type signals that this function performs side effects. putStrLn prints a string followed by a newline β the direct counterpart of Ruby's puts.
puts "no quotes"
p "with quotes"
p 42 main :: IO ()
main = do
putStrLn "no quotes"
print "with quotes"
print 42 putStrLn outputs a String as-is, just like Ruby's puts. print is equivalent to Ruby's p β it calls show on the value (adding quotes around strings) and prints the result. Use putStrLn for user-facing output and print for debugging.
puts "First"
puts "Second"
puts "Third" main :: IO ()
main = do
putStrLn "First"
putStrLn "Second"
putStrLn "Third" The do block sequences multiple IO actions. Each line is executed in order, just like a Ruby method body. Haskell normally evaluates lazily, but IO actions in a do block are performed strictly from top to bottom.
greeting = "Hello"
count = 42
puts greeting
p count main :: IO ()
main = do
let greeting = "Hello"
let count = 42 :: Int
putStrLn greeting
print count Haskell's let inside a do block creates an immutable local binding β there is no way to reassign it. Ruby's local variables are mutable; Haskell's are not. The :: Int annotation tells the compiler which numeric type to use when it cannot infer one.
def greeting_message
prefix = "Hello"
target = "from helper"
"#{prefix} #{target}"
end
puts greeting_message main :: IO ()
main = putStrLn greetingMessage
where
greetingMessage = prefix ++ " " ++ target
prefix = "Hello"
target = "from where" The where clause attaches private definitions to a function β like giving a Ruby method private helper variables that are only visible in that scope. In Haskell where bindings can be mutually recursive and can themselves have their own where clauses. Ruby has no direct equivalent; the closest idiom is local variables or private helper methods.
# Ruby infers types at runtime (dynamic typing)
count = 42
ratio = 3.14
p count
p ratio main :: IO ()
main = do
let count = 42 :: Int
let ratio = 3.14 :: Double
print count
print ratio Haskell is statically typed and infers most types automatically, but you can annotate with :: Type to be explicit or to resolve ambiguity. Ruby never requires type annotations β types are checked at runtime, not compile time.
name = "World"
puts "Hello, #{name}!" main :: IO ()
main = do
let name = "World"
putStrLn ("Hello, " ++ name ++ "!") Haskell has no string interpolation. You concatenate strings with the ++ operator, or use show to convert non-string values. The Data.Text.printf and Text.Printf.printf modules provide C-style formatting as an alternative. For Rubyists the lack of interpolation is one of the first friction points in Haskell.
number = 42
ratio = 3.14
flag = true
letter = 'A'
p number
p ratio
p flag
p letter main :: IO ()
main = do
let number = 42 :: Int
let ratio = 3.14 :: Double
let flag = True :: Bool
let letter = 'A' :: Char
print number
print ratio
print flag
print letter Haskell's basic types β Int, Double, Bool, Char β are similar to Ruby's, but declared at compile time. A notable difference is that Haskell's Char uses single quotes exclusively, while String (which is [Char]) uses double quotes.
# Ruby resolves types at runtime
count = 42
message = "inferred as String"
puts "#{count} and #{message}" main :: IO ()
main = do
let count = 42 :: Int
let message = "inferred as String"
putStrLn (show count ++ " and " ++ message) Haskell's type inference (the HindleyβMilner algorithm) figures out the type of every expression at compile time without requiring annotations. The compiler catches type mismatches before the program ever runs β something Ruby only discovers at runtime.
count = 7
ratio = count.to_f / 2.0
p ratio
p ratio.floor
p ratio.ceil main :: IO ()
main = do
let count = 7 :: Int
let ratio = fromIntegral count / 2.0 :: Double
print ratio
print (floor ratio :: Int)
print (ceiling ratio :: Int) Haskell never implicitly converts between numeric types β you must use fromIntegral to promote an Int to a Double. This is more verbose than Ruby's .to_f but prevents silent precision loss. floor and ceiling need a type annotation on the result because they are polymorphic.
sunny = true
warm = false
p sunny && warm
p sunny || warm
p !sunny main :: IO ()
main = do
let sunny = True
let warm = False
print (sunny && warm)
print (sunny || warm)
print (not sunny) Haskell uses && and || for boolean operators, just like Ruby. The difference is that Haskell uses the function not instead of the ! prefix operator. Haskell's Bool is a proper algebraic data type with two constructors: True and False (capitalized).
first = "Hello"
second = " World"
puts first + second main :: IO ()
main = do
let first = "Hello"
let second = " World"
putStrLn (first ++ second) Haskell uses ++ for list concatenation, and since String is just [Char], ++ concatenates strings too. Ruby uses +. The ++ operator works on any list, making string and list operations consistent β though for performance-critical code the Data.Text type is preferred.
message = "Hello, World!"
p message.length main :: IO ()
main = do
let message = "Hello, World!"
print (length message) Because String is [Char], length is the standard list length function β it counts characters. Be aware that this counts Unicode code points, not grapheme clusters, which matters for international text. The Data.Text package handles text encoding more robustly for production use.
number_str = "42"
number = Integer(number_str)
p number + 8
puts number.to_s main :: IO ()
main = do
let numberStr = "42"
let number = read numberStr :: Int
print (number + 8)
putStrLn (show number) read parses a string into any type that implements the Read typeclass β it is the inverse of show. read throws a runtime exception on invalid input, so production code uses reads or Text.Read.readMaybe for safe parsing. Ruby's Integer() similarly raises on invalid strings.
sentence = "one two three"
p sentence.split
multiline = "line one
line two
line three"
p multiline.lines.map(&:chomp) main :: IO ()
main = do
let sentence = "one two three"
print (words sentence)
let multiline = "line one\nline two\nline three"
print (lines multiline) words splits on whitespace (like Ruby's split with no arguments) and lines splits on newlines (like Ruby's lines). Their inverses are unwords and unlines, which join with spaces and newlines respectively β the same role Ruby's join plays.
message = "Haskell"
puts message.reverse main :: IO ()
main = do
let message = "Haskell"
putStrLn (reverse message) Because String is [Char], the polymorphic list function reverse works on strings without any special string methods. This is a recurring Haskell pattern: functions that operate on lists automatically work on strings too.
numbers = [1, 2, 3, 4, 5]
p numbers
p numbers.first
p numbers[1..] main :: IO ()
main = do
let numbers = [1, 2, 3, 4, 5] :: [Int]
print numbers
print (head numbers)
print (tail numbers) Haskell lists use the same [...] syntax as Ruby, but they are singly-linked lists under the hood β not arrays. head and tail correspond roughly to Ruby's first and [1..] slice. Calling head or tail on an empty list raises a runtime exception, so idiomatic Haskell prefers pattern matching.
rest = [2, 3, 4]
numbers = [1] + rest
p numbers main :: IO ()
main = do
let rest = [2, 3, 4] :: [Int]
let numbers = 1 : rest
print numbers The : operator (pronounced "cons") prepends a single element to a list. Because Haskell lists are singly-linked, cons is O(1). Ruby's + allocates a new array; Haskell's : just adds a node. All list literals are syntactic sugar for cons chains: [1,2,3] is 1:(2:(3:[])).
p (1..10).to_a
p (1..10).step(2).to_a
p ('a'..'e').to_a main :: IO ()
main = do
print [1..10 :: Int]
print [1,3..10 :: Int]
print ['a'..'e'] Haskell's [start..end] syntax mirrors Ruby's ranges. The two-element form [start,step..end] specifies a step β equivalent to Ruby's step. Ranges work on any type that implements the Enum typeclass, including characters and integers. Haskell also supports infinite ranges like [1..] thanks to lazy evaluation.
squares = (1..5).map { |x| x * x }
p squares
evens = (1..10).select { |x| x.even? }
p evens main :: IO ()
main = do
let squares = [x * x | x <- [1..5 :: Int]]
print squares
let evens = [x | x <- [1..10 :: Int], even x]
print evens List comprehensions combine a generator (x <- list) with optional guards (conditions after the comma) to produce a new list. The syntax mirrors mathematical set notation. Ruby achieves the same thing with map and select chained together; Haskell's comprehension syntax combines both in a single expression.
numbers = (1..5).to_a
p numbers.map { |x| x * 2 }
p numbers.select { |x| x.even? } main :: IO ()
main = do
let numbers = [1..5] :: [Int]
print (map (* 2) numbers)
print (filter even numbers) map and filter are the Haskell counterparts of Ruby's map and select. In Haskell they are standalone functions rather than methods β you pass the list as the last argument. (* 2) is an operator section: a partially-applied multiplication, equivalent to Ruby's { |x| x * 2 }.
numbers = (1..5).to_a
total = numbers.inject(0) { |sum, x| sum + x }
product_val = numbers.inject(1) { |prod, x| prod * x }
p total
p product_val main :: IO ()
main = do
let numbers = [1..5] :: [Int]
let total = foldr (+) 0 numbers
let productValue = foldr (*) 1 numbers
print total
print productValue foldr (fold right) is the Haskell equivalent of Ruby's inject/reduce. It takes a combining function, an initial value, and a list. foldl' (strict fold left, from Data.List) is more efficient for large lists because it does not build up a chain of thunks. foldr is preferred for lazy or infinite structures.
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
p numbers.length
p numbers.sum
p numbers.max
p numbers.min
p numbers.reverse main :: IO ()
main = do
let numbers = [3, 1, 4, 1, 5, 9, 2, 6] :: [Int]
print (length numbers)
print (sum numbers)
print (maximum numbers)
print (minimum numbers)
print (reverse numbers) Haskell's standard list functions length, sum, maximum, minimum, and reverse map directly onto their Ruby counterparts. They are ordinary functions rather than methods, so they work on any list β including strings, since String is [Char].
naturals = (1..).lazy
p naturals.first(5)
squares = (1..).lazy.map { |n| n ** 2 }
p squares.first(5) main :: IO ()
main = do
print (take 5 [1..] :: [Int])
print (take 5 (map (^2) [1..]) :: [Int]) Haskell evaluates lists lazily by default β elements are only computed when needed. This allows infinite lists like [1..] to exist as first-class values; take 5 forces only the first five. Ruby requires explicit .lazy enumerators to achieve the same effect. Lazy evaluation is one of Haskell's most distinctive and powerful features.
point = [3, 4]
p point
p point[0]
p point[1] main :: IO ()
main = do
let point = (3, 4) :: (Int, Int)
print point
print (fst point)
print (snd point) Haskell tuples are fixed-size, heterogeneous containers whose size and types are part of the type signature β (Int, String) and (String, Int) are different types. Ruby uses arrays where Haskell would use tuples. fst and snd extract the first and second elements of a pair; for larger tuples you use pattern matching.
person = ["Alice", 30, true]
name, age, active = person
puts name
p age
p active main :: IO ()
main = do
let person = ("Alice", 30, True) :: (String, Int, Bool)
let (name, age, active) = person
putStrLn name
print age
print active Destructuring a tuple with let (a, b, c) = tuple works like Ruby's multiple assignment. Haskell's type system makes the structure explicit β (String, Int, Bool) is a distinct type from (Int, String, Bool). This lets the compiler catch mistakes that would only surface as runtime errors in Ruby.
names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 92]
pairs = names.zip(scores)
p pairs
p pairs.map { |name, _score| name } main :: IO ()
main = do
let names = ["Alice", "Bob", "Carol"]
let scores = [95, 87, 92] :: [Int]
let pairs = zip names scores
print pairs
print (map fst pairs) zip combines two lists into a list of pairs, stopping at the shorter list β just like Ruby's zip. In Haskell, map fst pairs extracts the first element from each pair (equivalent to map(&:first) in Ruby). The combination of zip and map fst/snd is idiomatic for working with paired data.
temperature = 22
weather = temperature > 20 ? "warm" : "cool"
puts weather main :: IO ()
main = do
let temperature = 22 :: Int
let weather = if temperature > 20 then "warm" else "cool"
putStrLn weather In Haskell if/then/else is an expression, not a statement β it always produces a value, and the else branch is mandatory. This is similar to Ruby's ternary operator ? : rather than Ruby's if/end statement. Both branches must have the same type.
def classify(number)
if number < 0 then "negative"
elsif number == 0 then "zero"
elsif number < 10 then "small"
else "large"
end
end
puts classify(-5)
puts classify(0)
puts classify(7)
puts classify(42) classify :: Int -> String
classify number
| number < 0 = "negative"
| number == 0 = "zero"
| number < 10 = "small"
| otherwise = "large"
main :: IO ()
main = do
putStrLn (classify (-5))
putStrLn (classify 0)
putStrLn (classify 7)
putStrLn (classify 42) Guards (|) are Haskell's idiomatic multi-branch conditional β cleaner than chained if/else. Each guard is a boolean expression; otherwise is just True and acts as the catch-all. Guards can appear in function definitions, case expressions, and list comprehensions. They map naturally onto Ruby's if/elsif/else chains.
day = 3
name = case day
when 1 then "Monday"
when 2 then "Tuesday"
when 3 then "Wednesday"
else "Other"
end
puts name main :: IO ()
main = do
let day = 3 :: Int
let name = case day of
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
_ -> "Other"
putStrLn name Haskell's case expression matches on values like Ruby's case/when, but _ is the wildcard rather than else. Crucially, Haskell's case is exhaustiveness-checked β the compiler warns (or errors) if you leave out a case. Ruby's case silently returns nil for unmatched values.
result = begin
base = 10
multiplier = 3
base * multiplier
end
p result main :: IO ()
main = do
let result = let base = 10 :: Int
multiplier = 3
in base * multiplier
print result The let...in form is the pure-expression version of let in a do block. It introduces local bindings that are visible only in the in body and evaluates to the body's value. Ruby's begin...end serves a similar purpose for grouping expressions, though without name-scoping.
def describe_list(list)
case list
in [] then "empty"
in [_] then "one element"
in [_, _] then "two elements"
else "many elements"
end
end
puts describe_list([])
puts describe_list([1])
puts describe_list([1, 2])
puts describe_list([1, 2, 3]) describeList :: [Int] -> String
describeList [] = "empty"
describeList [_] = "one element"
describeList [_, _] = "two elements"
describeList _ = "many elements"
main :: IO ()
main = do
putStrLn (describeList [])
putStrLn (describeList [1])
putStrLn (describeList [1, 2])
putStrLn (describeList [1, 2, 3]) Haskell functions can have multiple equations, each matching a different pattern. The runtime tries each equation top-to-bottom and uses the first that matches. This is more expressive than Ruby's traditional case/when (which only tests equality) because patterns can match on structure. Ruby 3+ case/in is the closest Ruby equivalent. The compiler warns if your patterns are non-exhaustive.
def first_two(list)
case list
in [] then "Empty"
in [x] then "Only: #{x}"
in [x, y, *] then "First: #{x}, Second: #{y}"
end
end
puts first_two([1, 2, 3, 4])
puts first_two([42])
puts first_two([]) firstTwo :: [Int] -> String
firstTwo (x:y:_) = "First: " ++ show x ++ ", Second: " ++ show y
firstTwo (x:_) = "Only: " ++ show x
firstTwo [] = "Empty"
main :: IO ()
main = do
putStrLn (firstTwo [1, 2, 3, 4])
putStrLn (firstTwo [42])
putStrLn (firstTwo []) The cons pattern (x:rest) binds the head element to x and the tail list to rest. Nesting it as (x:y:_) matches at least two elements. This mirrors Ruby 3's [x, y, *] pattern in case/in, but in Haskell it is the primary way to process lists recursively.
def add_pair(pair)
left, right = pair
left + right
end
p add_pair([3, 4])
pairs = [[1, 2], [3, 4], [5, 6]]
p pairs.map { |pair| add_pair(pair) } addPair :: (Int, Int) -> Int
addPair (left, right) = left + right
main :: IO ()
main = do
print (addPair (3, 4))
let pairs = [(1, 2), (3, 4), (5, 6)] :: [(Int, Int)]
print (map addPair pairs) Tuple patterns destructure the tuple directly in the function parameter list β no separate destructuring statement needed. This is similar to Ruby's multiple assignment left, right = pair, but Haskell's compiler verifies that the tuple has exactly the right number of elements at compile time.
def greet(name)
case name
when "Alice" then "Hello, Alice! Special greeting for you."
when "Bob" then "Hey Bob!"
else "Hi, #{name}!"
end
end
puts greet("Alice")
puts greet("Bob")
puts greet("Carol") greet :: String -> String
greet "Alice" = "Hello, Alice! Special greeting for you."
greet "Bob" = "Hey Bob!"
greet name = "Hi, " ++ name ++ "!"
main :: IO ()
main = do
putStrLn (greet "Alice")
putStrLn (greet "Bob")
putStrLn (greet "Carol") Haskell function equations can match on literal string, integer, or character values. The last equation with a variable name (name) acts as the catch-all, binding the unmatched argument. This is more readable than a sequence of if checks and directly maps to Ruby's case/when with string matches.
def first_and_all(list)
return "empty" if list.empty?
"First: #{list.first}, All: #{list.inspect}"
end
puts first_and_all([1, 2, 3])
puts first_and_all([42]) firstAndAll :: [Int] -> String
firstAndAll [] = "empty"
firstAndAll list@(first:_) =
"First: " ++ show first ++ ", All: " ++ show list
main :: IO ()
main = do
putStrLn (firstAndAll [1, 2, 3])
putStrLn (firstAndAll [42]) The @ pattern (pronounced "as") binds the whole matched value to a name while also destructuring it. list@(first:_) binds the whole list to list and the first element to first simultaneously. This avoids the need to reconstruct the original value after destructuring β a convenience with no direct Ruby equivalent.
def double(number) = number * 2
def square(number) = number * number
p double(7)
p square(5) double :: Int -> Int
double number = number * 2
square :: Int -> Int
square number = number * number
main :: IO ()
main = do
print (double 7)
print (square 5) Haskell function definitions use the form name argument = body with no parentheses around arguments and no return keyword β the body expression is the return value. Function application is written with a space: double 7, not double(7). Type signatures like double :: Int -> Int are optional but strongly recommended.
double = ->(number) { number * 2 }
add = ->(x, y) { x + y }
p double.call(5)
p add.call(3, 4) main :: IO ()
main = do
let double = (\number -> number * 2) :: Int -> Int
let add = (\x y -> x + y) :: Int -> Int -> Int
print (double 5)
print (add 3 4) Haskell lambdas use a backslash (resembling Ξ») followed by parameters and -> to the body. A multi-parameter lambda \x y -> ... is syntactic sugar for a nested single-parameter lambda \x -> (\y -> ...). Ruby's ->(x, y) {} packs multiple parameters into a single closure; Haskell's currying keeps them separate.
negate_fn = ->(x) { -x }
double_fn = ->(x) { x * 2 }
double_then_negate = negate_fn << double_fn
p double_then_negate.call(5)
reverse_words = ->(sentence) { sentence.split.reverse }
p reverse_words.call("hello world foo") main :: IO ()
main = do
let doubleAndNegate = negate . (* 2)
print (doubleAndNegate (5 :: Int))
let reverseWords = reverse . words
print (reverseWords "hello world foo") The . operator composes two functions: (f . g) x is f (g x). It reads right-to-left β negate . (* 2) first doubles, then negates. Ruby 2.6+ has << and >> for proc composition, but Haskell's . is deeply idiomatic and used throughout real code.
# Ruby uses parentheses; no $ operator needed
p (1..5).map { |x| x * 2 }.sum
puts "Result: #{42}" main :: IO ()
main = do
-- Without $: print (sum (map (* 2) [1..5]))
print $ sum $ map (* 2) [1..5 :: Int]
putStrLn $ "Result: " ++ show (42 :: Int) The $ operator is just function application with the lowest possible precedence. f $ g $ x is f (g x) β it eliminates nested parentheses by associating to the right. Ruby's method chaining achieves the same readability goal but reads left-to-right, while $ chains read right-to-left.
add_five = ->(number) { number + 5 }
double = ->(number) { number * 2 }
p [1, 2, 3].map { |n| add_five.call(n) }
p [1, 2, 3].map { |n| double.call(n) } main :: IO ()
main = do
let addFive = (+ 5) :: Int -> Int
let double = (* 2) :: Int -> Int
print (map addFive [1, 2, 3])
print (map double [1, 2, 3]) Every Haskell function is automatically curried β applying it to fewer arguments than it expects produces a new function. Operator sections like (+ 5) and (* 2) partially apply the operator, creating a new one-argument function. Ruby achieves the same effect with lambdas or method(:name).curry, but in Haskell it is effortless and pervasive.
def apply_twice(operation, value)
operation.call(operation.call(value))
end
p apply_twice(->(x) { x * 2 }, 3)
p apply_twice(->(x) { x + 10 }, 5) applyTwice :: (Int -> Int) -> Int -> Int
applyTwice operation value = operation (operation value)
main :: IO ()
main = do
print (applyTwice (* 2) 3)
print (applyTwice (+ 10) 5) Functions that take other functions as arguments are first-class in Haskell. The type signature (Int -> Int) -> Int -> Int explicitly shows that the first argument is a function. Ruby requires lambdas or procs to pass callables; in Haskell any function β including operator sections β can be passed directly.
prices = [10, 20, 30]
quantities = [3, 2, 4]
totals = prices.zip(quantities).map { |price, quantity| price * quantity }
p totals
p totals.sum main :: IO ()
main = do
let prices = [10, 20, 30] :: [Int]
let quantities = [3, 2, 4] :: [Int]
let totals = zipWith (*) prices quantities
print totals
print (sum totals) zipWith combines two lists element-by-element using a function β it is zip followed by map in one step. Ruby needs zip(...).map { } to achieve the same result. zipWith (+) adds lists element-wise, zipWith max gives element-wise maxima, and so on.
numbers = [1, 3, 5, 8, 9]
p numbers.any?(&:even?)
p numbers.all?(&:odd?)
p numbers.any? { |n| n > 7 }
p numbers.all? { |n| n > 0 } main :: IO ()
main = do
let numbers = [1, 3, 5, 8, 9] :: [Int]
print (any even numbers)
print (all odd numbers)
print (any (> 7) numbers)
print (all (> 0) numbers) any and all are Haskell's counterparts of Ruby's any? and all?. They take a predicate function rather than a block. Haskell's lazy evaluation makes them short-circuit: any stops at the first True, all stops at the first False β just like Ruby.
halves = [10, 20, 30, 40].map { |number| number / 2 }
p halves
subtract_from_ten = ->(number) { 10 - number }
p subtract_from_ten.call(3) main :: IO ()
main = do
let halves = map (flip div 2) [10, 20, 30, 40] :: [Int]
print halves
let subtractFromTen = flip (-) (3 :: Int)
print (subtractFromTen 10) flip f x y calls f y x β it reverses the order of the first two arguments. This is useful when partial application needs the "wrong" argument fixed first. For example, flip div 2 creates a function that divides its argument by 2, even though div's first parameter is the dividend. Ruby handles this with lambdas or curry.
sentences = ["hello world", "foo bar baz"]
all_words = sentences.flat_map { |sentence| sentence.split }
p all_words
numbers = (1..4).to_a
expanded = numbers.flat_map { |n| [n, n * n] }
p expanded main :: IO ()
main = do
let sentences = ["hello world", "foo bar baz"]
let allWords = concatMap words sentences
print allWords
let numbers = [1..4] :: [Int]
let expanded = concatMap (\n -> [n, n * n]) numbers
print expanded concatMap f list maps f over the list and then concatenates the results β it is the Haskell name for what Ruby calls flat_map. It is also the implementation of the list monad's bind operation: concatMap is (>>=) for lists, making it a fundamental building block for list comprehensions.
powers = []
current = 1
while current <= 100
powers << current
current *= 2
end
p powers
countdown = 10.downto(6).to_a
p countdown main :: IO ()
main = do
let powers = takeWhile (<= 100) (iterate (* 2) (1 :: Int))
print powers
let countdown = take 5 (iterate (subtract 1) (10 :: Int))
print countdown iterate f x produces the infinite list [x, f x, f (f x), ...]. Combined with takeWhile or take, it replaces imperative loops that accumulate values. This is idiomatic Haskell: express the series you want, then take what you need β no mutable loop variable required.
def safe_head(list)
return nil if list.empty?
list.first
end
p safe_head([1, 2, 3])
p safe_head([]) safeHead :: [Int] -> Maybe Int
safeHead [] = Nothing
safeHead (x:_) = Just x
main :: IO ()
main = do
print (safeHead [1, 2, 3])
print (safeHead []) Maybe a is Haskell's type-safe alternative to nil. A value of Maybe Int is either Just n (a present integer) or Nothing (absent). Unlike Ruby's nil, the type system forces you to handle both cases β you cannot call + 1 on a Maybe Int without unwrapping it first.
def safe_head(list)
list.first # returns nil for empty arrays
end
result1 = safe_head([42, 1, 2]) || 0
result2 = safe_head([]) || 0
p result1
p result2 import Data.Maybe (fromMaybe)
safeHead :: [Int] -> Maybe Int
safeHead [] = Nothing
safeHead (x:_) = Just x
main :: IO ()
main = do
let result1 = fromMaybe 0 (safeHead [42, 1, 2])
let result2 = fromMaybe 0 (safeHead [])
print result1
print result2 fromMaybe defaultValue maybeValue unwraps Just x to x, or returns the default if Nothing. It is the Haskell equivalent of Ruby's || default idiom. The key difference is that the compiler tracks whether a value is Maybe β you cannot accidentally forget to handle the missing case.
def describe_result(value)
case value
when nil then "No result"
else "Got: #{value}"
end
end
puts describe_result(42)
puts describe_result(nil) describeResult :: Maybe Int -> String
describeResult Nothing = "No result"
describeResult (Just n) = "Got: " ++ show n
main :: IO ()
main = do
putStrLn (describeResult (Just 42))
putStrLn (describeResult Nothing) Pattern matching on Maybe is the idiomatic way to handle both cases. The compiler enforces exhaustiveness β if you forget to handle Nothing, you get a warning. Ruby's case/when nil is conventional but nothing prevents forgetting the nil branch.
def safe_divide(numerator, denominator)
return [:error, "Division by zero"] if denominator == 0
[:ok, numerator / denominator]
end
p safe_divide(10, 2)
p safe_divide(10, 0) safeDivide :: Int -> Int -> Either String Int
safeDivide _ 0 = Left "Division by zero"
safeDivide numerator denominator = Right (numerator `div` denominator)
main :: IO ()
main = do
print (safeDivide 10 2)
print (safeDivide 10 0) Either String Int holds either a Left String (an error message) or a Right Int (a success value). It is Haskell's typed alternative to exceptions β the error path is visible in the type signature. Ruby conventionally uses tagged arrays [:ok, value] or raises exceptions; Haskell makes the two outcomes part of the type.
maybe_number = 5
doubled = maybe_number&.*(2)
p doubled
nothing = nil
p nothing&.*(2) main :: IO ()
main = do
let maybeNumber = Just (5 :: Int)
let doubled = fmap (* 2) maybeNumber
print doubled
let nothing = Nothing :: Maybe Int
print (fmap (* 2) nothing) fmap f (Just x) returns Just (f x); fmap f Nothing returns Nothing. This is the same as Ruby's safe navigation operator &. β the function is applied only when the value is present. The power is that fmap is defined by the Functor typeclass and works on lists, Either, IO, and any other container.
def describe(color)
case color
when :red then "warm red"
when :green then "cool green"
when :blue then "calm blue"
end
end
p :red
puts describe(:green) data Color = Red | Green | Blue deriving (Show)
describe :: Color -> String
describe Red = "warm red"
describe Green = "cool green"
describe Blue = "calm blue"
main :: IO ()
main = do
print Red
putStrLn (describe Green) Haskell's data keyword defines an algebraic data type. A sum type like Color enumerates all possible values β similar to Ruby symbols, but typed. deriving (Show) automatically generates a string representation. The compiler exhaustiveness-checks case and pattern matches against the type's constructors.
Circle = Data.define(:radius)
Rectangle = Data.define(:width, :height)
def area(shape)
case shape
in Circle[radius:] then Math::PI * radius ** 2
in Rectangle[width:, height:] then width * height
end
end
p area(Circle.new(radius: 5.0))
p area(Rectangle.new(width: 4.0, height: 6.0)) data Shape = Circle Double | Rectangle Double Double
deriving (Show)
area :: Shape -> Double
area (Circle radius) = pi * radius * radius
area (Rectangle width height) = width * height
main :: IO ()
main = do
let circle = Circle 5.0
let rectangle = Rectangle 4.0 6.0
print (area circle)
print (area rectangle) Constructors can carry data β Circle Double wraps a radius, Rectangle Double Double wraps width and height. Pattern matching in function equations unpacks the values. This is Haskell's equivalent of Ruby's Data.define value objects, but the type is a single coherent union rather than separate classes.
Person = Data.define(:name, :age)
person = Person.new(name: "Alice", age: 30)
puts person.name
p person.age
p person data Person = Person
{ personName :: String
, personAge :: Int
} deriving (Show)
main :: IO ()
main = do
let person = Person { personName = "Alice", personAge = 30 }
putStrLn (personName person)
print (personAge person)
print person Record syntax adds named field accessors to a data type. personName person works like Ruby's person.name. Haskell also generates a record update syntax: person { personAge = 31 } creates a new Person with only the age changed β immutable update that mirrors Ruby's Data#with.
Name = Data.define(:value)
Age = Data.define(:value)
def greet(name, age)
"Hello, #{name.value}! You are #{age.value} years old."
end
puts greet(Name.new(value: "Alice"), Age.new(value: 30)) newtype Name = Name String deriving (Show)
newtype Age = Age Int deriving (Show)
greet :: Name -> Age -> String
greet (Name name) (Age age) =
"Hello, " ++ name ++ "! You are " ++ show age ++ " years old."
main :: IO ()
main = do
putStrLn (greet (Name "Alice") (Age 30)) newtype creates a distinct type from an existing one with zero runtime overhead β the wrapper is erased after type-checking. It prevents accidentally passing a raw String where a Name is expected. Ruby achieves a similar effect with thin wrapper classes or Data.define, but the guarantee is conventional rather than compiler-enforced.
number = 42
as_string = number.to_s
puts as_string
back_to_number = as_string.to_i
p back_to_number + 1 main :: IO ()
main = do
let number = 42 :: Int
let asString = show number
putStrLn asString
let backToNumber = read asString :: Int
print (backToNumber + 1) Show and Read are typeclasses that define how a type converts to and from a string representation. show is like Ruby's to_s and inspect; read is like Ruby's Integer() but generic over any readable type. deriving (Show, Read) generates these instances automatically for data types.
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
p numbers.min
p numbers.max
p 3 == 3
p 3 <=> 5 main :: IO ()
main = do
let numbers = [3, 1, 4, 1, 5, 9, 2, 6] :: [Int]
print (minimum numbers)
print (maximum numbers)
print (3 == (3 :: Int))
print (compare (3 :: Int) 5) The Eq typeclass provides == and /= (not-equal); Ord provides <, >, compare, minimum, and maximum. compare returns an Ordering value: LT, EQ, or GT β the equivalent of Ruby's <=> spaceship operator but typed.
module Describable
def describe = raise NotImplementedError
end
class Season
include Describable
DESCRIPTIONS = {
spring: "flowers bloom", summer: "sun shines",
autumn: "leaves fall", winter: "snow falls"
}
def initialize(name) = @name = name
def describe = DESCRIPTIONS[@name]
end
puts Season.new(:spring).describe
puts Season.new(:winter).describe class Describable a where
describe :: a -> String
data Season = Spring | Summer | Autumn | Winter
instance Describable Season where
describe Spring = "flowers bloom"
describe Summer = "sun shines"
describe Autumn = "leaves fall"
describe Winter = "snow falls"
main :: IO ()
main = do
putStrLn (describe Spring)
putStrLn (describe Winter) Typeclasses define an interface that types can implement β like Ruby's modules used as mixins, but enforced by the type system. class Describable a where declares the interface; instance Describable Season where implements it. The compiler verifies every instance provides all required methods and rejects uses that mix incompatible types.
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map { |n| n * 2 }
p doubled
maybe_value = 10
tripled = maybe_value.then { |n| n * 3 }
p tripled main :: IO ()
main = do
let numbers = [1, 2, 3, 4, 5] :: [Int]
let doubled = fmap (* 2) numbers
print doubled
let maybeValue = Just (10 :: Int)
let tripled = fmap (* 3) maybeValue
print tripled The Functor typeclass defines fmap :: (a -> b) -> f a -> f b β the ability to apply a function inside a container without unwrapping it. Lists, Maybe, Either, and IO are all functors. Ruby's map on arrays and then/yield_self on objects serve similar roles, but they are not unified under a single abstraction.
greeting = "Hello"
target = "Ruby"
message = "#{greeting}, #{target}!"
puts message
print "Count: "
p message.length main :: IO ()
main = do
let greeting = "Hello"
let target = "Haskell"
let message = greeting ++ ", " ++ target ++ "!"
putStrLn message
putStr "Count: "
print (length message) The do notation sequences IO actions, making Haskell's otherwise pure, lazy code execute steps in order. putStr prints without a newline (like Ruby's print); putStrLn adds one (like Ruby's puts). Under the hood, do is syntactic sugar for >>= (bind) chains.
def get_doubled(number)
number * 2
end
doubled = get_doubled(21)
puts "Doubled: #{doubled}"
tripled = 21 * 3
puts "Tripled: #{tripled}" getDoubled :: Int -> IO Int
getDoubled number = return (number * 2)
main :: IO ()
main = do
doubled <- getDoubled 21
putStrLn ("Doubled: " ++ show doubled)
tripled <- return (21 * 3 :: Int)
putStrLn ("Tripled: " ++ show tripled) The <- arrow inside a do block "extracts" a value from an IO action and binds it to a name. doubled <- getDoubled 21 runs the IO action and gives the resulting Int the name doubled. Ruby's assignment result = method_call is the equivalent; the difference is that <- only works inside monadic context.
def compute_stats(numbers)
total = numbers.sum
count = numbers.length
average = total / count
[total, count, average]
end
numbers = [10, 20, 30, 40, 50]
total, count, average = compute_stats(numbers)
puts "Total: #{total}"
puts "Count: #{count}"
puts "Average: #{average}" computeStats :: [Int] -> (Int, Int, Int)
computeStats numbers = (total, count, average)
where
total = sum numbers
count = length numbers
average = total `div` count
main :: IO ()
main = do
let numbers = [10, 20, 30, 40, 50] :: [Int]
let (total, count, average) = computeStats numbers
putStrLn ("Total: " ++ show total)
putStrLn ("Count: " ++ show count)
putStrLn ("Average: " ++ show average) The backtick syntax x `div` y turns any two-argument function into an infix operator β total `div` count is the same as div total count. This mirrors the readability goal of Ruby's method chaining. Returning a tuple of computed values is Haskell's lightweight alternative to returning a struct or hash.
def always_forty_two
42
end
number = always_forty_two
p number
puts "The answer is #{number}" alwaysFortyTwo :: IO Int
alwaysFortyTwo = return 42
main :: IO ()
main = do
number <- alwaysFortyTwo
print number
putStrLn ("The answer is " ++ show number) return in Haskell wraps a pure value in an IO context β it does not exit the function early as in Ruby. return 42 creates an IO Int action that, when executed, produces 42 without any side effects. This is often surprising for Ruby developers who expect return to control flow.
fruits = ["apple", "banana", "cherry"]
fruits.each { |fruit| puts fruit } main :: IO ()
main = do
let fruits = ["apple", "banana", "cherry"]
mapM_ putStrLn fruits mapM_ applies an IO-returning function to each element of a list and sequences the actions in order, discarding the results. The trailing _ signals that return values are ignored β the same convention as Ruby's each versus map. mapM (without the underscore) collects results into a list of IO actions.
# Ruby's Array has sort, uniq, join built-in
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5]
p numbers.sort
p numbers.uniq
items = ["one", "two", "three"]
puts items.join(", ") import Data.List (sort, nub, intercalate)
main :: IO ()
main = do
let numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5] :: [Int]
print (sort numbers)
print (nub numbers)
let items = ["one", "two", "three"]
putStrLn (intercalate ", " items) Data.List provides sort (like Ruby's sort), nub (remove duplicates, like Ruby's uniq), and intercalate (join with a separator, like Ruby's join). Imports are explicit in Haskell β listing names in parentheses like (sort, nub) prevents name collisions and makes dependencies obvious.
scores = { "Alice" => 95, "Bob" => 87, "Carol" => 92 }
p scores
p scores["Alice"]
p scores.size import qualified Data.Map.Strict as Map
main :: IO ()
main = do
let scores = Map.fromList
[("Alice", 95), ("Bob", 87), ("Carol", 92)] :: Map.Map String Int
print scores
print (Map.lookup "Alice" scores)
print (Map.size scores) import qualified ... as Map imports the module but requires the Map. prefix on every function β preventing clashes with Prelude functions like lookup and null. Data.Map.Strict is a balanced binary search tree (O(log n) operations), equivalent to Ruby's Hash for key-value storage. Map.lookup returns Maybe, not nil.
message = "Hello, World! 123"
puts message.upcase
puts message.chars.select { |char| char.match?(/[a-zA-Z]/) }.join
puts message.chars.select { |char| char.match?(/d/) }.join import Data.Char (toUpper, isAlpha, isDigit)
main :: IO ()
main = do
let message = "Hello, World! 123"
putStrLn (map toUpper message)
putStrLn (filter isAlpha message)
putStrLn (filter isDigit message) Data.Char provides character predicates and transformers. Because String is [Char], applying map toUpper to a string works exactly like filter isAlpha on a list β list functions and string functions are the same functions. Ruby needs regex or explicit character class checks; Haskell uses composable predicates.