Output & Basics
Hello, World
main :: IO ()
main = putStrLn "Hello, World!" object Main {
def main(args: Array[String]): Unit = {
println("Hello, World!")
}
} Haskell requires a named
main :: IO () entry point that the runtime calls once. Every runnable Scala program needs a main method on an object — a similar requirement, but Scala's comes from the JVM's own entry-point convention rather than from any monadic structure. println needs no import, since it comes from the automatically-included Predef.Sequencing multiple outputs
main :: IO ()
main = do
putStrLn "First"
putStrLn "Second"
putStrLn "Third" object Main {
def main(args: Array[String]): Unit = {
println("First")
println("Second")
println("Third")
}
} Haskell needs
do-notation to sequence IO actions, since it desugars to chained monadic binds. Scala's main body is an ordinary imperative block — statements execute top to bottom because Scala is strict, with no monad required for plain sequencing.Printing a value for inspection
main :: IO ()
main = print (Just [1, 2, 3]) object Main {
def main(args: Array[String]): Unit = {
val value = Some(List(1, 2, 3))
println(value)
}
} Haskell's
print uses the Show typeclass, deriving a textual representation for (almost) any value. Scala's println relies on every object's inherited toString, which case classes and standard collections already implement sensibly — the output shown, Some(List(1, 2, 3)), needs no extra formatting call, much like Haskell's derived Show.Values & Bindings
val is Haskell-style immutable binding
main :: IO ()
main =
let radius = 5.0
area = pi * radius * radius
in print area object Main {
def main(args: Array[String]): Unit = {
val radius = 5.0
val area = math.Pi * radius * radius
println(area)
}
} Haskell's
let ... in bindings and Scala's val both bind an immutable name once — val cannot be reassigned, matching Haskell's single-assignment discipline exactly, unlike Scala's separate var (next row).Opting in to mutation with var
import Data.IORef
main :: IO ()
main = do
ref <- newIORef 0
modifyIORef ref (+1)
value <- readIORef ref
print value object Main {
def main(args: Array[String]): Unit = {
var count = 0
count += 1
println(count)
}
} Haskell reaches for
IORef and threads it explicitly through IO with newIORef/modifyIORef/readIORef. Scala's var keyword offers a plain, in-place mutable local variable directly — no wrapper type, no monad required — though idiomatic Scala reaches for var far less often than idiomatic Java or C# would, preferring val almost everywhere.Types & Inference
Local inference feels familiar; signatures do not
double :: Int -> Int
double x = x * 2
main :: IO ()
main = print (double 21) object Main {
def double(x: Int): Int = x * 2
def main(args: Array[String]): Unit = {
println(double(21))
}
} Scala infers the type of local values the way Haskell does, so everyday code inside a method body reads almost as unannotated as Haskell. The difference surfaces at method boundaries: Scala parameter types and (with rare exceptions) return types are always explicit, the one place Haskell's inference goes further than Scala's.
Generic functions
firstOf :: [a] -> Maybe a
firstOf [] = Nothing
firstOf (x:_) = Just x
main :: IO ()
main = print (firstOf [1, 2, 3]) object Main {
def firstOf[A](list: List[A]): Option[A] = list match {
case Nil => None
case head :: _ => Some(head)
}
def main(args: Array[String]): Unit = {
println(firstOf(List(1, 2, 3)))
}
} Haskell's lowercase type variable
a and Scala's bracketed [A] both introduce a generic type parameter, but Scala requires it to be written explicitly on the method — Haskell infers that firstOf is generic purely from never constraining a, where Scala requires the [A] annotation even though the body itself needs no further help.Strings
String concatenation
main :: IO ()
main = putStrLn ("Hello, " ++ "World!") object Main {
def main(args: Array[String]): Unit = {
println("Hello, " + "World!")
}
} Haskell's
++ is the general list-concatenation operator, since a String is really [Char]. Scala's + is a genuine JVM java.lang.String concatenation, since Scala strings are ordinary Java strings, not character lists.String interpolation
import Text.Printf (printf)
main :: IO ()
main = do
let name = "Ada"
age = 36 :: Int
printf "%s is %d\n" name age object Main {
def main(args: Array[String]): Unit = {
val name = "Ada"
val age = 36
println(s"${name} is ${age}")
}
} Haskell has no native interpolation — the idiomatic route is
Text.Printf with positional format specifiers. Scala's s"..." string interpolator embeds expressions directly with \${ }, closer to Python f-strings or Ruby's #{}, and needs no import.A string is a list of characters — or a real JVM class
main :: IO ()
main = print (reverse "hello") object Main {
def main(args: Array[String]): Unit = {
println("hello".reverse)
}
} Haskell's
String is literally a type alias for [Char], so ordinary list functions like reverse apply directly. Scala's String is a genuine java.lang.String, but Scala's implicit conversions make it BEHAVE like a Seq[Char] for many operations (.reverse, .map, .filter all work directly on a String) — a middle ground between Haskell's literal list and a fully opaque string type.Collections
Lists and the cons pattern
main :: IO ()
main =
let (first:rest) = [1, 2, 3]
in print (first, rest) object Main {
def main(args: Array[String]): Unit = {
val first :: rest = List(1, 2, 3)
println((first, rest))
}
} Both languages model lists as singly-linked cons cells and use nearly identical syntax to destructure one: Haskell's
(first:rest) versus Scala's first :: rest — the same :: cons operator Scala's List is built from, right down to the symbol.Map and filter
main :: IO ()
main =
let doubled = map (* 2) [1, 2, 3, 4, 5]
evens = filter even doubled
in print evens object Main {
def main(args: Array[String]): Unit = {
val doubled = List(1, 2, 3, 4, 5).map(_ * 2)
val evens = doubled.filter(_ % 2 == 0)
println(evens)
}
} Both languages call these operations
map and filter. Scala calls them as methods on the collection (list.map(f)), where Haskell calls them as free functions taking the list last (map f list) — the same operation, opposite argument order convention.Folding a list
main :: IO ()
main = print (foldl (+) 0 [1, 2, 3, 4, 5]) object Main {
def main(args: Array[String]): Unit = {
val total = List(1, 2, 3, 4, 5).foldLeft(0)(_ + _)
println(total)
}
} Haskell's
foldl and Scala's foldLeft take the same pieces — initial accumulator, then a combining function — but Scala calls it as a method with two parameter lists: list.foldLeft(initial)(function), splitting what Haskell passes as three ordinary arguments.Key/value maps
import qualified Data.Map as Map
main :: IO ()
main =
let ages = Map.fromList [("Ada", 36), ("Alan", 41)]
in print (Map.lookup "Ada" ages) object Main {
def main(args: Array[String]): Unit = {
val ages = Map("Ada" -> 36, "Alan" -> 41)
println(ages.get("Ada"))
}
} Both languages provide an immutable map as a first-class collection: Haskell's
Data.Map (usually imported qualified) and Scala's built-in Map, needing no import at all. Map.lookup and Scala's .get both return an optional value rather than throwing on a missing key.Maybe/Either vs. Option/Either
Maybe becomes Option, Just/Nothing become Some/None
import qualified Data.Map as Map
main :: IO ()
main =
let ages = Map.fromList [("Ada", 36)]
in print (Map.lookup "Alan" ages) object Main {
def main(args: Array[String]): Unit = {
val ages = Map("Ada" -> 36)
println(ages.get("Alan"))
}
} Haskell's
Maybe a (Just x/Nothing) and Scala's Option[A] (Some(x)/None) are the same idea with renamed constructors — both replace null with an explicit, compiler-enforced "value or absence" type, and both are idiomatic defaults in their respective languages.Either matches directly — same name, same shape
divide :: Int -> Int -> Either String Int
divide _ 0 = Left "divide by zero"
divide x y = Right (x `div` y)
main :: IO ()
main = print (divide 10 0) object Main {
def divide(x: Int, y: Int): Either[String, Int] =
if (y == 0) Left("divide by zero") else Right(x / y)
def main(args: Array[String]): Unit = {
println(divide(10, 0))
}
} This is one of the closest matches on the entire site: Scala's
Either[String, Int] is spelled identically to Haskell's Either String Int, right down to reusing the names Left and Right for the same two cases with the same convention (Left for failure, Right for success).Skipping the failure case is a compile error in both
divide :: Int -> Int -> Either String Int
divide _ 0 = Left "divide by zero"
divide x y = Right (x `div` y)
main :: IO ()
main = case divide 10 0 of
Right value -> print value
Left reason -> putStrLn ("Error: " ++ reason) object Main {
def divide(x: Int, y: Int): Either[String, Int] =
if (y == 0) Left("divide by zero") else Right(x / y)
def main(args: Array[String]): Unit = {
divide(10, 0) match {
case Right(value) => println(value)
case Left(reason) => println(s"Error: ${reason}")
}
}
} Neither compiler lets you treat an
Either as if it were the bare success value. Matching both branches — or explicitly calling a combinator that forces a decision about the failure case, like Scala's .getOrElse or Haskell's either function — is the only way out in either language.Records vs. Case Classes
Records versus a genuine case class
data Point = Point { x :: Int, y :: Int } deriving (Show)
main :: IO ()
main =
let point = Point { x = 3, y = 4 }
in print (x point, y point) case class Point(x: Int, y: Int)
object Main {
def main(args: Array[String]): Unit = {
val point = Point(3, 4)
println((point.x, point.y))
}
} Both define a named product type with labeled fields and dot-accessible members. Haskell generates a top-level accessor function per field (
x :: Point -> Int); Scala's case class generates genuine object members plus free structural equality, a sensible toString, and pattern-matching support, all from one declaration.Record update syntax
data Point = Point { x :: Int, y :: Int } deriving (Show)
main :: IO ()
main =
let point = Point { x = 3, y = 4 }
moved = point { y = 99 }
in print moved case class Point(x: Int, y: Int)
object Main {
def main(args: Array[String]): Unit = {
val point = Point(3, 4)
val moved = point.copy(y = 99)
println(moved)
}
} Haskell's
point { y = 99 } and Scala's point.copy(y = 99) both produce a new value with one field replaced and every other field copied automatically — Scala's .copy method is generated for free on every case class, playing exactly the role Haskell's built-in record-update braces do.Algebraic Data Types vs. Sealed Traits
Modeling "one of several shapes"
data Shape = Circle Double | Rectangle Double Double
area :: Shape -> Double
area (Circle radius) = pi * radius * radius
area (Rectangle width height) = width * height
main :: IO ()
main = print (map area [Circle 5.0, Rectangle 3.0 4.0]) sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
object Main {
def area(shape: Shape): Double = shape match {
case Circle(radius) => math.Pi * radius * radius
case Rectangle(width, height) => width * height
}
def main(args: Array[String]): Unit = {
val shapes = List(Circle(5), Rectangle(3, 4))
println(shapes.map(area))
}
} Haskell's algebraic data types declare every variant in one
data statement; Scala expresses the same closed sum type as a sealed trait with case-class subtypes spread across (typically adjacent) declarations. sealed is the keyword doing the real work — it tells the compiler every subtype lives in this file, which is what lets a match be checked for exhaustiveness at all.Payload-free constructors become case objects
data Direction = North | South | East | West deriving (Show)
main :: IO ()
main = print North sealed trait Direction
case object North extends Direction
case object South extends Direction
case object East extends Direction
case object West extends Direction
object Main {
def main(args: Array[String]): Unit = {
val direction: Direction = North
println(direction)
}
} A payload-free Haskell constructor like
North becomes a Scala case object — a singleton instance rather than a class, since there is only ever one North. This is more verbose than Haskell's single-line enumeration, one of the real costs of Scala expressing algebraic data types through its class hierarchy rather than a dedicated sum-type syntax.Both compilers warn on non-exhaustive matches
-- GHC warns here (with -Wincomplete-patterns) since the
-- negative case is missing entirely:
classify :: Int -> String
classify 0 = "zero"
classify n | n > 0 = "positive"
main :: IO ()
main = putStrLn (classify 5) object Main {
def classify(n: Int): String = n match {
case 0 => "zero"
case n if n > 0 => "positive"
// Scala's compiler emits a WARNING here too — "match may not be
// exhaustive" — if the negative case were missing, PROVIDED the
// matched type is a sealed trait or similarly closed set:
case _ => "negative"
}
def main(args: Array[String]): Unit = {
println(classify(5))
}
} Both compilers statically check whether a match covers every case, but Scala's exhaustiveness checking only fires reliably against a
sealed hierarchy or similarly closed type — matching over a plain, unsealed Int like this one relies on Scala recognizing the guard pattern, which is less bulletproof than Haskell's check, which works uniformly across every data type.Pattern Matching
case versus match
describe :: Int -> String
describe n = case n of
0 -> "zero"
n | n > 0 -> "positive"
_ -> "negative"
main :: IO ()
main = putStrLn (describe (-5)) object Main {
def describe(n: Int): String = n match {
case 0 => "zero"
case n if n > 0 => "positive"
case _ => "negative"
}
def main(args: Array[String]): Unit = {
println(describe(-5))
}
} Haskell's
case ... of and Scala's match { } read as dialects of the same construct: patterns tried top to bottom, a guard attachable to a pattern (both reuse the word if/| respectively — Scala's if, Haskell's |), and a wildcard _ catch-all.Destructuring a tuple
main :: IO ()
main =
let point = (3, 4)
(x, y) = point
in print (x + y) object Main {
def main(args: Array[String]): Unit = {
val point = (3, 4)
val (x, y) = point
println(x + y)
}
} Tuple destructuring is essentially identical syntax in both languages — a parenthesized, comma-separated pattern on the left of a binding, matched positionally against the tuple's shape.
do-notation vs. For-Comprehensions
do-notation for IO
main :: IO ()
main = do
putStrLn "What is your name?"
let name = "Ada" -- stand-in for getLine, to keep this example self-contained
putStrLn ("Hello, " ++ name ++ "!") object Main {
def main(args: Array[String]): Unit = {
println("What is your name?")
val name = "Ada" // stand-in for scala.io.StdIn.readLine(), to keep this self-contained
println(s"Hello, ${name}!")
}
} Haskell's
do-notation is syntactic sugar over the general Monad typeclass — it works identically for IO, Maybe, lists, and any other type with a Monad instance. Scala has no single IO monad baked into the language (though libraries like cats-effect provide one) — ordinary strict imperative code already sequences actions without needing any special notation, since there is no IO value to thread through pure code the way Haskell does.The Maybe monad becomes a for-comprehension over Option
import qualified Data.Map as Map
lookupBoth :: String -> String -> Map.Map String Int -> Maybe Int
lookupBoth keyOne keyTwo table = do
first <- Map.lookup keyOne table
second <- Map.lookup keyTwo table
return (first + second)
main :: IO ()
main =
let table = Map.fromList [("a", 1), ("b", 2)]
in print (lookupBoth "a" "b" table) object Main {
def lookupBoth(keyOne: String, keyTwo: String, table: Map[String, Int]): Option[Int] =
for {
first <- table.get(keyOne)
second <- table.get(keyTwo)
} yield first + second
def main(args: Array[String]): Unit = {
val table = Map("a" -> 1, "b" -> 2)
println(lookupBoth("a", "b", table))
}
} This is the closest match to Haskell's
do-notation of any language on the site: Scala's for { x <- ...; y <- ... } yield ... desugars to exactly the same flatMap/map chain Haskell's do-notation desugars to for Monad, and it short-circuits on the first None automatically, with the same <- arrow spelling Haskell uses.For-comprehensions generalize across types — like Monad does
-- Haskell's do-notation works UNCHANGED for any Monad — IO, Maybe,
-- lists, or a custom type — because Monad is one typeclass with one
-- bind operation (>>=) that every instance implements:
main :: IO ()
main = do
let doubled = do
x <- [1, 2, 3]
return (x * 2)
print (doubled :: [Int]) object Main {
def main(args: Array[String]): Unit = {
// The exact same for-syntax that worked for Option above also
// works for List — Scala's for-comprehension is genuinely
// polymorphic over anything with flatMap/map/withFilter, the
// closest a non-Haskell language on this site gets to Monad:
val doubled = for (x <- List(1, 2, 3)) yield x * 2
println(doubled)
}
} Unlike F#'s separate, unrelated computation expressions or Roc's complete absence of a generalized abstraction, Scala's
for-comprehension really does generalize the way Haskell's do-notation does: the exact same syntax that worked for Option in the previous row works unchanged for List, Either, or Future, because the Scala compiler desugars for into calls to whichever flatMap/map/withFilter methods the underlying type happens to define — structural, not typeclass-based, but genuinely uniform across types the way Haskell's Monad is.Functions & Currying
Anonymous functions (lambdas)
main :: IO ()
main = print (map (\x -> x * x) [1, 2, 3, 4]) object Main {
def main(args: Array[String]): Unit = {
println(List(1, 2, 3, 4).map(x => x * x))
}
} Haskell spells a lambda
\x -> ... (the backslash evoking the Greek letter lambda). Scala spells the same idea x => ..., and — when the parameter is used exactly once and its type is already known from context — can shorten it further to the placeholder syntax _ * _ seen in earlier rows.Currying needs an extra parameter list — not automatic
add :: Int -> Int -> Int
add x y = x + y
main :: IO ()
main =
let addFive = add 5
in print (addFive 10) object Main {
def add(x: Int)(y: Int): Int = x + y
def main(args: Array[String]): Unit = {
val addFive = add(5) _
println(addFive(10))
}
} This is a genuine difference. In Haskell,
add 5 alone is already valid — every function is secretly a chain of one-argument functions. Scala supports currying natively via multiple parameter lists — def add(x: Int)(y: Int) — but it is opt-in: a normal def add(x: Int, y: Int) cannot be partially applied at all, and even with multiple parameter lists, add(5) _ (the trailing underscore) is required to explicitly request the remaining function as a value rather than a syntax error.Recursion works the same way
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
main :: IO ()
main = print (factorial 10) object Main {
def factorial(n: Int): Long =
if (n == 0) 1L else n * factorial(n - 1)
def main(args: Array[String]): Unit = {
println(factorial(10))
}
} A base-case clause and a recursive clause, nearly a mechanical translation — the main syntactic difference is that Scala's single
def uses an if/else where Haskell writes separate pattern-matched equations for the base case and the recursive case.Laziness vs. Strictness
Infinite lists need LazyList — not the default List
main :: IO ()
main = print (take 5 [1..]) object Main {
def main(args: Array[String]): Unit = {
val numbers = LazyList.from(1)
println(numbers.take(5).toList)
}
} Haskell's
[1..] is an ordinary list that only works because Haskell is lazy by default, generating each element on demand as take asks for it. Scala's default List is strict and would try to build the entire infinite list immediately (and hang forever); Scala's separate LazyList type is the one that plays Haskell's "infinite list" role, evaluating and caching elements only as they are demanded.lazy val: opt-in, not the default
main :: IO ()
main = do
let expensive = 2 ^ 20 -- not computed until actually demanded
putStrLn "before use"
print expensive object Main {
def main(args: Array[String]): Unit = {
lazy val expensive = math.pow(2, 20)
println("before use")
println(expensive) // computed here, on first access, and cached
}
} In Haskell, every binding is lazy unless forced otherwise —
expensive is not actually computed until print demands it. Scala is strict by default, so achieving the same deferred, compute-once-and-cache behavior requires the explicit lazy keyword — laziness is opt-in machinery in Scala, not the ambient evaluation model, though lazy val does genuinely cache its result after the first access, matching Haskell's thunk-memoization behavior exactly once triggered.Type Classes vs. Implicit Parameters
Type classes have a real, working Scala analogue
class Describable a where
describe :: a -> String
data Dog = Dog
instance Describable Dog where
describe _ = "a dog"
main :: IO ()
main = putStrLn (describe Dog) trait Describable[A] {
def describe(a: A): String
}
case class Dog()
object Describable {
implicit val dogDescribable: Describable[Dog] = new Describable[Dog] {
def describe(a: Dog): String = "a dog"
}
}
object Main {
def show[A](a: A)(implicit ev: Describable[A]): String = ev.describe(a)
def main(args: Array[String]): Unit = {
println(show(Dog()))
}
} Unlike F#'s interfaces, Roc's structural constraints, or ReScript's complete absence of ad-hoc polymorphism, Scala's implicit parameters give a genuinely working analogue to Haskell type classes:
Describable[A] is the class, implicit val dogDescribable is the instance, and show's implicit ev: Describable[A] parameter is resolved automatically by the compiler searching for a matching implicit value — the same "the compiler picks the right implementation for the type at the call site" behavior Haskell's type classes provide, just spelled with an explicit parameter instead of Haskell's built-in dispatch syntax.Multiple instances resolve automatically, just like Haskell
class Printable a where
toDisplay :: a -> String
instance Printable Int where
toDisplay n = show n
instance Printable Double where
toDisplay d = show d
main :: IO ()
main = do
putStrLn (toDisplay (42 :: Int))
putStrLn (toDisplay (2.5 :: Double)) trait Printable[A] {
def toDisplay(a: A): String
}
object Printable {
implicit val intPrintable: Printable[Int] = new Printable[Int] {
def toDisplay(a: Int): String = a.toString
}
implicit val doublePrintable: Printable[Double] = new Printable[Double] {
def toDisplay(a: Double): String = a.toString
}
}
object Main {
def display[A](a: A)(implicit ev: Printable[A]): String = ev.toDisplay(a)
def main(args: Array[String]): Unit = {
println(display(42))
println(display(2.5))
}
} The same
display call resolves to a different implicit instance depending on the argument's type — Int versus Double — exactly as Haskell's toDisplay resolves to a different type-class instance automatically. The caller never names which instance to use in either language; the compiler figures it out from the type.deriving (Eq) versus automatic case-class equality
data Point = Point Int Int deriving (Eq, Show)
main :: IO ()
main = print (Point 1 2 == Point 1 2) case class Point(x: Int, y: Int)
object Main {
def main(args: Array[String]): Unit = {
println(Point(1, 2) == Point(1, 2))
}
} Haskell requires an explicit
deriving (Eq) clause to opt a type into structural equality — mechanically, an automatically generated type-class instance. A Scala case class gets structural equality, hashing, and a sensible toString automatically with no annotation at all, one case where Scala's default behavior is more convenient than Haskell's.Error Handling
Exceptions exist in both, but are secondary to Either
import Control.Exception
main :: IO ()
main = do
result <- try (evaluate (1 `div` 0)) :: IO (Either ArithException Int)
case result of
Left err -> putStrLn ("Caught: " ++ show err)
Right value -> print value object Main {
def main(args: Array[String]): Unit = {
try {
val result = 1 / 0
println(result)
} catch {
case e: ArithmeticException => println(s"Caught: ${e.getMessage}")
}
}
} Both languages support exceptions (Haskell's
Control.Exception, Scala's inherited JVM try/catch), but idiomatic code in both prefers modeling expected failure explicitly with Maybe/Either or Option/Either, reserving exceptions for genuinely unexpected, unrecoverable situations.Try wraps a risky computation as a value
import Control.Exception
safeDivide :: Int -> Int -> IO (Either SomeException Int)
safeDivide x y = try (evaluate (x `div` y))
main :: IO ()
main = do
result <- safeDivide 10 0
case result of
Left err -> putStrLn "Caught a division error"
Right value -> print value import scala.util.{Try, Success, Failure}
object Main {
def safeDivide(x: Int, y: Int): Try[Int] = Try(x / y)
def main(args: Array[String]): Unit = {
safeDivide(10, 0) match {
case Success(value) => println(value)
case Failure(_) => println("Caught a division error")
}
}
} Scala's
Try[A] (Success/Failure) is a standard-library type built exactly for this: wrap a computation that might throw, and get back a pattern-matchable value instead of an exception that must be caught. Haskell's Control.Exception.try plays the identical role, wrapping the risky action in an IO (Either SomeException a) — same idea, expressed through IO and Either rather than a dedicated Try type.Gotchas for Haskell Developers
Scala is not purely functional — mutation and OOP are always available
-- Haskell structurally cannot mutate a value in place outside of
-- an explicit IORef/STRef/MVar — there is no escape hatch:
main :: IO ()
main = print [1, 2, 3] import scala.collection.mutable.ArrayBuffer
object Main {
def main(args: Array[String]): Unit = {
// Scala's standard library ships genuinely mutable collections
// right alongside the immutable ones used everywhere else on
// this page — nothing stops reaching for them:
val numbers = ArrayBuffer(1, 2, 3)
numbers += 4
println(numbers)
}
} Haskell has no escape hatch into ordinary in-place mutation outside of explicit
IORef/STRef/MVar machinery threaded through IO. Scala, being a hybrid OOP/FP language on the JVM, ships genuinely mutable collections (scala.collection.mutable.*) as first-class citizens right alongside the immutable ones — nothing in the language stops a Scala codebase from being written in an entirely imperative, Java-like style, a discipline Haskell enforces structurally that Scala only encourages by convention.`null` still exists, unlike in Haskell
main :: IO ()
main = print (Just "hello") object Main {
def main(args: Array[String]): Unit = {
val value: String = null // legal — inherited from the JVM, though discouraged
println(value == null)
}
} Haskell has no
null concept at all — absence is always represented explicitly via Maybe. Scala, inheriting the JVM, still permits null for any reference type (though idiomatic Scala avoids it almost entirely in favor of Option), so a stray NullPointerException remains possible in a way it structurally cannot be in Haskell.