Funx Banner

Funx - Functional Programming Patterns for Elixir

Continuous IntegrationHex.pm

⚠️ Beta: Funx is in active development. APIs may change until version 1.0. Feedback and contributions are welcome.

Official website:https://www.funxlib.comCode and API documentation:https://hex.pm/packages/funx

Breaking Changes in 0.6.0

If you're upgrading from 0.6.0 or earlier, be aware of the module reorganization:

Eq changes

# Change protocol implementations
defimpl Funx.Eq, for: MyStruct          # Old
defimpl Funx.Eq.Protocol, for: MyStruct  # New

# Change imports and aliases
alias Funx.Eq.Utils  # Old
use Funx.Eq.Dsl      # Old

use Funx.Eq          # New (imports eq DSL macro)
alias Funx.Eq        # New (for utility functions)

# Example usage
Eq.contramap(&(&1.age))

Ord changes

# Change protocol implementations
defimpl Funx.Ord, for: MyStruct          # Old
defimpl Funx.Ord.Protocol, for: MyStruct  # New

# Change imports and aliases
alias Funx.Ord.Utils  # Old
use Funx.Ord.Dsl      # Old

use Funx.Ord          # New (imports ord DSL macro)
alias Funx.Ord        # New (for utility functions)

# Example usage
Ord.contramap(&(&1.score))

See the CHANGELOG for more details.

Installation

To use Funx, add it to the list of dependencies in mix.exs:

def deps do
  [
    {:funx, "~> 0.8"}
  ]
end

Then, run the following command to fetch the dependencies:

mix deps.get

Usage Rules

Funx includes embedded usage rules in addition to API documentation.
They are written for development workflows assisted by LLMs.

Equality

The Eq protocol defines how two values are compared, making equality explicit and adaptable to your domain.

Ordering

The Ord protocol defines ordering relationships in a structured way, without relying on Elixir's built-in comparison operators.

Ord DSL

The Ord module includes a DSL for building custom ordering comparators declaratively:

use Funx.Ord

user_ord = ord do
  desc :priority
  asc :name
  desc :created_at
end

Enum.sort(users, Funx.Ord.comparator(user_ord))

Features:

Eq DSL

The Eq module includes a DSL for building equality comparators with boolean logic:

use Funx.Eq

contact_eq = eq do
  on :name
  any do
    on :email
    on :username
  end
end

Funx.Eq.eq?(user1, user2, contact_eq)

Features:

Monads

Monads encapsulate computations, allowing operations to be chained while handling concerns like optional values, failures, dependencies, or deferred effects.

Either DSL

The Either monad includes a DSL for writing declarative pipelines that handle errors gracefully:

use Funx.Monad.Either

either user_id do
  bind fetch_user()
  bind validate_active()
  map transform_to_dto()
end

Supported operations:

Formatter Configuration: Funx exports formatter rules for clean DSL formatting. Add :funx to import_deps in your .formatter.exs:

[
  import_deps: [:funx],
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

See FORMATTER_EXPORT.md for details.

Optics

Optics provide composable, lawful abstractions for focusing on and transforming parts of data structures.

Monoids

Monoids combine values using an associative operation and an identity element. They are useful for accumulation, selection, and combining logic.

Predicates

Predicates are functions that return true or false. Funx provides combinators for composing them cleanly.

Pred DSL

The Predicate module includes a DSL for building boolean predicates declaratively:

use Funx.Predicate
alias Funx.Predicate.{GreaterThanOrEqual, IsTrue, In}

check_eligible = pred do
  check :age, {GreaterThanOrEqual, value: 18}
  check :verified, IsTrue
  check :role, {In, values: [:admin, :moderator]}
end

Enum.filter(users, check_eligible)

Features:

Validation

The Validate module provides declarative data validation with applicative error accumulation—all validators run and all errors are collected.

Validate DSL

use Funx.Validate
alias Funx.Monad.Either
alias Funx.Validator.{Required, Email, MinLength, Positive}

user_validation =
  validate do
    at :name, [Required, {MinLength, min: 3}]
    at :email, [Required, Email]
    at :age, Positive
  end

Either.validate(%{name: "Alice", email: "alice@example.com", age: 30}, user_validation)
# => %Right{right: %{name: "Alice", email: "alice@example.com", age: 30}}

Either.validate(%{name: "", email: "bad", age: -5}, user_validation)
# => %Left{left: %ValidationError{errors: ["is required", "must be at least 3 characters", ...]}}

Features:

Built-in validators: Required, Email, MinLength, MaxLength, Pattern, Positive, Negative, Integer, GreaterThan, LessThan, In, NotIn, Range, Each, Confirmation, Not

Folding

The Foldable protocol defines how to reduce a structure to a single result.

Useful for accumulating values, transforming collections, or extracting data.

Filtering

The Filterable protocol defines how to conditionally retain values within a context.

Sequencing

Sequencing runs a series of monadic operations in order, combining the results.

Lifting

Lifting functions promote ordinary logic into a monadic or contextual form.

Interop

Funx integrates with common Elixir patterns like {:ok, value} and {:error, reason}.

Documentation

The authoritative API documentation is published on HexDocs.

Learning Resources

Contributing

  1. Fork the repository.
  2. Create a new branch for the feature or bugfix (git checkout -b feature-branch).
  3. Commit changes (git commit -am 'Add new feature').
  4. Push the branch (git push origin feature-branch).
  5. Create a pull request.

License

This project is licensed under the MIT License.