convert-haskell-scala
Convert Haskell code to idiomatic Scala. Use when migrating Haskell projects to Scala, translating Haskell patterns to idiomatic Scala, or refactoring Haskell codebases. Extends meta-convert-dev with Haskell-to-Scala specific patterns.
When & Why to Use This Skill
This Claude skill facilitates the seamless conversion of Haskell code into idiomatic Scala. It specializes in mapping complex functional programming concepts—including type systems, monadic patterns, and concurrency models—ensuring that migrated code remains maintainable, performant, and follows Scala's best practices.
Use Cases
- Legacy Project Migration: Porting existing Haskell microservices or libraries to the Scala/JVM ecosystem while preserving functional logic and purity.
- Idiom Translation: Converting Haskell-specific patterns like Type Classes, Lazy Evaluation, and Do-notation into Scala's Implicits (Given/Using), LazyList, and For-comprehensions.
- Codebase Refactoring: Modernizing Scala projects by translating robust Haskell patterns into Scala to improve type safety and architectural modularity.
- Developer Onboarding: Assisting Haskell developers in transitioning to Scala by providing side-by-side translations of familiar functional logic into idiomatic Scala syntax.
| name | convert-haskell-scala |
|---|---|
| description | Convert Haskell code to idiomatic Scala. Use when migrating Haskell projects to Scala, translating Haskell patterns to idiomatic Scala, or refactoring Haskell codebases. Extends meta-convert-dev with Haskell-to-Scala specific patterns. |
Convert Haskell to Scala
Convert Haskell code to idiomatic Scala. This skill extends meta-convert-dev with Haskell-to-Scala specific type mappings, idiom translations, and tooling.
This Skill Extends
meta-convert-dev- Foundational conversion patterns (APTV workflow, testing strategies)
For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.
This Skill Adds
- Type mappings: Haskell types → Scala types
- Idiom translations: Haskell patterns → idiomatic Scala
- Error handling: Haskell Maybe/Either → Scala Option/Either
- Async patterns: Haskell IO/Async → Scala Future/IO
- Lazy evaluation: Haskell lazy by default → Scala strict with lazy vals
- Type classes: Haskell type classes → Scala implicits/given
This Skill Does NOT Cover
- General conversion methodology - see
meta-convert-dev - Haskell language fundamentals - see
lang-haskell-dev - Scala language fundamentals - see
lang-scala-dev - Reverse conversion (Scala → Haskell) - see
convert-scala-haskell
Quick Reference
| Haskell | Scala | Notes |
|---|---|---|
String |
String |
Both are immutable strings |
Int |
Int |
32-bit integers |
Integer |
BigInt |
Arbitrary precision |
Double |
Double |
Floating point |
Bool |
Boolean |
Boolean values |
[a] |
List[A] |
Linked list |
(a, b) |
(A, B) |
Tuple |
Maybe a |
Option[A] |
Nullable values |
Either a b |
Either[A, B] |
Sum type for errors |
IO a |
IO[A] (Cats Effect) |
Side effects |
data |
case class / sealed trait |
ADTs |
class (type class) |
trait + implicit/given |
Type classes |
-> (function type) |
=> (function type) |
Function types |
Monad |
Monad (Cats) |
Requires Cats library |
When Converting Code
- Analyze source thoroughly before writing target
- Map types first - create type equivalence table
- Preserve semantics over syntax similarity
- Adopt Scala idioms - don't write "Haskell code in Scala syntax"
- Handle edge cases - lazy evaluation, null safety, JVM interop
- Test equivalence - same inputs → same outputs
Type System Mapping
Primitive Types
| Haskell | Scala | Notes |
|---|---|---|
Int |
Int |
32-bit signed integer |
Integer |
BigInt |
Arbitrary precision integer |
Double |
Double |
64-bit floating point |
Float |
Float |
32-bit floating point |
Bool |
Boolean |
Boolean type |
Char |
Char |
Single character |
String |
String |
Immutable string |
() |
Unit |
Unit type (void) |
Collection Types
| Haskell | Scala | Notes |
|---|---|---|
[a] |
List[A] |
Immutable linked list |
(a, b) |
(A, B) |
Tuple (up to 22 elements in Scala) |
(a, b, c) |
(A, B, C) |
3-tuple |
Map k v |
Map[K, V] |
Immutable map |
Set a |
Set[A] |
Immutable set |
Vector a |
Vector[A] |
Indexed sequence |
Seq a |
Seq[A] |
Generic sequence |
Composite Types
| Haskell | Scala | Notes |
|---|---|---|
data X = A | B |
sealed trait X; case object A extends X; case object B extends X |
Sum types |
data X = X { field :: Type } |
case class X(field: Type) |
Product types with named fields |
newtype X = X Type |
case class X(value: Type) extends AnyVal |
Zero-cost wrapper |
type X = Y |
type X = Y |
Type alias |
Maybe a |
Option[A] |
Optional values |
Either a b |
Either[A, B] |
Sum type (Left is error by convention in both) |
Function Types
| Haskell | Scala | Notes |
|---|---|---|
a -> b |
A => B |
Function from A to B |
a -> b -> c |
A => B => C or (A, B) => C |
Curried vs uncurried |
IO a |
IO[A] (Cats Effect) |
Effectful computation |
Monad m => m a |
F[A] (with Monad[F]) |
Higher-kinded types |
Idiom Translation
Pattern 1: Maybe/Option Handling
Haskell:
findUser :: String -> Maybe User
findUser userId = lookup userId users
-- Pattern matching
case findUser "123" of
Just user -> processUser user
Nothing -> putStrLn "User not found"
-- Maybe functions
fromMaybe defaultUser (findUser "123")
maybe "No user" userName (findUser "123")
Scala:
def findUser(userId: String): Option[User] =
users.get(userId)
// Pattern matching
findUser("123") match {
case Some(user) => processUser(user)
case None => println("User not found")
}
// Option methods
findUser("123").getOrElse(defaultUser)
findUser("123").map(_.name).getOrElse("No user")
Why this translation:
MaybeandOptionare semantically equivalent- Both use pattern matching for explicit handling
- Scala's
.getOrElseis more concise thanfromMaybe - Method chaining is idiomatic in Scala
Pattern 2: Either for Error Handling
Haskell:
data AppError = NotFound | ValidationError String
parseAge :: String -> Either AppError Int
parseAge str =
case reads str of
[(n, "")] -> if n >= 0
then Right n
else Left (ValidationError "Age must be positive")
_ -> Left (ValidationError "Not a valid number")
-- Chaining with do-notation
validateUser :: String -> String -> Either AppError User
validateUser ageStr emailStr = do
age <- parseAge ageStr
email <- validateEmail emailStr
return $ User email age
Scala:
sealed trait AppError
case object NotFound extends AppError
case class ValidationError(message: String) extends AppError
def parseAge(str: String): Either[AppError, Int] = {
str.toIntOption match {
case Some(n) if n >= 0 => Right(n)
case Some(_) => Left(ValidationError("Age must be positive"))
case None => Left(ValidationError("Not a valid number"))
}
}
// Chaining with for-comprehension
def validateUser(ageStr: String, emailStr: String): Either[AppError, User] = {
for {
age <- parseAge(ageStr)
email <- validateEmail(emailStr)
} yield User(email, age)
}
Why this translation:
- Both use Either with Left for errors, Right for success
- Haskell's
do-notationmaps to Scala'sfor-comprehension - ADT error types work similarly in both languages
- Scala requires explicit
yieldat the end of for-comprehension
Pattern 3: List Comprehensions
Haskell:
-- List comprehension
squares = [x^2 | x <- [1..10], even x]
-- With multiple generators
pairs = [(x, y) | x <- [1..3], y <- [1..3], x < y]
-- Nested comprehensions
matrix = [[1..n] | n <- [1..5]]
Scala:
// For-comprehension
val squares = for {
x <- 1 to 10
if x % 2 == 0
} yield x * x
// With multiple generators
val pairs = for {
x <- 1 to 3
y <- 1 to 3
if x < y
} yield (x, y)
// Using map/filter (more idiomatic for simple cases)
val squares = (1 to 10).filter(_ % 2 == 0).map(x => x * x)
// Nested
val matrix = (1 to 5).map(n => (1 to n).toList)
Why this translation:
- Both desugar to map/flatMap/filter
- Scala's for-comprehension requires
yieldkeyword - Guards (filters) use
ifin both - Scala often prefers method chaining for simple transformations
Pattern 4: Pattern Matching on ADTs
Haskell:
data Shape = Circle Double
| Rectangle Double Double
| Triangle Double Double Double
area :: Shape -> Double
area (Circle r) = pi * r^2
area (Rectangle w h) = w * h
area (Triangle b h _) = 0.5 * b * h
-- Guards
classify :: Int -> String
classify n
| n < 0 = "negative"
| n == 0 = "zero"
| otherwise = "positive"
Scala:
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Triangle(base: Double, height: Double, side: Double) extends Shape
def area(shape: Shape): Double = shape match {
case Circle(r) => math.Pi * r * r
case Rectangle(w, h) => w * h
case Triangle(b, h, _) => 0.5 * b * h
}
// Guards
def classify(n: Int): String = n match {
case n if n < 0 => "negative"
case 0 => "zero"
case _ => "positive"
}
Why this translation:
- Haskell uses data constructors, Scala uses case classes
- Pattern matching syntax is similar
- Guards use
ifin Scala match,|in Haskell function definitions sealed traitensures exhaustiveness checking like Haskell
Pattern 5: Type Classes to Implicits/Given
Haskell:
class Show a where
show :: a -> String
instance Show Int where
show = Prelude.show
instance Show User where
show (User name age) = name ++ " (" ++ Prelude.show age ++ ")"
printValue :: Show a => a -> IO ()
printValue x = putStrLn (show x)
Scala 2 (implicits):
trait Show[A] {
def show(a: A): String
}
object Show {
implicit val intShow: Show[Int] = new Show[Int] {
def show(a: Int): String = a.toString
}
implicit val userShow: Show[User] = new Show[User] {
def show(user: User): String = s"${user.name} (${user.age})"
}
}
def printValue[A](x: A)(implicit s: Show[A]): Unit = {
println(s.show(x))
}
Scala 3 (given/using):
trait Show[A] {
def show(a: A): String
}
given Show[Int] with {
def show(a: Int): String = a.toString
}
given Show[User] with {
def show(user: User): String = s"${user.name} (${user.age})"
}
def printValue[A](x: A)(using s: Show[A]): Unit = {
println(s.show(x))
}
Why this translation:
- Type classes map to traits with implicit/given instances
- Constraints become implicit/using parameters
- Scala 3 syntax is closer to Haskell's
- Both support type class derivation (Haskell with deriving, Scala with macros)
Pattern 6: Lazy Evaluation
Haskell:
-- Infinite lists (lazy by default)
naturals = [1..]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
-- Take first 10
take 10 naturals
take 10 fibs
-- Lazy evaluation of expressions
expensiveComputation = trace "Computing..." (sum [1..1000000])
result = if condition then expensiveComputation else 0 -- Only computed if condition is True
Scala:
// LazyList (Stream in Scala 2.12)
val naturals = LazyList.from(1)
val fibs: LazyList[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map { case (a, b) => a + b }
// Take first 10
naturals.take(10).toList
fibs.take(10).toList
// Lazy val for deferred evaluation
lazy val expensiveComputation = {
println("Computing...")
(1 to 1000000).sum
}
val result = if (condition) expensiveComputation else 0 // Only computed if condition is true
// By-name parameters for lazy arguments
def ifThenElse[A](cond: Boolean)(thenBranch: => A)(elseBranch: => A): A = {
if (cond) thenBranch else elseBranch
}
Why this translation:
- Haskell is lazy by default, Scala is strict by default
- Use
LazyListfor infinite sequences - Use
lazy valfor deferred computation - By-name parameters (
=> A) for lazy function arguments - Scala 2.13+ renamed Stream to LazyList
Pattern 7: Function Composition and Application
Haskell:
-- Function composition
addThenDouble = (*2) . (+1)
process = filter even . map (*2) . filter (>0)
-- Function application
result = f $ g $ h x -- Equivalent to f (g (h x))
-- Point-free style
sumOfSquares = sum . map (^2)
Scala:
// Function composition
val addThenDouble = ((x: Int) => x + 1) andThen (_ * 2)
val addThenDouble2 = ((x: Int) => x * 2) compose ((x: Int) => x + 1)
// Method chaining (more idiomatic)
def process(list: List[Int]): List[Int] =
list.filter(_ > 0).map(_ * 2).filter(_ % 2 == 0)
// Infix notation for single-arg methods
val result = f(g(h(x))) // No special operator needed
// Function style with compose
val sumOfSquares = ((list: List[Int]) => list.map(x => x * x)).andThen(_.sum)
// More idiomatic Scala
def sumOfSquares(list: List[Int]): Int = list.map(x => x * x).sum
Why this translation:
- Haskell's
.maps tocompose(right-to-left) orandThen(left-to-right) - Scala prefers method chaining over function composition
- Haskell's
$isn't needed in Scala (no precedence issues) - Point-free style less common in Scala
Pattern 8: Monadic Composition
Haskell:
-- Do-notation
computation :: IO ()
computation = do
putStrLn "What's your name?"
name <- getLine
putStrLn $ "Hello, " ++ name
-- Maybe monad
safeDivision :: Maybe Int
safeDivision = do
a <- Just 10
b <- Just 2
result <- Just (div a b)
return (result * 2)
-- List monad
pairs :: [(Int, Int)]
pairs = do
x <- [1..3]
y <- [1..3]
guard (x < y)
return (x, y)
Scala:
// For-comprehension with IO (Cats Effect)
import cats.effect.IO
val computation: IO[Unit] = for {
_ <- IO.println("What's your name?")
name <- IO.readLine
_ <- IO.println(s"Hello, $name")
} yield ()
// Option monad
val safeDivision: Option[Int] = for {
a <- Some(10)
b <- Some(2)
result <- Some(a / b)
} yield result * 2
// List monad
val pairs: List[(Int, Int)] = for {
x <- (1 to 3).toList
y <- (1 to 3).toList
if x < y
} yield (x, y)
Why this translation:
- Haskell's
do-notationmaps directly to Scala'sfor-comprehension - Both desugar to flatMap/map
- Haskell uses
return, Scala usesyield guardbecomesifin for-comprehension- IO monad requires Cats Effect library in Scala
Error Handling
Haskell Maybe/Either → Scala Option/Either
| Haskell Pattern | Scala Equivalent | Notes |
|---|---|---|
Nothing |
None |
Absence of value |
Just x |
Some(x) |
Present value |
Left err |
Left(err) |
Error case |
Right val |
Right(val) |
Success case |
fromMaybe default |
.getOrElse(default) |
Provide default |
maybe defaultVal f |
.map(f).getOrElse(defaultVal) |
Map with default |
either errorHandler successHandler |
.fold(errorHandler, successHandler) |
Fold both cases |
Exception Handling
Haskell:
import Control.Exception
readFileSafe :: FilePath -> IO (Either IOException String)
readFileSafe path = try $ readFile path
-- Using try/catch
processFile :: FilePath -> IO ()
processFile path = do
result <- try (readFile path) :: IO (Either IOException String)
case result of
Left ex -> putStrLn $ "Error: " ++ show ex
Right content -> putStrLn content
Scala:
import scala.util.{Try, Success, Failure}
import java.io.IOException
def readFileSafe(path: String): Either[IOException, String] = {
Try(scala.io.Source.fromFile(path).mkString).toEither match {
case Right(content) => Right(content)
case Left(ex: IOException) => Left(ex)
case Left(ex) => Left(new IOException(ex))
}
}
// Using Try
def processFile(path: String): Unit = {
Try(scala.io.Source.fromFile(path).mkString) match {
case Success(content) => println(content)
case Failure(ex) => println(s"Error: ${ex.getMessage}")
}
}
Why this translation:
- Haskell's
tryfrom Control.Exception maps to Scala'sTry - Both can convert to Either for type-safe error handling
- Scala has nullable types from Java, so be careful with interop
- Use Try for exception handling, Either for domain errors
Concurrency Model
Haskell IO/Async → Scala Future/IO
| Haskell | Scala | Library |
|---|---|---|
IO a |
Future[A] |
scala.concurrent |
IO a |
IO[A] |
cats-effect |
async |
Future { ... } |
scala.concurrent |
forkIO |
Future { ... } |
scala.concurrent |
Async |
Async[F] |
cats-effect |
concurrently |
Future.sequence |
scala.concurrent |
race |
Future.firstCompletedOf |
scala.concurrent |
Basic Async Translation
Haskell:
import Control.Concurrent.Async
fetchData :: IO String
fetchData = do
threadDelay 1000000
return "data"
main :: IO ()
main = do
result <- fetchData
putStrLn result
-- Concurrent execution
main :: IO ()
main = do
(data1, data2) <- concurrently fetchData1 fetchData2
putStrLn $ data1 ++ data2
Scala:
import scala.concurrent.{Future, Await}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
def fetchData: Future[String] = Future {
Thread.sleep(1000)
"data"
}
def main(): Unit = {
val result = Await.result(fetchData, 5.seconds)
println(result)
}
// Concurrent execution
def main(): Unit = {
val combined = for {
data1 <- fetchData1
data2 <- fetchData2
} yield data1 + data2
println(Await.result(combined, 5.seconds))
}
Why this translation:
- Haskell's IO is pure, Scala's Future is eager
- Use Cats Effect IO for pure functional effects in Scala
concurrentlymaps to for-comprehension with Futures- Avoid
Awaitin production; use callbacks or IO
Software Transactional Memory
Haskell:
import Control.Concurrent.STM
type Account = TVar Int
transfer :: Account -> Account -> Int -> STM ()
transfer from to amount = do
fromBalance <- readTVar from
when (fromBalance < amount) retry
modifyTVar from (subtract amount)
modifyTVar to (+ amount)
main :: IO ()
main = do
account1 <- newTVarIO 1000
account2 <- newTVarIO 0
atomically $ transfer account1 account2 500
Scala:
import scala.concurrent.stm._
type Account = Ref[Int]
def transfer(from: Account, to: Account, amount: Int): Unit = {
atomic { implicit txn =>
val fromBalance = from()
if (fromBalance < amount) retry
from() = fromBalance - amount
to() = to() + amount
}
}
def main(): Unit = {
val account1 = Ref(1000)
val account2 = Ref(0)
transfer(account1, account2, 500)
}
Why this translation:
- Both support STM with similar semantics
- Scala STM requires
scala-stmlibrary atomicblock replacesatomicallyretryworks the same way
Memory Model
Haskell Lazy Evaluation → Scala Strict Evaluation
| Concept | Haskell | Scala | Notes |
|---|---|---|---|
| Default evaluation | Lazy | Strict | Major difference |
| Force evaluation | seq, deepseq |
N/A (already strict) | - |
| Defer evaluation | Default | lazy val, LazyList |
Explicit in Scala |
| Infinite structures | [1..] |
LazyList.from(1) |
Requires LazyList |
| Thunks | Automatic | => A (by-name) |
Explicit in Scala |
Space Leaks and Strictness
Haskell:
-- Potential space leak (lazy accumulation)
badSum :: [Int] -> Int
badSum = foldl (+) 0 -- Builds up thunks
-- Strict version
goodSum :: [Int] -> Int
goodSum = foldl' (+) 0 -- Forces evaluation
-- Bang patterns
data Point = Point !Int !Int -- Strict fields
-- Forcing evaluation
result = x `seq` y -- Evaluate x, then return y
Scala:
// No space leak (strict by default)
def sum(list: List[Int]): Int =
list.foldLeft(0)(_ + _) // Always strict
// Lazy evaluation when needed
lazy val expensiveValue = {
println("Computing...")
1 + 1
}
// Strict fields by default
case class Point(x: Int, y: Int) // Already strict
// All evaluation is forced by default
val result = {
val _ = x // x is evaluated
y // y is returned
}
Why this matters:
- Haskell's laziness can cause space leaks
- Scala doesn't have this problem by default
- Use
lazy valsparingly in Scala - LazyList for infinite structures
Common Pitfalls
1. Assuming Lazy Evaluation
Problem: Expecting Scala to be lazy like Haskell.
Example:
// ❌ This will hang in Scala (strict evaluation)
val naturals = (1 to Int.MaxValue).toList // Tries to build entire list!
// ✓ Use LazyList for infinite sequences
val naturals = LazyList.from(1)
Solution: Use LazyList for potentially infinite sequences, lazy val for deferred computation.
2. Null Pointer Exceptions
Problem: Scala has null from Java interop, Haskell doesn't.
Example:
// ❌ Dangerous when calling Java code
val name: String = javaObject.getName // Could be null!
val length = name.length // NullPointerException
// ✓ Wrap in Option
val name: Option[String] = Option(javaObject.getName)
val length = name.map(_.length).getOrElse(0)
Solution: Always use Option(...) when calling Java code that might return null.
3. Uncurried Functions
Problem: Haskell functions are curried by default, Scala's are not.
Example:
// Haskell: add :: Int -> Int -> Int
// Scala: Either curried or uncurried
// ❌ Uncurried (not partial-application friendly)
def add(a: Int, b: Int): Int = a + b
// Can't do: val add5 = add(5, _) // Requires placeholder
// ✓ Curried (Haskell-style)
def add(a: Int)(b: Int): Int = a + b
val add5 = add(5) _ // Partial application
Solution: Use curried functions def f(a: A)(b: B) when partial application is needed.
4. Missing Type Classes
Problem: Haskell has many built-in type classes; Scala requires libraries.
Example:
// ❌ No built-in Functor, Monad, etc.
def map[F[_], A, B](fa: F[A])(f: A => B): F[B] = ??? // Can't implement generically
// ✓ Use Cats library
import cats.Functor
import cats.implicits._
def map[F[_]: Functor, A, B](fa: F[A])(f: A => B): F[B] =
fa.map(f)
Solution: Use Cats library for functional abstractions (Functor, Monad, Applicative, etc.).
5. Pattern Matching Exhaustiveness
Problem: Scala allows non-exhaustive matches without warnings by default.
Example:
// ❌ Non-exhaustive match (compiles but warns)
sealed trait Result
case class Success(value: Int) extends Result
case class Failure(error: String) extends Result
def handle(result: Result): String = result match {
case Success(value) => s"Got $value"
// Missing Failure case!
}
// ✓ Complete match
def handle(result: Result): String = result match {
case Success(value) => s"Got $value"
case Failure(error) => s"Error: $error"
}
Solution: Use sealed trait and enable -Xfatal-warnings compiler option to catch non-exhaustive matches.
6. Do-Notation vs For-Comprehension Differences
Problem: Subtle differences between Haskell do-notation and Scala for-comprehension.
Example:
// Haskell: do { return x }
// Scala: Must use yield
// ❌ Missing yield
val result = for {
x <- Some(1)
y <- Some(2)
x + y // Does nothing!
}
// ✓ Use yield
val result = for {
x <- Some(1)
y <- Some(2)
} yield x + y
Solution: Always use yield at the end of for-comprehensions (equivalent to Haskell's return).
7. Higher-Kinded Types
Problem: Scala 2 requires explicit kind annotation; Scala 3 is better.
Example:
// Haskell: class Functor f where
// fmap :: (a -> b) -> f a -> f b
// Scala 2: Requires explicit kind
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
// Usage requires type lambda for partially applied types
// ❌ Can't do: Functor[Either[String, ?]] in Scala 2
// ✓ Scala 2: Need kind-projector plugin
// ✓ Scala 3: Much better support
Solution: Use Scala 3 for better higher-kinded type support, or use kind-projector plugin in Scala 2.
Tooling
| Category | Haskell | Scala |
|---|---|---|
| Build Tool | Cabal, Stack | sbt, Mill |
| REPL | GHCi | scala, sbt console |
| Formatter | stylish-haskell, brittany | scalafmt |
| Linter | hlint | scalafix, wartremover |
| Testing | HSpec, QuickCheck | ScalaTest, ScalaCheck |
| Type Checker | GHC | scalac |
| Package Registry | Hackage | Maven Central |
| Documentation | Haddock | Scaladoc |
Helpful Libraries for Haskell Patterns
| Pattern | Haskell | Scala Library |
|---|---|---|
| Type classes | Prelude, base | cats, scalaz |
| Effects | transformers, mtl | cats-effect, zio |
| Optics | lens | monocle |
| JSON | aeson | circe, play-json |
| Parsing | parsec, megaparsec | cats-parse, fastparse |
| Testing | QuickCheck | scalacheck |
| STM | stm | scala-stm |
Examples
Example 1: Simple - List Processing
Before (Haskell):
-- Sum of squares of even numbers
sumOfEvenSquares :: [Int] -> Int
sumOfEvenSquares xs = sum [x^2 | x <- xs, even x]
-- Alternative with functions
sumOfEvenSquares' :: [Int] -> Int
sumOfEvenSquares' = sum . map (^2) . filter even
-- Pattern matching on lists
myLength :: [a] -> Int
myLength [] = 0
myLength (_:xs) = 1 + myLength xs
After (Scala):
// Sum of squares of even numbers
def sumOfEvenSquares(xs: List[Int]): Int = {
(for {
x <- xs
if x % 2 == 0
} yield x * x).sum
}
// Alternative with method chaining (more idiomatic)
def sumOfEvenSquares(xs: List[Int]): Int =
xs.filter(_ % 2 == 0).map(x => x * x).sum
// Pattern matching on lists
def myLength[A](xs: List[A]): Int = xs match {
case Nil => 0
case _ :: tail => 1 + myLength(tail)
}
Example 2: Medium - Type Classes and ADTs
Before (Haskell):
-- Type class
class Describable a where
describe :: a -> String
-- ADT
data Shape = Circle Double
| Rectangle Double Double
deriving (Show, Eq)
instance Describable Shape where
describe (Circle r) = "Circle with radius " ++ show r
describe (Rectangle w h) = "Rectangle " ++ show w ++ "x" ++ show h
-- Using type class
printDescription :: Describable a => a -> IO ()
printDescription x = putStrLn (describe x)
-- Higher-order function with type class
mapDescribe :: Describable a => [a] -> [String]
mapDescribe = map describe
After (Scala):
// Type class
trait Describable[A] {
def describe(a: A): String
}
object Describable {
def apply[A](implicit d: Describable[A]): Describable[A] = d
implicit class DescribableOps[A](val a: A) extends AnyVal {
def describe(implicit d: Describable[A]): String = d.describe(a)
}
}
// ADT
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
// Type class instance
object Shape {
implicit val describableShape: Describable[Shape] = new Describable[Shape] {
def describe(shape: Shape): String = shape match {
case Circle(r) => s"Circle with radius $r"
case Rectangle(w, h) => s"Rectangle ${w}x$h"
}
}
}
// Using type class
def printDescription[A: Describable](x: A): Unit = {
println(Describable[A].describe(x))
}
// Or with extension method
import Describable._
def printDescription[A: Describable](x: A): Unit = {
println(x.describe)
}
// Higher-order function with type class
def mapDescribe[A: Describable](xs: List[A]): List[String] =
xs.map(_.describe)
Example 3: Complex - Parser Combinator with Monadic Composition
Before (Haskell):
import Text.Parsec
import Text.Parsec.String (Parser)
-- Simple expression parser
data Expr = Num Int
| Add Expr Expr
| Mul Expr Expr
deriving (Show, Eq)
number :: Parser Expr
number = Num . read <$> many1 digit
expr :: Parser Expr
expr = term `chainl1` addOp
term :: Parser Expr
term = factor `chainl1` mulOp
factor :: Parser Expr
factor = number <|> parens expr
parens :: Parser a -> Parser a
parens p = char '(' *> p <* char ')'
addOp :: Parser (Expr -> Expr -> Expr)
addOp = Add <$ char '+'
mulOp :: Parser (Expr -> Expr -> Expr)
mulOp = Mul <$ char '*'
-- Evaluator
eval :: Expr -> Int
eval (Num n) = n
eval (Add e1 e2) = eval e1 + eval e2
eval (Mul e1 e2) = eval e1 * eval e2
-- Usage
parseAndEval :: String -> Either ParseError Int
parseAndEval input = do
expr <- parse expr "" input
return (eval expr)
After (Scala):
import cats.parse.{Parser, Numbers}
import cats.parse.Parser._
import cats.parse.Rfc5234.{char, digit}
// Simple expression parser
sealed trait Expr
case class Num(value: Int) extends Expr
case class Add(left: Expr, right: Expr) extends Expr
case class Mul(left: Expr, right: Expr) extends Expr
object ExprParser {
val number: Parser[Expr] =
Numbers.digits.map(s => Num(s.toInt))
lazy val expr: Parser[Expr] =
chainl1(term, addOp)
lazy val term: Parser[Expr] =
chainl1(factor, mulOp)
lazy val factor: Parser[Expr] =
number.orElse(parens(expr))
def parens[A](p: Parser[A]): Parser[A] =
(char('(') *> p <* char(')'))
val addOp: Parser[(Expr, Expr) => Expr] =
char('+').as((e1: Expr, e2: Expr) => Add(e1, e2))
val mulOp: Parser[(Expr, Expr) => Expr] =
char('*').as((e1: Expr, e2: Expr) => Mul(e1, e2))
// Helper for chainl1 (not built-in)
def chainl1[A](p: Parser[A], op: Parser[(A, A) => A]): Parser[A] = {
(p ~ (op ~ p).rep0).map {
case (initial, ops) =>
ops.foldLeft(initial) { case (acc, (f, next)) => f(acc, next) }
}
}
}
// Evaluator
def eval(expr: Expr): Int = expr match {
case Num(n) => n
case Add(e1, e2) => eval(e1) + eval(e2)
case Mul(e1, e2) => eval(e1) * eval(e2)
}
// Usage
def parseAndEval(input: String): Either[Parser.Error, Int] = {
ExprParser.expr.parseAll(input).map(eval)
}
See Also
For more examples and patterns, see:
meta-convert-dev- Foundational patterns with cross-language examplesconvert-typescript-rust- Similar functional programming conversionlang-haskell-dev- Haskell development patternslang-scala-dev- Scala development patterns
Cross-cutting pattern skills:
patterns-concurrency-dev- STM, async patterns across languagespatterns-serialization-dev- JSON, validation patternspatterns-metaprogramming-dev- Type classes, macros, generics