Skip to content
Back to skills

Convert Haskell Fsharp

ASecurity

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.

  • 8 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added February 8, 2026
developmentgonodeexpresstestingrefactoringgitapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add aRustyDev/ai --skill convert-haskell-fsharp --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convert Haskell Fsharp?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Convert Haskell Fsharp
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/arustydev-convert-haskell-fsharp/badge)](https://www.skillsdirectory.com/skills/arustydev-convert-haskell-fsharp)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
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:**
```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#:**
```fsharp
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:**
```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#:**
```fsharp
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:**
```haskell
result :: [Int] -> Int
result = sum . map (*2) . filter even

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

**F#:**
```fsharp
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:**
```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#:**
```fsharp
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:**
```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#:**
```fsharp
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:**
```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#:**
```fsharp
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:**
```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#:**
```fsharp
// 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:**
```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#:**
```fsharp
// 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:**
```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#:**
```fsharp
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:**
```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#:**
```fsharp
// 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:**
```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#:**
```fsharp
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:**
```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#:**
```fsharp
// 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:**
```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#:**
```fsharp
// 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:**
```haskell
-- Everything is lazy
expensiveComputation :: Int
expensiveComputation = sum [1..1000000]

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

**F#:**
```fsharp
// 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:**
```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#:**
```fsharp
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:**
```haskell
import Control.Concurrent.Async

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

**F#:**
```fsharp
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
-- Haskell: Either ErrorType SuccessType
parseConfig :: String -> Either String Config
parseConfig str = Right config  -- Success is Right
```

```fsharp
// 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
-- Haskell: works fine (lazy)
allNumbers = [1..]
firstTen = take 10 allNumbers
```

```fsharp
// 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
-- Haskell: polymorphic with type class
sumAll :: (Num a) => [a] -> a
sumAll = foldr (+) 0
```

```fsharp
// 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
-- Haskell: patterns in function definition
factorial 0 = 1
factorial n = n * factorial (n - 1)
```

```fsharp
// 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
-- Haskell
import qualified Data.Map as M
import Data.List (sort, nub)

result = M.lookup "key" map
```

```fsharp
// 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
-- Haskell: consistent syntax
value :: (Int, String)
value = (42, "hello")
```

```fsharp
// 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
-- Haskell: currying everywhere
add x y = x + y
add5 = add 5  -- Partial application
```

```fsharp
// 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
-- Haskell: String is a list
reverse :: String -> String
reverse = reverse  -- List reverse works on strings

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

**F#:**
```fsharp
// 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):**
```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#):**
```fsharp
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):**
```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#):**
```fsharp
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):**
```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#):**
```fsharp
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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.