スキル一覧に戻る
aRustyDev

convert-haskell-clojure

by aRustyDev

AI Contexts, Commands, Plugins, Docs, Examples, Specs, Etc

1🍴 1📅 2026年1月16日
GitHubで見るManusで実行

SKILL.md


name: convert-haskell-clojure description: Convert Haskell code to idiomatic Clojure. Use when migrating Haskell projects to Clojure, translating pure functional patterns to practical functional JVM programming, or refactoring Haskell codebases to leverage REPL-driven development and dynamic typing. Extends meta-convert-dev with Haskell-to-Clojure specific patterns.

Convert Haskell to Clojure

Convert Haskell code to idiomatic Clojure. This skill extends meta-convert-dev with Haskell-to-Clojure specific type mappings, idiom translations, and transformation strategies for moving from pure lazy functional programming with static types to practical dynamic functional programming on the JVM.

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 static types (HM) → Clojure dynamic types
  • Idiom translations: Type classes → protocols, monads → explicit threading, lazy → lazy seqs
  • Error handling: Maybe/Either → nil/exceptions or tagged maps
  • Concurrency patterns: STM/async → atoms/refs/agents, parallel → pmap/reducers
  • Evaluation strategy: Lazy (default) → strict with explicit lazy seqs
  • REPL workflow: GHCi → Clojure REPL-driven development
  • Effects: IO monad → effects anywhere (with discipline)

This Skill Does NOT Cover

  • General conversion methodology - see meta-convert-dev
  • Haskell language fundamentals - see lang-haskell-dev
  • Clojure language fundamentals - see lang-clojure-dev
  • Reverse conversion (Clojure → Haskell) - see convert-clojure-haskell
  • Advanced type-level programming - Template Haskell, GADTs, Type Families have no direct equivalent

Quick Reference

HaskellClojureNotes
String or TextStringClojure strings are Java strings
Int / IntegerLong / BigIntegerClojure uses Long by default
DoubleDoubleJava Double
BoolBooleantrue / false
NothingnilRepresents absence
Just xxValue present (check with some?)
[a][] or (list)Vector or list
Map k v{}Hash map
Set a#{}Hash set
data T = ...(defrecord T ...)Named types
newtypeNo equivalentUse plain value or reify
type(def T ...)Type alias
f :: a -> b(defn f [a] ...)Function (no type signature)
\x -> ...(fn [x] ...) or #(...)Lambda
f . g(comp f g)Function composition (reversed order)
fmapmapTransform in context
>>=mapcat or explicit chainingMonadic bind
do notationlet or threading macrosSequential operations
Maybe aValue or nilOptional values
Either e a{:ok val} / {:error e}Result type pattern
IO aNo equivalentEffects anywhere
Type classesProtocolsPolymorphism
STMRefs with dosyncSoftware transactional memory
asyncfuture / pmapAsync computation

When Converting Code

  1. Remove type signatures - Clojure is dynamically typed
  2. Make laziness explicit - Haskell lazy by default, Clojure strict by default
  3. Replace monads with idioms - Maybe → nil checks, Either → tagged maps, IO → direct effects
  4. Convert type classes to protocols - Or use multimethods for dynamic dispatch
  5. Embrace REPL workflow - Test incrementally, explore data interactively
  6. Handle purity boundaries - No IO monad; effects can happen anywhere (maintain discipline)
  7. Test equivalence - Property-based testing with test.check

Type System Mapping

Primitive Types

HaskellClojureNotes
IntLong64-bit signed integer (default)
IntegerBigIntegerArbitrary precision (use N suffix: 42N)
FloatFloat32-bit float (rare)
DoubleDouble64-bit float (default: 3.14)
BoolBooleantrue / false
CharCharacterSingle character (rare, use strings)
StringStringJava String (immutable)
TextStringBoth map to Java String
() (unit)nilVoid/no meaningful return

Collection Types

HaskellClojureNotes
[a] (list)(list 1 2 3) or '(1 2 3)Linked list (rarely used)
[a][1 2 3]Preferred: Vector (indexed access)
Map k v{:key val}Hash map (fast lookup)
Set a#{1 2 3}Hash set (unique elements)
(a, b)[a b]Tuple as vector
(a, b, c)[a b c]Multi-element tuple
Infinite listLazy seq: (iterate inc 0)Must be explicit

Composite Types

HaskellClojureNotes
data User = User { name :: String, age :: Int }(defrecord User [name age])Named record type
type UserId = Int(def UserId Long)Type alias (documentation only)
newtype Email = Email StringPlain string or custom validationNo compile-time wrapper
data Color = Red | Green | Blue#{:red :green :blue} or specEnum as keyword set
data Result a = Ok a | Err String{:ok val} or {:error msg}Tagged map pattern

Optional and Error Types

Haskell:

-- Maybe for optional values
findUser :: UserId -> Maybe User
findUser uid = Map.lookup uid users

-- Either for errors
divide :: Double -> Double -> Either String Double
divide _ 0 = Left "Division by zero"
divide a b = Right (a / b)

Clojure:

;; nil for optional values
(defn find-user [user-id]
  (get users user-id))  ; Returns nil if not found

;; Tagged maps for errors (idiomatic)
(defn divide [a b]
  (if (zero? b)
    {:error "Division by zero"}
    {:ok (/ a b)}))

;; Or throw exceptions
(defn divide! [a b]
  (when (zero? b)
    (throw (ex-info "Division by zero" {:a a :b b})))
  (/ a b))

Why this translation:

  • Haskell's Maybe forces explicit handling; Clojure's nil is pervasive (less safe)
  • Either → tagged maps is idiomatic Clojure for recoverable errors
  • Exceptions are acceptable in Clojure for exceptional cases
  • Use ex-info for exceptions with data

Idiom Translation

Pattern: Type Classes → Protocols

Haskell:

-- Type class for serialization
class Serializable a where
  serialize :: a -> ByteString
  deserialize :: ByteString -> Maybe a

-- Instance for User
instance Serializable User where
  serialize (User name age) = encode (name, age)
  deserialize bs = case decode bs of
    Just (name, age) -> Just (User name age)
    Nothing -> Nothing

-- Use
let json = serialize user

Clojure:

;; Protocol for serialization
(defprotocol Serializable
  (serialize [this])
  (deserialize [this bytes]))

;; Extend for User record
(defrecord User [name age])

(extend-protocol Serializable
  User
  (serialize [user]
    (json/generate-string user))
  (deserialize [_ bytes]
    (json/parse-string bytes true)))

;; Or inline with defrecord
(defrecord User [name age]
  Serializable
  (serialize [this]
    (json/generate-string this)))

;; Use
(serialize user)

Why this translation:

  • Type classes define polymorphic interfaces; protocols do the same in Clojure
  • Protocols are more dynamic: can extend existing types at runtime
  • No compile-time type checking in Clojure
  • extend-protocol extends multiple types at once

Pattern: Monadic Do-Notation → Threading Macros

Haskell:

-- Maybe monad chaining
processUser :: UserId -> Maybe Result
processUser uid = do
  user <- findUser uid
  profile <- getProfile user
  settings <- getSettings profile
  return (computeResult settings)

-- Or with bind
processUser' uid =
  findUser uid >>= getProfile >>= getSettings >>= return . computeResult

Clojure:

;; Using some-> (thread-first, stop on nil)
(defn process-user [user-id]
  (some-> user-id
          find-user
          get-profile
          get-settings
          compute-result))

;; Or with when-let for explicit nil handling
(defn process-user [user-id]
  (when-let [user (find-user user-id)]
    (when-let [profile (get-profile user)]
      (when-let [settings (get-settings profile)]
        (compute-result settings)))))

;; Or using monads library (cats, algo.monads)
(require '[cats.core :as m])
(require '[cats.monad.maybe :as maybe])

(defn process-user [user-id]
  (m/mlet [user (maybe/maybe (find-user user-id))
           profile (maybe/maybe (get-profile user))
           settings (maybe/maybe (get-settings profile))]
    (m/return (compute-result settings))))

Why this translation:

  • some-> is idiomatic Clojure for Maybe-like chaining
  • Stops threading at first nil
  • For complex error handling, tagged maps or exceptions are more common
  • Monads library exists but not idiomatic for most Clojure code

Pattern: Function Composition

Haskell:

-- Right-to-left composition
processData :: [Int] -> Int
processData = sum . filter even . map (*2)

-- Or with $
processData' xs = sum $ filter even $ map (*2) xs

-- Or with &
import Data.Function ((&))
processData'' xs =
  xs
  & map (*2)
  & filter even
  & sum

Clojure:

;; Thread-last (->>), left-to-right
(defn process-data [xs]
  (->> xs
       (map #(* % 2))
       (filter even?)
       (reduce +)))

;; Or comp (right-to-left like Haskell)
(def process-data
  (comp
    #(reduce + %)
    #(filter even? %)
    #(map (fn [x] (* x 2)) %)))

;; But ->> is more idiomatic

Why this translation:

  • Haskell's . composes right-to-left: (f . g . h) x = f (g (h x))
  • Clojure's comp also composes right-to-left
  • But ->> (thread-last) is more idiomatic and reads left-to-right
  • Use ->> for data transformations

Pattern: Pattern Matching

Haskell:

-- Pattern matching on ADT
data Shape = Circle Double
           | Rectangle Double Double
           | Triangle Double Double Double

area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle a b c) =
  let s = (a + b + c) / 2
  in sqrt (s * (s - a) * (s - b) * (s - c))

-- Pattern matching on lists
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

Clojure:

;; Use maps with :type key for tagged unions
(defn area [shape]
  (case (:type shape)
    :circle (let [{:keys [radius]} shape]
              (* Math/PI radius radius))
    :rectangle (let [{:keys [width height]} shape]
                 (* width height))
    :triangle (let [{:keys [a b c]} shape
                    s (/ (+ a b c) 2)]
                (Math/sqrt (* s (- s a) (- s b) (- s c))))))

;; Or use multimethods for polymorphism
(defmulti area :type)

(defmethod area :circle [{:keys [radius]}]
  (* Math/PI radius radius))

(defmethod area :rectangle [{:keys [width height]}]
  (* width height))

(defmethod area :triangle [{:keys [a b c]}]
  (let [s (/ (+ a b c) 2)]
    (Math/sqrt (* s (- s a) (- s b) (- s c)))))

;; Pattern matching on lists
(defn sum-list [xs]
  (if (empty? xs)
    0
    (+ (first xs) (sum-list (rest xs)))))

;; Or idiomatic reduce
(defn sum-list [xs]
  (reduce + 0 xs))

Why this translation:

  • Haskell has first-class ADTs; Clojure uses maps with :type keys
  • case on type tag for simple dispatch
  • Multimethods for extensible polymorphism
  • Pattern matching on lists uses first/rest instead of :
  • Prefer higher-order functions (reduce, map) over explicit recursion

Pattern: Lazy Evaluation

Haskell:

-- Lazy by default
naturals :: [Integer]
naturals = [0..]  -- Infinite list, no problem

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

-- Take only what you need
take 10 fibs  -- [0,1,1,2,3,5,8,13,21,34]

Clojure:

;; Strict by default, lazy sequences explicit
(def naturals (iterate inc 0))  ; Lazy seq

(def fibs
  (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

;; Or using lazy-seq
(defn fib-seq
  ([] (fib-seq 0 1))
  ([a b]
   (lazy-seq (cons a (fib-seq b (+ a b))))))

(take 10 fibs)  ; (0 1 1 2 3 5 8 13 21 34)

;; Force realization with doall
(doall (take 10 fibs))  ; Realizes all elements

Why this translation:

  • Haskell: lazy by default, force with seq / deepseq / bang patterns
  • Clojure: strict by default, lazy with lazy-seq, iterate, repeat, cycle
  • Use iterate for infinite sequences
  • Use take to consume only what's needed
  • doall forces realization (opposite of Haskell's forcing strictness)

Pattern: List Comprehensions

Haskell:

-- List comprehension with guards
pythagorean :: Int -> [(Int, Int, Int)]
pythagorean n = [(a, b, c) | a <- [1..n],
                             b <- [a..n],
                             c <- [b..n],
                             a^2 + b^2 == c^2]

Clojure:

;; for comprehension
(defn pythagorean [n]
  (for [a (range 1 (inc n))
        b (range a (inc n))
        c (range b (inc n))
        :when (= (+ (* a a) (* b b)) (* c c))]
    [a b c]))

Why this translation:

  • Syntax is very similar
  • Haskell uses |, Clojure uses :when for filters
  • Generators bind with <- in Haskell, bind directly in Clojure
  • Both support multiple generators and filters

Error Handling

Maybe → Nil

Haskell:

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x

-- Use with pattern matching
case safeHead xs of
  Nothing -> defaultValue
  Just x -> processValue x

-- Or with maybe function
maybe defaultValue processValue (safeHead xs)

Clojure:

(defn safe-head [xs]
  (first xs))  ; Returns nil for empty

;; Use with if-let
(if-let [x (safe-head xs)]
  (process-value x)
  default-value)

;; Or with some-> (nil-safe threading)
(some-> xs safe-head process-value)

;; Or with or for default
(or (safe-head xs) default-value)

Why this translation:

  • Haskell forces explicit handling with Maybe
  • Clojure uses nil pervasively (less safe but more convenient)
  • if-let, when-let, some-> provide nil-safe operations
  • Use or for default values

Either → Tagged Maps or Exceptions

Haskell:

parseAge :: String -> Either String Int
parseAge s = case reads s of
  [(n, "")] | n >= 0 -> Right n
  _ -> Left "Invalid age"

-- Chain with do-notation
validateUser :: UserData -> Either String User
validateUser userData = do
  age <- parseAge (ageString userData)
  email <- validateEmail (emailString userData)
  return (User (userName userData) age email)

Clojure:

;; Tagged maps (idiomatic for recoverable errors)
(defn parse-age [s]
  (try
    (let [n (Long/parseLong s)]
      (if (>= n 0)
        {:ok n}
        {:error "Age must be non-negative"}))
    (catch NumberFormatException _
      {:error "Not a valid number"})))

;; Chain with helper
(defn ok? [result]
  (contains? result :ok))

(defn validate-user [user-data]
  (let [age-result (parse-age (:age user-data))]
    (if (ok? age-result)
      (let [email-result (validate-email (:email user-data))]
        (if (ok? email-result)
          {:ok {:name (:name user-data)
                :age (:ok age-result)
                :email (:ok email-result)}}
          email-result))
      age-result)))

;; Or exceptions (idiomatic for unrecoverable errors)
(defn parse-age! [s]
  (let [n (Long/parseLong s)]  ; Throws on invalid
    (when (< n 0)
      (throw (ex-info "Age must be non-negative" {:age n})))
    n))

Why this translation:

  • Either forces error handling; Clojure uses tagged maps or exceptions
  • Tagged maps {:ok val} / {:error msg} are common for recoverable errors
  • Exceptions are acceptable for programming errors or exceptional conditions
  • No monadic chaining; use explicit conditionals or helper macros

Concurrency Patterns

STM → Refs with dosync

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)

-- Run transaction
main = do
  account1 <- newTVarIO 1000
  account2 <- newTVarIO 0
  atomically $ transfer account1 account2 500

Clojure:

;; Refs for coordinated, synchronous updates
(def account1 (ref 1000))
(def account2 (ref 0))

(defn transfer [from to amount]
  (dosync
    (let [from-balance @from]
      (when (< from-balance amount)
        (throw (ex-info "Insufficient funds" {})))
      (alter from - amount)
      (alter to + amount))))

;; Use
(transfer account1 account2 500)
;; Or with commute for non-order-dependent updates
(dosync
  (commute account1 - 500)
  (commute account2 + 500))

Why this translation:

  • Both use software transactional memory
  • Haskell: TVar with atomically, readTVar, modifyTVar, retry
  • Clojure: ref with dosync, @ (deref), alter, commute
  • Haskell's retry blocks until condition met; Clojure uses explicit checks

Async → Future / Pmap

Haskell:

import Control.Concurrent.Async

main = do
  (result1, result2) <- concurrently
    (fetchUrl "http://example.com/1")
    (fetchUrl "http://example.com/2")
  print (result1, result2)

-- Race: first to complete wins
winner <- race
  (fetchFromServer1 key)
  (fetchFromServer2 key)

Clojure:

;; future for async execution
(let [result1 (future (fetch-url "http://example.com/1"))
      result2 (future (fetch-url "http://example.com/2"))]
  (println [@result1 @result2]))  ; @ blocks until ready

;; pmap for parallel map
(def results
  (pmap fetch-url ["http://example.com/1"
                   "http://example.com/2"]))

;; No built-in race; use promises
(defn race [& fns]
  (let [p (promise)]
    (doseq [f fns]
      (future (deliver p (f))))
    @p))

(race #(fetch-from-server1 key)
      #(fetch-from-server2 key))

Why this translation:

  • Haskell async library → Clojure future
  • concurrently → spawn futures, deref all
  • race → promise with multiple futures (first delivery wins)
  • mapConcurrentlypmap (parallel map)

Metaprogramming

Template Haskell → Macros

Haskell:

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH

-- Generate function at compile time
$(do
    let name = mkName "add5"
    let body = [| \x -> x + 5 |]
    [d| $(varP name) = $body |]
 )

-- Use
result = add5 10  -- 15

Clojure:

;; Macros expand at compile time
(defmacro add-n [n]
  `(fn [x#] (+ x# ~n)))

(def add5 (add-n 5))
(add5 10)  ; => 15

;; Or simpler: just generate code
(defmacro defadder [name n]
  `(defn ~name [x#]
     (+ x# ~n)))

(defadder add5 5)
(add5 10)  ; => 15

Why this translation:

  • Both provide compile-time code generation
  • Template Haskell is more powerful but complex
  • Clojure macros are simpler, more accessible
  • Quote/unquote syntax: ` (quote), ~ (unquote), ~@ (unquote-splice)
  • Auto-gensym with #: x# generates unique symbol

Serialization

Aeson → Cheshire/Transit

Haskell:

{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson
import GHC.Generics

data User = User
  { name :: Text
  , email :: Text
  , age :: Int
  } deriving (Generic, Show)

instance FromJSON User
instance ToJSON User

-- Encode/decode
encodeUser :: User -> ByteString
encodeUser = encode

decodeUser :: ByteString -> Maybe User
decodeUser = decode

Clojure:

;; Using Cheshire for JSON
(require '[cheshire.core :as json])

;; Records for structured data
(defrecord User [name email age])

;; Encode
(defn encode-user [user]
  (json/generate-string user))

;; Decode
(defn decode-user [json-str]
  (json/parse-string json-str true))  ; true = keywordize keys

;; Or use Transit for Clojure types
(require '[cognitect.transit :as transit])
(import '[java.io ByteArrayOutputStream ByteArrayInputStream])

(defn to-transit [data]
  (let [out (ByteArrayOutputStream.)]
    (transit/write (transit/writer out :json) data)
    (.toString out)))

(defn from-transit [s]
  (transit/read
    (transit/reader
      (ByteArrayInputStream. (.getBytes s))
      :json)))

Why this translation:

  • Haskell Aeson uses Generic deriving; Clojure uses runtime serialization
  • Cheshire for JSON (most common)
  • Transit preserves Clojure data types (keywords, sets, etc.)
  • No compile-time validation in Clojure

Build and Dependencies

Cabal/Stack → Leiningen/tools.deps

Haskell (Cabal):

name:          my-app
version:       0.1.0.0
build-depends: base >= 4.14 && < 5
             , text >= 1.2
             , aeson >= 2.0
             , containers

library
  exposed-modules: MyApp
  hs-source-dirs:  src

Haskell (Stack):

resolver: lts-21.0
packages: [.]
extra-deps:
  - some-package-1.0.0

Clojure (Leiningen):

(defproject my-app "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [cheshire "5.12.0"]
                 [org.clojure/data.json "2.4.0"]])

Clojure (tools.deps):

{:deps
 {org.clojure/clojure {:mvn/version "1.11.1"}
  cheshire/cheshire {:mvn/version "5.12.0"}}}

Why this translation:

  • Cabal/Stack manage packages and versions; Leiningen/tools.deps do the same
  • Dependencies from Maven Central (Clojure) vs Hackage/Stackage (Haskell)
  • Both support local dependencies and git dependencies

Testing

QuickCheck → test.check

Haskell:

import Test.QuickCheck

-- Property: reversing twice gives original
prop_reverse_involutive :: [Int] -> Bool
prop_reverse_involutive xs = reverse (reverse xs) == xs

-- Property: sorted list is sorted
prop_sort_sorted :: [Int] -> Bool
prop_sort_sorted xs = isSorted (sort xs)

-- Run
main = do
  quickCheck prop_reverse_involutive
  quickCheck prop_sort_sorted

Clojure:

(ns myapp.props-test
  (:require [clojure.test :refer [deftest is]]
            [clojure.test.check :as tc]
            [clojure.test.check.generators :as gen]
            [clojure.test.check.properties :as prop]
            [clojure.test.check.clojure-test :refer [defspec]]))

;; Property: reversing twice gives original
(defspec reverse-involutive 100
  (prop/for-all [v (gen/vector gen/small-integer)]
    (= v (vec (reverse (reverse v))))))

;; Property: sorted output
(defspec sort-produces-sorted 100
  (prop/for-all [v (gen/vector gen/small-integer)]
    (let [sorted (sort v)]
      (every? (fn [[a b]] (<= a b))
              (partition 2 1 sorted)))))

Why this translation:

  • Both are property-based testing libraries
  • QuickCheck → test.check (same concepts)
  • quickCheckdefspec or tc/quick-check
  • Arbitrary generators → gen/ generators
  • forAllprop/for-all

Common Pitfalls

1. Assuming Static Type Safety

;; ❌ No compile-time type errors
(defn add [a b]
  (+ a b))

(add "hello" "world")  ; Runtime error!

Fix: Use spec for runtime validation

(require '[clojure.spec.alpha :as s])

(s/def ::number number?)
(defn add [a b]
  {:pre [(s/valid? ::number a) (s/valid? ::number b)]}
  (+ a b))

2. Not Handling Nil Explicitly

;; ❌ Nil is not checked at compile time
(defn process [data]
  (.toUpperCase (:name data)))  ; NullPointerException if :name is nil

Fix: Use nil-safe operations

(defn process [data]
  (some-> data :name .toUpperCase))

;; Or explicit check
(defn process [data]
  (when-let [name (:name data)]
    (.toUpperCase name)))

3. Forgetting Lazy Evaluation Differences

;; ❌ This realizes the entire sequence multiple times
(let [nums (map expensive-fn (range 1000))]
  (+ (count nums) (first nums) (last nums)))

Fix: Force realization once

(let [nums (vec (map expensive-fn (range 1000)))]
  (+ (count nums) (first nums) (last nums)))

4. Over-using Exceptions vs. Tagged Maps

;; ❌ Exceptions for control flow
(try
  (divide a b)
  (catch ArithmeticException e
    :division-by-zero))

Fix: Use tagged maps for expected errors

(let [result (divide a b)]
  (if (:error result)
    (handle-error result)
    (handle-success (:ok result))))

5. Not Leveraging REPL-Driven Development

;; ❌ Writing entire function without testing
(defn complex-algorithm [data]
  ;; 50 lines of code
  )

Fix: Build incrementally in REPL

;; In REPL:
(def sample-data {...})

;; Step 1
(def step1 (parse-data sample-data))
;; Inspect step1

;; Step 2
(def step2 (transform step1))
;; Inspect step2

;; Combine into function after validation
(defn complex-algorithm [data]
  (-> data parse-data transform))

Tooling

CategoryHaskellClojureNotes
Build toolCabal, StackLeiningen, tools.depsPackage management
REPLGHCilein repl, cljInteractive development
Package registryHackage, StackageClojars, Maven CentralDependency sources
TestingHSpec, QuickCheckclojure.test, test.checkUnit + property-based
LintingHLintclj-kondo, eastwoodStatic analysis
FormatterOrmolu, Brittanycljfmt, zprintCode formatting
Doc generationHaddockCodoxAPI documentation

Examples

Example 1: Simple - Function with Pattern Matching

Before (Haskell):

-- Factorial with pattern matching
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

After (Clojure):

;; Factorial with cond
(defn factorial [n]
  (if (zero? n)
    1
    (* n (factorial (dec n)))))

;; Or with recur for tail recursion
(defn factorial [n]
  (loop [n n acc 1]
    (if (zero? n)
      acc
      (recur (dec n) (* acc n)))))

Example 2: Medium - Map Transformation with Maybe

Before (Haskell):

import Data.Maybe (mapMaybe)
import qualified Data.Map as Map

-- Extract emails from user map, filtering out Nothing
getEmails :: Map.Map UserId User -> [Email]
getEmails users =
  mapMaybe (userEmail . snd) (Map.toList users)

userEmail :: User -> Maybe Email
userEmail (User _ email _) = email

After (Clojure):

;; Extract emails, filtering out nil
(defn get-emails [users]
  (->> users
       vals
       (map :email)
       (filter some?)))

;; Or with keep (map + filter non-nil in one step)
(defn get-emails [users]
  (keep :email (vals users)))

Example 3: Complex - Concurrent Processing with STM

Before (Haskell):

import Control.Concurrent.STM
import Control.Concurrent.Async
import qualified Data.Map as Map

type Cache = TVar (Map.Map Key Value)

-- Concurrent cache operations
updateCache :: Cache -> Key -> Value -> STM ()
updateCache cache key value = do
  m <- readTVar cache
  writeTVar cache (Map.insert key value m)

lookupCache :: Cache -> Key -> STM (Maybe Value)
lookupCache cache key = do
  m <- readTVar cache
  return (Map.lookup key m)

-- Process items concurrently and update cache
processItems :: Cache -> [Item] -> IO ()
processItems cache items = do
  results <- mapConcurrently processItem items
  atomically $ mapM_ (uncurry (updateCache cache)) results

processItem :: Item -> IO (Key, Value)
processItem item = do
  value <- expensiveComputation item
  return (itemKey item, value)

After (Clojure):

;; Using refs for coordinated updates
(def cache (ref {}))

;; Concurrent cache operations
(defn update-cache! [cache key value]
  (dosync
    (alter cache assoc key value)))

(defn lookup-cache [cache key]
  (dosync
    (get @cache key)))

;; Process items concurrently and update cache
(defn process-items! [cache items]
  (let [results (pmap process-item items)]
    (dosync
      (doseq [[k v] results]
        (alter cache assoc k v)))))

(defn process-item [item]
  (let [value (expensive-computation item)]
    [(:key item) value]))

;; Or using atoms for independent updates (simpler)
(def cache-atom (atom {}))

(defn update-cache-atom! [cache key value]
  (swap! cache assoc key value))

(defn process-items-atom! [cache items]
  (doseq [item (pmap process-item items)]
    (let [[k v] item]
      (swap! cache assoc k v))))

See Also

For more examples and patterns, see:

  • meta-convert-dev - Foundational conversion patterns (APTV workflow, testing strategies)
  • convert-clojure-haskell - Reverse conversion (Clojure → Haskell)
  • lang-haskell-dev - Haskell language fundamentals
  • lang-clojure-dev - Clojure language fundamentals

Cross-cutting pattern skills:

  • patterns-concurrency-dev - Concurrency patterns across languages (STM, async, actors)
  • patterns-serialization-dev - Serialization patterns across languages (JSON, validation)
  • patterns-metaprogramming-dev - Metaprogramming across languages (macros, Template Haskell)

スコア

総合スコア

55/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

0/5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です