Introduction
Getting Started
- QuickStart
Patterns
- Languages
- Supported Languages
- Python
- Java
- JavaScript
- TypeScript
- Node.js
- React
- Fastify
- Next.js
- Terraform
- C#
- C++
- C
- Go
- Rust
- Swift
- React Native
- Spring Boot
- Kotlin
- Flutter
- Ruby
- PHP
- Scala
- Perl
- R
- Dart
- Elixir
- Erlang
- Haskell
- Lua
- Julia
- Clojure
- Groovy
- Fortran
- COBOL
- Pascal
- Assembly
- Bash
- PowerShell
- SQL
- PL/SQL
- T-SQL
- MATLAB
- Objective-C
- VBA
- ABAP
- Apex
- Apache Camel
- Crystal
- D
- Delphi
- Elm
- F#
- Hack
- Lisp
- OCaml
- Prolog
- Racket
- Scheme
- Solidity
- Verilog
- VHDL
- Zig
- MongoDB
- ClickHouse
- MySQL
- GraphQL
- Redis
- Cassandra
- Elasticsearch
- Security
- Performance
Integrations
- Code Repositories
- Team Messengers
- Ticketing
Enterprise
OCaml is a general-purpose, multi-paradigm programming language which extends the Caml dialect of ML with object-oriented features. It emphasizes expressiveness and safety through a strong static type system with type inference.
OCaml, despite being a powerful and expressive language with a strong type system, has several common anti-patterns that can lead to code that’s difficult to maintain, inefficient, or doesn’t follow the language’s idioms. Here are the most important anti-patterns to avoid when writing OCaml code.
(* Anti-pattern: Excessive mutation *)
let sum_list lst =
let total = ref 0 in
List.iter (fun x -> total := !total + x) lst;
!total
(* Better approach: Use functional constructs *)
let sum_list lst =
List.fold_left (+) 0 lst
Avoid excessive use of mutable references (ref
) and imperative loops. OCaml is primarily a functional language, so prefer immutable data and functional constructs like pattern matching, recursion, and higher-order functions.
(* Anti-pattern: Not leveraging the type system *)
type user = {
name: string;
email: string;
status: string; (* Could be "active", "inactive", "pending" *)
}
let is_user_active user =
user.status = "active"
(* Better approach: Use variant types *)
type status = Active | Inactive | Pending
type user = {
name: string;
email: string;
status: status;
}
let is_user_active user =
match user.status with
| Active -> true
| _ -> false
Leverage OCaml’s powerful type system, especially variant types (sum types), to model your domain accurately. This provides compile-time safety and makes your code more expressive and self-documenting.
(* Anti-pattern: Ignoring option types *)
let find_user users id =
List.find (fun u -> u.id = id) users (* Raises Not_found if not found *)
(* Usage that might raise an exception *)
let user = find_user users "123" in
Printf.printf "Found user: %s\n" user.name
(* Better approach: Use option types *)
let find_user users id =
try Some (List.find (fun u -> u.id = id) users)
with Not_found -> None
(* Or better yet, use built-in functions *)
let find_user users id =
List.find_opt (fun u -> u.id = id) users
(* Safe usage with pattern matching *)
match find_user users "123" with
| Some user -> Printf.printf "Found user: %s\n" user.name
| None -> Printf.printf "User not found\n"
Use option types ('a option
) to represent values that might not exist, rather than raising exceptions or using sentinel values. This makes the possibility of missing values explicit in your type signatures and forces clients to handle both cases.
(* Anti-pattern: Excessive OO style *)
class customer name email =
object (self)
val mutable _name = name
val mutable _email = email
method name = _name
method set_name n = _name <- n
method email = _email
method set_email e = _email <- e
method is_valid =
String.length _name > 0 && String.contains _email '@'
end
(* Usage *)
let c = new customer "John" "john@example.com" in
c#set_name "John Doe";
if c#is_valid then
Printf.printf "Valid customer: %s\n" c#name
(* Better approach: Use records and functions *)
type customer = {
name: string;
email: string;
}
let is_valid customer =
String.length customer.name > 0 && String.contains customer.email '@'
(* Usage *)
let c = { name = "John"; email = "john@example.com" } in
let c = { c with name = "John Doe" } in
if is_valid c then
Printf.printf "Valid customer: %s\n" c.name
Avoid excessive object-oriented style with mutable objects and methods. Instead, prefer immutable records and standalone functions. This leads to code that’s easier to reason about and test.
(* Anti-pattern: Not using pattern matching *)
let describe x =
if x = 0 then
"Zero"
else if x > 0 then
"Positive"
else
"Negative"
let process_shape shape =
if shape.kind = "circle" then
let radius = shape.radius in
Float.pi *. radius *. radius
else if shape.kind = "rectangle" then
let width = shape.width in
let height = shape.height in
width *. height
else
0.0
(* Better approach: Use pattern matching *)
let describe x =
match x with
| 0 -> "Zero"
| x when x > 0 -> "Positive"
| _ -> "Negative"
(* With variant types *)
type shape =
| Circle of float (* radius *)
| Rectangle of float * float (* width * height *)
| Triangle of float * float (* base * height *)
let area shape =
match shape with
| Circle radius -> Float.pi *. radius *. radius
| Rectangle (width, height) -> width *. height
| Triangle (base, height) -> base *. height /. 2.0
Use pattern matching extensively, especially with variant types. Pattern matching makes your code more concise, readable, and helps ensure you handle all possible cases.
(* Anti-pattern: Excessive type annotations *)
let add (x : int) (y : int) : int =
x + y
let process (items : int list) : float =
let sum : int = List.fold_left (+) 0 items in
let count : int = List.length items in
float_of_int sum /. float_of_int count
(* Better approach: Let type inference work *)
let add x y = x + y
let process items =
let sum = List.fold_left (+) 0 items in
let count = List.length items in
float_of_int sum /. float_of_int count
Avoid excessive type annotations. OCaml has powerful type inference, so you usually don’t need to specify types explicitly. Add type annotations only when necessary for clarity, to resolve ambiguities, or at API boundaries.
(* Anti-pattern: Not using the pipeline operator *)
let result = List.map (fun x -> x * 2) (List.filter (fun x -> x mod 2 = 0) [1; 2; 3; 4; 5])
(* Better approach: Use the pipeline operator *)
let result =
[1; 2; 3; 4; 5]
|> List.filter (fun x -> x mod 2 = 0)
|> List.map (fun x -> x * 2)
Use the pipeline operator (|>
) to create readable data processing pipelines. The pipeline operator makes the flow of data through transformations clear and reduces the need for nested function calls or intermediate variables.
(* Anti-pattern: Ignoring partial application *)
let filter predicate list =
List.filter predicate list
let map mapper list =
List.map mapper list
(* Better approach: Leverage partial application *)
let filter predicate =
List.filter predicate
let map mapper =
List.map mapper
(* Usage *)
let even_numbers =
[1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
|> filter (fun x -> x mod 2 = 0)
|> map (fun x -> x * 2)
Leverage partial application to create specialized functions from more general ones. This allows for more concise and composable code.
(* Anti-pattern: Everything in one file without modules *)
(* Better approach: Use modules to organize code *)
module User = struct
type t = {
id: string;
name: string;
email: string;
}
let create id name email =
{ id; name; email }
let is_valid user =
String.length user.name > 0 && String.contains user.email '@'
end
module UserRepository = struct
let users = ref []
let add user =
users := user :: !users
let find_by_id id =
List.find_opt (fun user -> user.User.id = id) !users
let find_all () =
!users
end
(* Even better: Use functors for parameterized modules *)
module type Repository = sig
type t
val add : t -> unit
val find_by_id : string -> t option
val find_all : unit -> t list
end
module MakeRepository (Entity : sig type t val id : t -> string end) : Repository with type t = Entity.t = struct
type t = Entity.t
let items = ref []
let add item =
items := item :: !items
let find_by_id id =
List.find_opt (fun item -> Entity.id item = id) !items
let find_all () =
!items
end
module UserRepo = MakeRepository(struct type t = User.t let id user = user.User.id end)
Use modules to organize your code into logical units with clear interfaces. For more advanced abstractions, use functors (module functions) to create parameterized modules.
(* Anti-pattern: Exception-based error handling *)
let divide a b =
if b = 0 then
failwith "Division by zero"
else
a / b
(* Usage that might raise an exception *)
let result =
try
let x = divide 10 0 in
Printf.printf "Result: %d\n" x
with
Failure msg -> Printf.printf "Error: %s\n" msg
(* Better approach: Use Result type *)
type error = DivisionByZero | InvalidInput of string
let divide a b =
if b = 0 then
Error DivisionByZero
else
Ok (a / b)
(* Usage with pattern matching *)
match divide 10 0 with
| Ok result -> Printf.printf "Result: %d\n" result
| Error DivisionByZero -> Printf.printf "Error: Division by zero\n"
| Error (InvalidInput msg) -> Printf.printf "Error: %s\n" msg
(* With the Result module (from Base, Containers, etc.) *)
let compute_value a b c =
divide a b
|> Result.map (fun result -> result * c)
|> Result.map_error (function
| DivisionByZero -> "Cannot divide by zero"
| InvalidInput msg -> msg)
Use the Result
type for operations that might fail, rather than raising exceptions. This makes error paths explicit in your code and allows for better composition of functions that might fail.
(* Anti-pattern: Functions with many positional arguments *)
let create_user id name email age is_admin is_active =
{ id; name; email; age; is_admin; is_active }
(* Usage - unclear what each argument means *)
let user = create_user "123" "John Doe" "john@example.com" 30 true false
(* Better approach: Use labeled arguments *)
let create_user ~id ~name ~email ~age ~is_admin ~is_active =
{ id; name; email; age; is_admin; is_active }
(* Usage - much clearer *)
let user = create_user
~id:"123"
~name:"John Doe"
~email:"john@example.com"
~age:30
~is_admin:true
~is_active:false
(* Even better: Use optional arguments with defaults *)
let create_user ~id ~name ~email ~age ?(is_admin=false) ?(is_active=true) () =
{ id; name; email; age; is_admin; is_active }
(* Usage with defaults *)
let user = create_user
~id:"123"
~name:"John Doe"
~email:"john@example.com"
~age:30
~is_admin:true
()
Use labeled and optional arguments for functions with many parameters. Labeled arguments make function calls more readable by clarifying the purpose of each argument. Optional arguments with defaults reduce the need for multiple function variants.
(* Anti-pattern: Using lists for everything *)
let find_user users id =
List.find_opt (fun user -> user.id = id) users (* O(n) lookup *)
let user_exists users id =
List.exists (fun user -> user.id = id) users (* O(n) lookup *)
(* Better approach: Use appropriate data structures *)
module UserMap = Map.Make(String) (* For O(log n) lookups by ID *)
let find_user users id =
UserMap.find_opt id users
let user_exists users id =
UserMap.mem id users
(* For sets of values *)
module StringSet = Set.Make(String)
let tags = StringSet.empty
|> StringSet.add "ocaml"
|> StringSet.add "functional"
|> StringSet.add "programming"
let has_tag tags tag =
StringSet.mem tag tags
Use appropriate data structures for your needs. Lists are good for sequential access and small collections, but use maps for key-based lookups, sets for unique collections, and arrays for random access and performance-critical code.
(* Anti-pattern: Recursion without tail calls *)
let rec factorial n =
if n <= 1 then 1
else n * factorial (n - 1) (* Not tail-recursive *)
(* Better approach: Use tail recursion *)
let factorial n =
let rec aux n acc =
if n <= 1 then acc
else aux (n - 1) (n * acc) (* Tail-recursive *)
in
aux n 1
When using recursion, make your functions tail-recursive to avoid stack overflow errors with large inputs. In a tail-recursive function, the recursive call is the last operation in the function.
(* Anti-pattern: No documentation *)
let calculate_score player_stats opponent_stats =
let attack = player_stats.attack in
let defense = opponent_stats.defense in
attack *. (100.0 /. (100.0 +. defense))
(* Better approach: Include documentation comments *)
(** [calculate_score player opponent] calculates the effective score of an attack.
@param player_stats The player's statistics record with at least an [attack] field.
@param opponent_stats The opponent's statistics record with at least a [defense] field.
@return A float representing the effective damage. *)
let calculate_score player_stats opponent_stats =
let attack = player_stats.attack in
let defense = opponent_stats.defense in
attack *. (100.0 /. (100.0 +. defense))
Include documentation comments for your functions, modules, and types. OCaml’s documentation tool, ocamldoc, can extract these comments to generate documentation.
(* Anti-pattern: No tests *)
(* Better approach: Write tests *)
(* Using a testing framework like Alcotest *)
let test_factorial () =
Alcotest.(check int) "factorial 0" 1 (factorial 0);
Alcotest.(check int) "factorial 1" 1 (factorial 1);
Alcotest.(check int) "factorial 5" 120 (factorial 5)
let test_fibonacci () =
Alcotest.(check int) "fibonacci 0" 0 (fibonacci 0);
Alcotest.(check int) "fibonacci 1" 1 (fibonacci 1);
Alcotest.(check int) "fibonacci 5" 5 (fibonacci 5)
let () =
Alcotest.run "My tests" [
"factorial", [
Alcotest.test_case "factorial function" `Quick test_factorial;
];
"fibonacci", [
Alcotest.test_case "fibonacci function" `Quick test_fibonacci;
];
]
Write tests for your OCaml code. Testing helps ensure your code works correctly and continues to work as you make changes. Use testing frameworks like Alcotest, OUnit, or QCheck to structure and run your tests.
(* Anti-pattern: Exposing implementation details *)
module Counter : sig
type t = { mutable value : int }
val create : int -> t
val increment : t -> unit
val get : t -> int
end = struct
type t = { mutable value : int }
let create initial = { value = initial }
let increment counter = counter.value <- counter.value + 1
let get counter = counter.value
end
(* Usage that depends on implementation details *)
let c = Counter.create 0
c.value <- 10 (* Directly modifying the implementation *)
(* Better approach: Use abstract types *)
module Counter : sig
type t (* Abstract type, implementation hidden *)
val create : int -> t
val increment : t -> unit
val get : t -> int
end = struct
type t = { mutable value : int }
let create initial = { value = initial }
let increment counter = counter.value <- counter.value + 1
let get counter = counter.value
end
(* Usage that respects the abstraction *)
let c = Counter.create 0
Counter.increment c
let value = Counter.get c
Use abstract types in module signatures to hide implementation details. This allows you to change the implementation without affecting client code and enforces the use of your module’s API.
(* Anti-pattern: Runtime type checking *)
type value =
| Int of int
| String of string
| Bool of bool
let add a b =
match (a, b) with
| (Int x, Int y) -> Int (x + y)
| _ -> failwith "Type error: can only add integers"
(* Better approach: Use GADTs for type safety *)
type _ value =
| Int : int -> int value
| String : string -> string value
| Bool : bool -> bool value
let add (Int a) (Int b) = Int (a + b)
(* Usage *)
let x = Int 5
let y = Int 10
let z = add x y (* Type-safe *)
(* Using phantom types for additional type safety *)
type 'a validated
let validate_email email : string validated option =
if String.contains email '@' then
Some (Obj.magic email) (* Using Obj.magic for demonstration only *)
else
None
let send_email : string validated -> unit = fun email ->
(* Implementation that can assume email is valid *)
()
(* Usage *)
match validate_email "user@example.com" with
| Some validated_email -> send_email validated_email
| None -> Printf.printf "Invalid email\n"
Use Generalized Algebraic Data Types (GADTs) and phantom types to encode more type information and catch more errors at compile time. These advanced type system features can help you create more type-safe APIs.
(* Anti-pattern: Poor error messages *)
let parse_config filename =
try
let ic = open_in filename in
let config = parse_from_channel ic in
close_in ic;
config
with
| _ -> failwith "Error"
(* Better approach: Provide detailed error messages *)
let parse_config filename =
try
let ic = open_in filename in
try
let config = parse_from_channel ic in
close_in ic;
Ok config
with
| Parse_error pos ->
close_in ic;
Error (Printf.sprintf "Parse error at line %d, column %d" pos.line pos.column)
| e ->
close_in ic;
Error (Printf.sprintf "Unexpected error: %s" (Printexc.to_string e))
with
| Sys_error msg ->
Error (Printf.sprintf "Could not open file '%s': %s" filename msg)
Provide detailed and helpful error messages. Good error messages include context about what went wrong and how to fix it, making your code more user-friendly.