Croma Elixir Conventions
When writing Elixir code in projects that use Croma, follow these conventions:
Module Structure
- Start files with
use Croma before the defmodule.
Function Definitions
- Use
defun / defunp instead of def / defp.
- Prefer
case/cond over multi-clause functions when branching on a single argument's value. This keeps a single defun/defunp with v[] validation:# Good - single defunp with case, gets v[] validation
defunp calculate_discount(price :: v[non_neg_integer()], membership_level :: v[atom()]) :: v[non_neg_integer()] do
case membership_level do
:gold -> round(price * 0.7)
:silver -> round(price * 0.85)
_ -> price
end
end
# Avoid - loses v[] validation, multiple clauses for simple value dispatch
@spec calculate_discount(non_neg_integer(), atom()) :: non_neg_integer()
defp calculate_discount(price, :gold), do: round(price * 0.7)
defp calculate_discount(price, :silver), do: round(price * 0.85)
defp calculate_discount(price, _), do: price
Type Validation with v[]
- Always wrap arguments and return types with
v[] for runtime type validation. This applies to all types including union types and nilable types. Do not hesitate to use v[] on unions — it works correctly:defun my_func(name :: v[String.t()], count :: v[non_neg_integer()]) :: v[String.t()] do
defun find_name(user_id :: v[String.t()]) :: v[nil | String.t()] do
defunp do_lookup(id :: v[String.t()]) :: v[nil | Terminal.t()] do
defunp classify(value :: v[String.t()]) :: v[:small | :large] do
- Do NOT use
v[] on single literal atom return types (e.g., :ok) — causes a compile warning:defun pretty_print(str :: v[String.t()]) :: :ok do # Good
defun pretty_print(str :: v[String.t()]) :: v[:ok] do # Bad
- Do NOT use
v[] on types without Croma's valid?/1 (e.g., DateTime.t(), Keyword.t(), term()) — causes UndefinedFunctionError or RuntimeError:defun process(dt :: DateTime.t()) :: v[String.t()] do # Good
defun process(dt :: v[DateTime.t()]) :: v[String.t()] do # Bad
defun process(arg :: v[{:ok, term()} | :timeout]) :: term() do # Good
defun process(arg :: v[{:ok, term()} | :timeout]) :: v[term()] do # Bad
Return Types
Example
use Croma
defmodule MyApp.UserValidator do
alias Croma.Result, as: R
defun validate(username :: v[String.t()], age :: v[non_neg_integer()]) ::
v[R.t(map())] do
{:ok, %{username: username, age: age}}
end
defunp check_length(value :: v[String.t()]) :: v[:ok | {:error, String.t()}] do
if String.length(value) > 0, do: :ok, else: {:error, "empty"}
end
end
Source: access-company/antikythera — distributed by TomeVault.
1---2name: access-company-antikythera-antikythera3description: Croma Elixir Conventions4---56# Croma Elixir Conventions78When writing Elixir code in projects that use Croma, follow these conventions:910## Module Structure1112- Start files with `use Croma` before the `defmodule`.1314## Function Definitions1516- Use `defun` / `defunp` instead of `def` / `defp`.17- **Prefer `case`/`cond` over multi-clause functions** when branching on a single argument's value. This keeps a single `defun`/`defunp` with `v[]` validation:18 ```elixir19 # Good - single defunp with case, gets v[] validation20 defunp calculate_discount(price :: v[non_neg_integer()], membership_level :: v[atom()]) :: v[non_neg_integer()] do21 case membership_level do22 :gold -> round(price * 0.7)23 :silver -> round(price * 0.85)24 _ -> price25 end26 end2728 # Avoid - loses v[] validation, multiple clauses for simple value dispatch29 @spec calculate_discount(non_neg_integer(), atom()) :: non_neg_integer()30 defp calculate_discount(price, :gold), do: round(price * 0.7)31 defp calculate_discount(price, :silver), do: round(price * 0.85)32 defp calculate_discount(price, _), do: price33 ```3435## Type Validation with `v[]`3637- **Always** wrap arguments and return types with `v[]` for runtime type validation. This applies to all types including union types and nilable types. **Do not hesitate** to use `v[]` on unions — it works correctly:38 ```elixir39 defun my_func(name :: v[String.t()], count :: v[non_neg_integer()]) :: v[String.t()] do40 defun find_name(user_id :: v[String.t()]) :: v[nil | String.t()] do41 defunp do_lookup(id :: v[String.t()]) :: v[nil | Terminal.t()] do42 defunp classify(value :: v[String.t()]) :: v[:small | :large] do43 ```44- **Do NOT use `v[]`** on single literal atom return types (e.g., `:ok`) — causes a compile warning:45 ```elixir46 defun pretty_print(str :: v[String.t()]) :: :ok do # Good47 defun pretty_print(str :: v[String.t()]) :: v[:ok] do # Bad48 ```49- **Do NOT use `v[]`** on types without Croma's `valid?/1` (e.g., `DateTime.t()`, `Keyword.t()`, `term()`) — causes `UndefinedFunctionError` or `RuntimeError`:50 ```elixir51 defun process(dt :: DateTime.t()) :: v[String.t()] do # Good52 defun process(dt :: v[DateTime.t()]) :: v[String.t()] do # Bad53 defun process(arg :: v[{:ok, term()} | :timeout]) :: term() do # Good54 defun process(arg :: v[{:ok, term()} | :timeout]) :: v[term()] do # Bad55 ```5657## Return Types5859- For `{:ok, value} | {:error, reason}`, use `alias Croma.Result, as: R` and `R.t(value_type)`:60 ```elixir61 alias Croma.Result, as: R62 defun validate(name :: v[String.t()]) :: v[R.t(map())] do63 ```64- Only add the alias when the module actually uses `R.t()`.6566## Example6768```elixir69use Croma7071defmodule MyApp.UserValidator do72 alias Croma.Result, as: R7374 defun validate(username :: v[String.t()], age :: v[non_neg_integer()]) ::75 v[R.t(map())] do76 {:ok, %{username: username, age: age}}77 end7879 defunp check_length(value :: v[String.t()]) :: v[:ok | {:error, String.t()}] do80 if String.length(value) > 0, do: :ok, else: {:error, "empty"}81 end82end83```8485---86> Source: [access-company/antikythera](https://github.com/access-company/antikythera) — distributed by [TomeVault](https://tomevault.io).87<!-- tomevault:4.0:skill_md:2026-06-19 -->