Claude Code Plugins

Community-maintained marketplace

Feedback

convert-haskell-fsharp

@aRustyDev/ai
0
0

Convert Haskell code to idiomatic F#. Use when migrating Haskell projects to F#, translating Haskell patterns to idiomatic F#, or refactoring Haskell codebases. Extends meta-convert-dev with Haskell-to-F# specific patterns.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name convert-haskell-fsharp
description Convert Haskell code to idiomatic F#. Use when migrating Haskell projects to F#, translating Haskell patterns to idiomatic F#, or refactoring Haskell codebases. Extends meta-convert-dev with Haskell-to-F# specific patterns.

Convert Haskell to F#

Convert Haskell code to idiomatic F#. This skill extends meta-convert-dev with Haskell-to-F# 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 → F# types
  • Idiom translations: Haskell patterns → idiomatic F#
  • Error handling: Haskell Maybe/Either → F# Option/Result
  • Type classes: Haskell type classes → F# interfaces/traits
  • Evaluation: Lazy evaluation → eager evaluation with seq
  • Ecosystem: GHC/Cabal/Stack → .NET/NuGet/dotnet CLI

This Skill Does NOT Cover

  • General conversion methodology - see meta-convert-dev
  • Haskell language fundamentals - see lang-haskell-dev
  • F# language fundamentals - see lang-fsharp-dev
  • Reverse conversion (F# → Haskell) - see convert-fsharp-haskell

Quick Reference

Haskell F# Notes
String string Direct mapping
Int int 32-bit signed integer
Integer bigint Arbitrary precision
Float/Double float 64-bit floating point
Bool bool Direct mapping
Char char Direct mapping
[a] list<'a> Immutable linked list
Maybe a option<'a> Optional values
Either a b Result<'b,'a> Note: order swapped
(a, b) 'a * 'b Tuple (different syntax)
data X = ... type X = ... Discriminated union
type X = ... type X = ... Type alias
newtype X = ... type X = X of ... Single-case union
class C a where type I = interface Type class → interface
IO a Async<'a> or Task<'a> Side effects

When Converting Code

  1. Analyze source thoroughly before writing target
  2. Map types first - create type equivalence table
  3. Handle laziness - Haskell is lazy by default, F# is eager
  4. Adopt F# idioms - don't write "Haskell code in F# syntax"
  5. Replace type classes - use interfaces or module functions
  6. Handle edge cases - pattern matching, infinite lists, laziness
  7. Test equivalence - same inputs → same outputs

Type System Mapping

Primitive Types

Haskell F# Notes
Int int 32-bit signed integer
Integer bigint Arbitrary precision (System.Numerics.BigInteger)
Float float32 / single 32-bit floating point
Double float / double 64-bit floating point (default)
Bool bool Direct mapping
Char char Unicode character
String string Unicode string
() unit Unit type

Collection Types

Haskell F# Notes
[a] 'a list Immutable linked list
[a] (infinite) seq<'a> Use sequences for lazy evaluation
(a, b) 'a * 'b Tuple (note: * syntax in types)
(a, b, c) 'a * 'b * 'c Tuple with 3+ elements
Map k v Map<'k,'v> Immutable map
Set a Set<'a> Immutable set
Array a 'a[] or array<'a> Mutable array
Vector a 'a[] Arrays are more common in F#

Composite Types

Haskell F# Notes
data X = C1 | C2 type X = C1 | C2 Discriminated union
data X = C a b type X = C of 'a * 'b Union with data
data X = C { f :: a } type X = { F: 'a } Record type
newtype X = X a type X = X of 'a Single-case discriminated union
type X = a type X = 'a Type alias

Option and Result Types

Haskell F# Notes
Maybe a 'a option Some/None instead of Just/Nothing
Just x Some x Present value
Nothing None Absent value
Either a b Result<'b,'a> Note: parameter order is swapped!
Left err Error err Error case
Right val Ok val Success case

Type Classes → Interfaces/Modules

Haskell F# Strategy
class Eq a where interface IEquatable<'a> or = operator Built-in equality
class Ord a where interface IComparable<'a> or compare Built-in comparison
class Show a where Override ToString() or use sprintf String representation
class Functor f where Module with map function No direct equivalent
class Applicative f where Module with combinators No direct equivalent
class Monad m where Computation expressions Use let! and return

Idiom Translation

Pattern 1: Maybe/Either → Option/Result

Haskell:

findUser :: String -> Maybe User
findUser userId = case lookup userId users of
    Just user -> Just user
    Nothing -> Nothing

validateAge :: Int -> Either String Int
validateAge age
    | age < 0 = Left "Age cannot be negative"
    | age > 150 = Left "Age too high"
    | otherwise = Right age

F#:

let findUser (userId: string) : User option =
    users |> Map.tryFind userId

let validateAge (age: int) : Result<int, string> =
    if age < 0 then
        Error "Age cannot be negative"
    elif age > 150 then
        Error "Age too high"
    else
        Ok age

Why this translation:

  • F#'s Option maps directly to Haskell's Maybe
  • F#'s Result<'T,'Error> maps to Haskell's Either, but parameter order is swapped
  • F# uses Some/None instead of Just/Nothing
  • F# uses Ok/Error instead of Right/Left
  • Pattern matching syntax is similar but uses match ... with

Pattern 2: List Comprehensions

Haskell:

squares :: [Int]
squares = [x^2 | x <- [1..10]]

pythagoras :: [(Int, Int, Int)]
pythagoras = [(a,b,c) | a <- [1..20],
                        b <- [a..20],
                        c <- [b..20],
                        a^2 + b^2 == c^2]

F#:

let squares: int list =
    [ for x in 1..10 -> x * x ]

let pythagoras: (int * int * int) list =
    [ for a in 1..20 do
      for b in a..20 do
      for c in b..20 do
      if a*a + b*b = c*c then
        yield (a, b, c) ]

Why this translation:

  • F# uses [ for x in ... ] instead of Haskell's [ x | ... ]
  • F# uses -> for simple projections, yield for conditional yields
  • F# requires do for multiple generators
  • F# uses = for equality instead of ==

Pattern 3: Higher-Order Functions

Haskell:

result :: [Int] -> Int
result = sum . map (*2) . filter even

addThenDouble :: Int -> Int
addThenDouble = (*2) . (+1)

F#:

let result (xs: int list) : int =
    xs
    |> List.filter (fun x -> x % 2 = 0)
    |> List.map (fun x -> x * 2)
    |> List.sum

let addThenDouble: int -> int =
    (+) 1 >> (*) 2

Why this translation:

  • F# uses |> (pipe forward) instead of . (compose)
  • F# reads left-to-right with |>, Haskell reads right-to-left with .
  • F# uses >> for function composition (same direction as Haskell's .)
  • F# requires explicit lambda syntax fun x -> ... more often

Pattern 4: Pattern Matching and Guards

Haskell:

classify :: Int -> String
classify n
    | n < 0     = "negative"
    | n == 0    = "zero"
    | n < 10    = "small"
    | otherwise = "large"

describeList :: [a] -> String
describeList [] = "empty"
describeList [x] = "singleton"
describeList xs = "longer"

F#:

let classify (n: int) : string =
    match n with
    | n when n < 0 -> "negative"
    | 0 -> "zero"
    | n when n < 10 -> "small"
    | _ -> "large"

let describeList (xs: 'a list) : string =
    match xs with
    | [] -> "empty"
    | [x] -> "singleton"
    | _ -> "longer"

Why this translation:

  • Haskell uses guards (|) in function definitions
  • F# uses match ... with expression with when guards
  • F# can match literals directly without guards
  • Both support pattern matching on list structure

Pattern 5: Recursive Functions

Haskell:

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

-- Tail-recursive
factorial' :: Integer -> Integer
factorial' n = go n 1
  where
    go 0 acc = acc
    go n acc = go (n - 1) (acc * n)

F#:

let rec factorial (n: bigint) : bigint =
    if n = 0I then 1I
    else n * factorial (n - 1I)

// Tail-recursive
let factorial' (n: bigint) : bigint =
    let rec loop n acc =
        if n = 0I then acc
        else loop (n - 1I) (acc * n)
    loop n 1I

Why this translation:

  • F# requires explicit rec keyword for recursive functions
  • F# uses let ... in for local definitions instead of where
  • F# can use pattern matching in function parameters, but if/match is more common
  • Tail recursion optimization works similarly in both languages

Pattern 6: Typeclasses → Interfaces/Modules

Haskell:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

data Color = Red | Green | Blue

instance Eq Color where
    Red == Red = True
    Green == Green = True
    Blue == Blue = True
    _ == _ = False
    x /= y = not (x == y)

F#:

type Color =
    | Red
    | Green
    | Blue

// F# generates equality automatically for discriminated unions
// Manual implementation:
type Color with
    static member op_Equality(a: Color, b: Color) =
        match a, b with
        | Red, Red -> true
        | Green, Green -> true
        | Blue, Blue -> true
        | _ -> false

// Or use structural equality (automatic for DUs)
let color1 = Red
let color2 = Red
let isEqual = (color1 = color2)  // true

Why this translation:

  • F# doesn't have type classes
  • F# discriminated unions have automatic structural equality
  • Custom equality requires overriding Equals or operator overloading
  • For polymorphic behavior, use interfaces or module functions

Pattern 7: Functors and Monads → Computation Expressions

Haskell:

-- Functor usage
result = fmap (+1) (Just 5)  -- Just 6

-- Monad usage (do-notation)
process :: Maybe Int
process = do
    x <- Just 10
    y <- Just 20
    return (x + y)

-- Either monad
validate :: Either String Int
validate = do
    age <- validateAge 25
    score <- validateScore 85
    return (age + score)

F#:

// Functor-like usage
let result = Option.map (fun x -> x + 1) (Some 5)  // Some 6

// Computation expression (option)
let process: int option =
    option {
        let! x = Some 10
        let! y = Some 20
        return x + y
    }

// Result computation expression
let validate: Result<int, string> =
    result {
        let! age = validateAge 25
        let! score = validateScore 85
        return age + score
    }

Why this translation:

  • F# computation expressions are similar to Haskell's do-notation
  • let! in F# ≈ <- in Haskell
  • return works the same way
  • F# requires explicit computation expression builders (option, result, async)
  • Some builders are built-in, others need to be defined or imported

Pattern 8: Lazy Evaluation → Sequences

Haskell:

-- Infinite list (lazy by default)
naturals :: [Integer]
naturals = [1..]

fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

takeFirst10 :: [Integer]
takeFirst10 = take 10 fibs

F#:

// Lazy sequence (must be explicit)
let naturals: seq<bigint> =
    Seq.initInfinite (fun i -> bigint i + 1I)

let fibs: seq<bigint> =
    (0I, 1I)
    |> Seq.unfold (fun (a, b) -> Some(a, (b, a + b)))

let takeFirst10: bigint list =
    fibs |> Seq.take 10 |> Seq.toList

Why this translation:

  • Haskell lists are lazy by default
  • F# lists are eager, sequences (seq<'a>) are lazy
  • F# Seq.unfold replaces recursive definitions
  • Must explicitly convert sequences to lists with Seq.toList
  • F# doesn't support infinite lists natively, use sequences

Pattern 9: Type Families → Generic Types

Haskell:

class Container c where
    type Elem c :: *
    empty :: c
    insert :: Elem c -> c -> c

instance Container [a] where
    type Elem [a] = a
    empty = []
    insert = (:)

F#:

type IContainer<'c, 'elem> =
    abstract member Empty: 'c
    abstract member Insert: 'elem -> 'c -> 'c

type ListContainer<'a>() =
    interface IContainer<'a list, 'a> with
        member _.Empty = []
        member _.Insert elem container = elem :: container

Why this translation:

  • Haskell type families → F# generic interfaces
  • No direct equivalent to associated types
  • Use explicit type parameters
  • Requires more verbose interface definitions

Pattern 10: Module System

Haskell:

-- MyApp/User.hs
module MyApp.User
    ( User(..)
    , createUser
    , validateEmail
    ) where

import Data.Text (Text)
import qualified Data.Map as M

data User = User
    { name :: Text
    , email :: Text
    }

createUser :: Text -> Text -> User
createUser n e = User n e

F#:

// User.fs
module MyApp.User

type User = {
    Name: string
    Email: string
}

let createUser (name: string) (email: string) : User =
    { Name = name; Email = email }

let validateEmail (email: string) : bool =
    email.Contains("@")

Why this translation:

  • F# modules are file-based (one module per file by default)
  • F# uses module Name at top of file
  • No explicit export list in F#; everything is public by default
  • Use internal or private for visibility control
  • F# records use { Field: Type } syntax instead of Haskell's { field :: Type }

Error Handling

Haskell Maybe/Either → F# Option/Result

Haskell uses Maybe for optional values and Either for computations that can fail. F# uses Option and Result respectively.

Haskell F# Notes
Maybe a 'a option Optional values
Just x Some x Present value
Nothing None Absent value
Either a b Result<'b,'a> Parameters swapped!
Left err Error err Error case
Right val Ok val Success case
fromMaybe defaultArg or Option.defaultValue Provide default
maybe Option.fold Fold over option

Basic Error Translation

Haskell:

safeDivide :: Double -> Double -> Maybe Double
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)

parseAge :: String -> Either String Int
parseAge str =
    case reads str of
        [(n, "")] -> if n >= 0
                     then Right n
                     else Left "Age must be positive"
        _ -> Left "Not a valid number"

F#:

let safeDivide (x: float) (y: float) : float option =
    if y = 0.0 then None
    else Some (x / y)

let parseAge (str: string) : Result<int, string> =
    match System.Int32.TryParse(str) with
    | true, n when n >= 0 -> Ok n
    | true, _ -> Error "Age must be positive"
    | false, _ -> Error "Not a valid number"

Chaining Operations

Haskell:

-- Option chaining
getUserEmail :: UserId -> Maybe Email
getUserEmail userId = do
    user <- findUser userId
    return (email user)

-- Or with bind
getUserEmail' :: UserId -> Maybe Email
getUserEmail' userId =
    findUser userId >>= return . email

-- Either chaining
validateUser :: String -> String -> Either String User
validateUser ageStr emailStr = do
    age <- parseAge ageStr
    email <- validateEmail emailStr
    return $ User email age

F#:

// Option chaining with computation expression
let getUserEmail (userId: UserId) : Email option =
    option {
        let! user = findUser userId
        return user.Email
    }

// Or with bind
let getUserEmail' (userId: UserId) : Email option =
    findUser userId
    |> Option.map (fun user -> user.Email)

// Result chaining
let validateUser (ageStr: string) (emailStr: string) : Result<User, string> =
    result {
        let! age = parseAge ageStr
        let! email = validateEmail emailStr
        return { Email = email; Age = age }
    }

Evaluation Strategy

Lazy vs Eager Evaluation

Haskell is lazy by default, while F# is eager by default. This is one of the most significant differences.

Haskell F# Strategy
Lazy by default Eager by default Use seq for laziness
[1..] Seq.initInfinite Infinite sequences
take 10 [1..] Seq.take 10 (Seq.initInfinite id) Take from infinite
let x = expr let x = lazy expr Explicit lazy values
x x.Force() Force lazy evaluation

Converting Lazy Code

Haskell:

-- Infinite list of fibonacci numbers
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

-- Take first 10
first10 :: [Integer]
first10 = take 10 fibs

-- Infinite list of primes (conceptual)
primes :: [Integer]
primes = sieve [2..]
  where
    sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]

F#:

// Lazy sequence of fibonacci numbers
let fibs: seq<bigint> =
    (0I, 1I)
    |> Seq.unfold (fun (a, b) ->
        Some(a, (b, a + b)))

// Take first 10
let first10: bigint list =
    fibs |> Seq.take 10 |> Seq.toList

// Lazy sequence of primes
let primes: seq<bigint> =
    let rec sieve (nums: seq<bigint>) : seq<bigint> = seq {
        let p = Seq.head nums
        yield p
        yield! sieve (nums |> Seq.filter (fun x -> x % p <> 0I))
    }
    sieve (Seq.initInfinite (fun i -> bigint i + 2I))

Explicit Lazy Values

Haskell:

-- Everything is lazy
expensiveComputation :: Int
expensiveComputation = sum [1..1000000]

-- Used like any value
result :: Int
result = if condition then expensiveComputation else 0

F#:

// Must explicitly mark as lazy
let expensiveComputation: Lazy<int> =
    lazy (
        List.sum [1..1000000]
    )

// Must force evaluation
let result: int =
    if condition then expensiveComputation.Force()
    else 0

Concurrency Patterns

Haskell Concurrency → F# Async

Haskell uses lightweight threads and STM, while F# uses async workflows and Task-based async.

Haskell F# Notes
forkIO Async.Start Spawn concurrent computation
MVar MailboxProcessor Message-passing concurrency
STM No direct equivalent Use agents or locks
async library async { } Async computations
Async a Async<'a> Async type

Basic Async Translation

Haskell:

import Control.Concurrent.Async

fetchData :: String -> IO String
fetchData url = do
    threadDelay 1000000
    return $ "Data from " ++ url

main :: IO ()
main = do
    result1 <- async (fetchData "url1")
    result2 <- async (fetchData "url2")
    data1 <- wait result1
    data2 <- wait result2
    putStrLn $ data1 ++ ", " ++ data2

F#:

open System

let fetchData (url: string) : Async<string> = async {
    do! Async.Sleep 1000
    return $"Data from {url}"
}

[<EntryPoint>]
let main argv =
    let result =
        async {
            let! data1 = fetchData "url1"
            let! data2 = fetchData "url2"
            return $"{data1}, {data2}"
        }
        |> Async.RunSynchronously

    printfn "%s" result
    0

Parallel Execution

Haskell:

import Control.Concurrent.Async

fetchAll :: IO ([User], [Order])
fetchAll = do
    (users, orders) <- concurrently fetchUsers fetchOrders
    return (users, orders)

F#:

let fetchAll: Async<User list * Order list> = async {
    let! users, orders =
        Async.Parallel [
            fetchUsers() |> Async.map (fun x -> Choice1Of2 x)
            fetchOrders() |> Async.map (fun x -> Choice2Of2 x)
        ]
        |> Async.map (fun results ->
            // Handle results...
            ([], [])  // Simplified
        )
    return users, orders
}

// Or simpler with tuple:
let fetchAll': Async<User list * Order list> = async {
    let! results =
        (fetchUsers(), fetchOrders())
        ||> Async.Parallel2
    return results
}

Common Pitfalls

1. Confusing Result Parameter Order

Problem: Haskell's Either a b has error first, F#'s Result<'T,'E> has success first.

Example:

-- Haskell: Either ErrorType SuccessType
parseConfig :: String -> Either String Config
parseConfig str = Right config  -- Success is Right
// F#: Result<SuccessType, ErrorType>
let parseConfig (str: string) : Result<Config, string> =
    Ok config  // Success is Ok

Solution: Remember F# swaps the order. When converting:

  • Haskell Either e a → F# Result<a, e>
  • Haskell Left err → F# Error err
  • Haskell Right val → F# Ok val

2. Forgetting Lazy vs Eager Evaluation

Problem: Infinite lists work in Haskell but cause infinite loops in F#.

Example:

-- Haskell: works fine (lazy)
allNumbers = [1..]
firstTen = take 10 allNumbers
// F#: This will hang! (eager)
// let allNumbers = [1..] // Doesn't compile

// Correct: use sequences
let allNumbers = Seq.initInfinite ((+) 1)
let firstTen = allNumbers |> Seq.take 10 |> Seq.toList

Solution: Use seq<'a> for lazy evaluation in F#, not lists.

3. Type Class Constraints Not Translating

Problem: Haskell type class constraints don't have direct F# equivalents.

Example:

-- Haskell: polymorphic with type class
sumAll :: (Num a) => [a] -> a
sumAll = foldr (+) 0
// F#: must be specific or use inline
let sumAll (xs: int list) : int =
    List.fold (+) 0 xs

// Or use inline for polymorphism
let inline sumAll xs =
    List.fold (+) LanguagePrimitives.GenericZero xs

Solution: Use inline functions or make types concrete.

4. Pattern Matching Syntax Differences

Problem: Haskell allows pattern matching in function definitions, F# requires match.

Example:

-- Haskell: patterns in function definition
factorial 0 = 1
factorial n = n * factorial (n - 1)
// F#: must use match or if
let rec factorial (n: int) : int =
    match n with
    | 0 -> 1
    | n -> n * factorial (n - 1)

// Or with if
let rec factorial' (n: int) : int =
    if n = 0 then 1
    else n * factorial' (n - 1)

Solution: Use match ... with for pattern matching in F#.

5. Module Import Differences

Problem: Haskell's qualified imports don't translate directly.

Example:

-- Haskell
import qualified Data.Map as M
import Data.List (sort, nub)

result = M.lookup "key" map
// F#: different syntax
open System.Collections.Generic

// For modules, open at top
// No "qualified" keyword

// Use full paths for disambiguation
let result = Map.tryFind "key" map

Solution: F# uses open for imports, use full module paths for disambiguation.

6. Tuple Syntax Confusion

Problem: Tuple constructor vs type syntax differs.

Example:

-- Haskell: consistent syntax
value :: (Int, String)
value = (42, "hello")
// F#: different syntax for type vs value
let value: int * string =  // Type uses *
    (42, "hello")          // Value uses ,

Solution: Remember F# uses * in types, , in values.

7. No Automatic Currying in All Contexts

Problem: F# functions are curried, but some .NET APIs are not.

Example:

-- Haskell: currying everywhere
add x y = x + y
add5 = add 5  -- Partial application
// F#: currying works for F# functions
let add x y = x + y
let add5 = add 5  // Works

// But .NET methods are not curried
// Math.Max(1, 2)  // Must provide all args
let max1 = Math.Max(1)  // Error!

// Wrap in F# function for currying
let max a b = Math.Max(a, b)
let max1' = max 1  // Works

Solution: Wrap .NET methods in F# functions for partial application.

8. String Type Differences

Problem: Haskell String is [Char], F# string is System.String.

Example:

-- Haskell: String is a list
reverse :: String -> String
reverse = reverse  -- List reverse works on strings

head :: String -> Char
head (c:_) = c

F#:

// F#: string is not a list
let reverse (s: string) : string =
    s.ToCharArray()
    |> Array.rev
    |> System.String

let head (s: string) : char =
    s.[0]  // Index, not pattern matching

Solution: Use string methods and array operations in F#, not list operations.


Tooling

Category Haskell F# Equivalent
Compiler GHC F# compiler (fsc)
Build Tool Cabal, Stack dotnet CLI
Package Manager Cabal, Stack NuGet
REPL GHCi FSI (F# Interactive)
Formatter stylish-haskell, ormolu fantomas
Linter HLint FSharpLint
IDE VS Code + HLS, IntelliJ IDEA VS Code, Visual Studio, Rider
Documentation Haddock XML docs / FSharp.Formatting
Testing HSpec, QuickCheck Expecto, FsCheck

Package Ecosystem Mapping

Purpose Haskell F#
JSON aeson FSharp.Json, System.Text.Json
HTTP Client http-client, req FSharp.Data, HttpClient
Parsing parsec, megaparsec FParsec
CLI optparse-applicative Argu
Async async Built-in async
Testing QuickCheck FsCheck
Web Framework Servant, Scotty, Yesod Giraffe, Saturn, Suave

Examples

Example 1: Simple - List Processing

Before (Haskell):

module ListUtils where

-- Filter and map in one pass
processNumbers :: [Int] -> [Int]
processNumbers = map (*2) . filter even

-- Find first match
findFirst :: (a -> Bool) -> [a] -> Maybe a
findFirst pred [] = Nothing
findFirst pred (x:xs)
    | pred x = Just x
    | otherwise = findFirst pred xs

-- Remove duplicates
unique :: Eq a => [a] -> [a]
unique [] = []
unique (x:xs) = x : unique (filter (/= x) xs)

After (F#):

module ListUtils

// Filter and map in one pass
let processNumbers (xs: int list) : int list =
    xs
    |> List.filter (fun x -> x % 2 = 0)
    |> List.map (fun x -> x * 2)

// Find first match
let findFirst (pred: 'a -> bool) (xs: 'a list) : 'a option =
    List.tryFind pred xs

// Remove duplicates
let unique (xs: 'a list) : 'a list =
    xs |> List.distinct

Example 2: Medium - Tree Data Structure with Traversal

Before (Haskell):

module Tree where

data Tree a = Leaf a
            | Node a (Tree a) (Tree a)
            deriving (Show, Eq)

-- Insert into binary search tree
insert :: Ord a => a -> Tree a -> Tree a
insert x (Leaf y)
    | x < y = Node y (Leaf x) (Leaf y)
    | otherwise = Node y (Leaf y) (Leaf x)
insert x (Node y left right)
    | x < y = Node y (insert x left) right
    | otherwise = Node y left (insert x right)

-- Map over tree
instance Functor Tree where
    fmap f (Leaf x) = Leaf (f x)
    fmap f (Node x left right) =
        Node (f x) (fmap f left) (fmap f right)

-- Fold tree
foldTree :: (a -> b -> b -> b) -> (a -> b) -> Tree a -> b
foldTree node leaf (Leaf x) = leaf x
foldTree node leaf (Node x left right) =
    node x (foldTree node leaf left) (foldTree node leaf right)

-- Inorder traversal
inorder :: Tree a -> [a]
inorder (Leaf x) = [x]
inorder (Node x left right) =
    inorder left ++ [x] ++ inorder right

After (F#):

module Tree

type Tree<'a> =
    | Leaf of 'a
    | Node of 'a * Tree<'a> * Tree<'a>

// Insert into binary search tree
let rec insert (x: 'a) (tree: Tree<'a>) : Tree<'a> =
    match tree with
    | Leaf y ->
        if x < y then Node(y, Leaf x, Leaf y)
        else Node(y, Leaf y, Leaf x)
    | Node(y, left, right) ->
        if x < y then Node(y, insert x left, right)
        else Node(y, left, insert x right)

// Map over tree
let rec map (f: 'a -> 'b) (tree: Tree<'a>) : Tree<'b> =
    match tree with
    | Leaf x -> Leaf (f x)
    | Node(x, left, right) ->
        Node(f x, map f left, map f right)

// Fold tree
let rec fold (nodeF: 'a -> 'b -> 'b -> 'b) (leafF: 'a -> 'b) (tree: Tree<'a>) : 'b =
    match tree with
    | Leaf x -> leafF x
    | Node(x, left, right) ->
        nodeF x (fold nodeF leafF left) (fold nodeF leafF right)

// Inorder traversal
let rec inorder (tree: Tree<'a>) : 'a list =
    match tree with
    | Leaf x -> [x]
    | Node(x, left, right) ->
        inorder left @ [x] @ inorder right

Example 3: Complex - Parser Combinator

Before (Haskell):

{-# LANGUAGE ApplicativeDo #-}

module Parser where

import Control.Applicative
import Data.Char

newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }

instance Functor Parser where
    fmap f (Parser p) = Parser $ \input -> do
        (x, rest) <- p input
        return (f x, rest)

instance Applicative Parser where
    pure x = Parser $ \input -> Just (x, input)
    Parser pf <*> Parser px = Parser $ \input -> do
        (f, rest1) <- pf input
        (x, rest2) <- px rest1
        return (f x, rest2)

instance Alternative Parser where
    empty = Parser $ const Nothing
    Parser p1 <|> Parser p2 = Parser $ \input ->
        p1 input <|> p2 input

-- Basic parsers
satisfy :: (Char -> Bool) -> Parser Char
satisfy pred = Parser $ \input ->
    case input of
        [] -> Nothing
        (x:xs) -> if pred x then Just (x, xs) else Nothing

char :: Char -> Parser Char
char c = satisfy (== c)

string :: String -> Parser String
string [] = pure []
string (c:cs) = do
    char c
    string cs
    pure (c:cs)

-- JSON-like parser
data Value = VNull
           | VBool Bool
           | VNumber Double
           | VString String
           | VArray [Value]
           deriving (Show, Eq)

parseNull :: Parser Value
parseNull = string "null" *> pure VNull

parseBool :: Parser Value
parseBool = (string "true" *> pure (VBool True))
        <|> (string "false" *> pure (VBool False))

parseNumber :: Parser Value
parseNumber = VNumber . read <$> some (satisfy isDigit)

parseString :: Parser Value
parseString = VString <$> (char '"' *> many (satisfy (/= '"')) <* char '"')

parseValue :: Parser Value
parseValue = parseNull <|> parseBool <|> parseNumber <|> parseString

After (F#):

module Parser

type Parser<'a> = Parser of (string -> ('a * string) option)

let runParser (Parser p) input = p input

// Functor (map)
let map (f: 'a -> 'b) (Parser p: Parser<'a>) : Parser<'b> =
    Parser (fun input ->
        match p input with
        | Some(x, rest) -> Some(f x, rest)
        | None -> None)

// Applicative (pure and apply)
let pure' (x: 'a) : Parser<'a> =
    Parser (fun input -> Some(x, input))

let apply (Parser pf: Parser<'a -> 'b>) (Parser px: Parser<'a>) : Parser<'b> =
    Parser (fun input ->
        match pf input with
        | Some(f, rest1) ->
            match px rest1 with
            | Some(x, rest2) -> Some(f x, rest2)
            | None -> None
        | None -> None)

// Alternative (empty and or)
let empty<'a> : Parser<'a> =
    Parser (fun _ -> None)

let orElse (Parser p1: Parser<'a>) (Parser p2: Parser<'a>) : Parser<'a> =
    Parser (fun input ->
        match p1 input with
        | Some result -> Some result
        | None -> p2 input)

// Basic parsers
let satisfy (pred: char -> bool) : Parser<char> =
    Parser (fun input ->
        match Seq.tryHead input with
        | Some c when pred c ->
            Some(c, input.Substring(1))
        | _ -> None)

let char' (c: char) : Parser<char> =
    satisfy ((=) c)

let rec string' (s: string) : Parser<string> =
    if s.Length = 0 then
        pure' ""
    else
        Parser (fun input ->
            if input.StartsWith(s) then
                Some(s, input.Substring(s.Length))
            else
                None)

// JSON-like parser
type Value =
    | VNull
    | VBool of bool
    | VNumber of float
    | VString of string
    | VArray of Value list

let parseNull: Parser<Value> =
    string' "null"
    |> map (fun _ -> VNull)

let parseBool: Parser<Value> =
    orElse
        (string' "true" |> map (fun _ -> VBool true))
        (string' "false" |> map (fun _ -> VBool false))

let parseNumber: Parser<Value> =
    Parser (fun input ->
        let digits = input |> Seq.takeWhile System.Char.IsDigit |> Seq.toArray
        if digits.Length > 0 then
            let numStr = System.String(digits)
            let num = float numStr
            Some(VNumber num, input.Substring(digits.Length))
        else
            None)

let parseString: Parser<Value> =
    Parser (fun input ->
        if input.StartsWith("\"") then
            let endIndex = input.IndexOf('"', 1)
            if endIndex > 0 then
                let content = input.Substring(1, endIndex - 1)
                Some(VString content, input.Substring(endIndex + 1))
            else
                None
        else
            None)

let parseValue: Parser<Value> =
    parseNull
    |> orElse parseBool
    |> orElse parseNumber
    |> orElse parseString

See Also

For more examples and patterns, see:

  • meta-convert-dev - Foundational conversion patterns (APTV workflow, testing strategies)
  • lang-haskell-dev - Haskell development patterns
  • lang-fsharp-dev - F# development patterns
  • patterns-concurrency-dev - Async, threads, STM across languages
  • patterns-serialization-dev - JSON, validation across languages
  • patterns-metaprogramming-dev - Template Haskell → Type Providers