Powered by AppSignal & Oban Pro

Funx.Appendable

livebooks/appendable/appendable.livemd

Funx.Appendable

Mix.install([
  {:funx, "0.8.8"}
])

A protocol for combining values in a generic, extensible way.

The Appendable protocol defines how two values of the same type can be combined. It is used throughout Funx in functions like traverse_a/2 and wither_a/2 to accumulate intermediate results without coupling logic to a specific type.

This protocol enables functions to remain flexible and composable when reducing, aggregating, or accumulating values across a wide variety of domains.

Required functions

  • coerce/1 – Normalizes an input value into a form suitable for aggregation.
  • append/2 – Combines two values of the same type into one.

Default – Flat list aggregation

A fallback implementation is provided for all types that do not define a specific Appendable instance. This default uses list concatenation as a universal aggregation strategy: all inputs are coerced into lists (if not already), and combined using ++.

When using the default aggregation strategy, values are collected in a plain list:

validate_positive = fn x ->
  Funx.Monad.Either.lift_predicate(x, &(&1 > 0), fn v -> "Value must be positive: " <> to_string(v) end)
end

validate_even = fn x ->
  Funx.Monad.Either.lift_predicate(x, &(rem(&1, 2) == 0), fn v -> "Value must be even: " <> to_string(v) end)
end

Funx.Monad.Either.validate(4, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Right{right: 4}

Funx.Monad.Either.validate(3, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Left{left: ["Value must be even: 3"]}

Funx.Monad.Either.validate(-3, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Left{left: ["Value must be positive: -3", "Value must be even: -3"]}

Structured aggregation with ValidationError

You can also use a custom struct to hold errors. This example uses ValidationError:

alias Funx.Errors.ValidationError

validate_positive = fn x ->
  Funx.Monad.Either.lift_predicate(x, &(&1 > 0), fn v -> "Value must be positive: " <> to_string(v) end)
  |> Funx.Monad.Either.map_left(&ValidationError.new/1)
end

validate_even = fn x ->
  Funx.Monad.Either.lift_predicate(x, &(rem(&1, 2) == 0), fn v -> "Value must be even: " <> to_string(v) end)
  |> Funx.Monad.Either.map_left(&ValidationError.new/1)
end

Funx.Monad.Either.validate(-3, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Left{
#     left: %ValidationError{
#       errors: ["Value must be positive: -3", "Value must be even: -3"]
#     }
#   }

coerce/1

Normalizes a single input value into a form suitable for accumulation.

append/2

Combines two values into a single result.

Implementations must ensure the operation is associative within their type. For types that require disambiguation or structural control, define a custom implementation.

Function Examples

import Funx.Appendable