# Funx: Free Your Predicates
```elixir
Mix.install([
{:funx, "0.8.2"}
])
```
## What is Free?
Functional programming likes to borrow mathematical terms, one of which is `free`.
In FP, `free` means: build a description first, interpret it later.
Here are a couple of basic boolean functions:
```elixir
positive? = fn x -> x > 0 end
even? = fn x -> rem(x, 2) == 0 end
```
When we apply them, they collapse to booleans:
```elixir
positive?.(2) # true
positive?.(-2) # false
even?.(2) # true
even?.(1) # false
```
We can combine them:
```elixir
positive_and_even? = fn x -> positive?.(x) and even?.(x) end
positive_or_even? = fn x -> positive?.(x) or even?.(x) end
positive_and_even?.(1) # false
positive_and_even?.(2) # true
positive_and_even?.(-2) # false
positive_or_even?.(1) # true
positive_or_even?.(2) # true
positive_or_even?.(-2) # true
positive_or_even?.(-1) # false
```
We run the checks inline and bake in the grouping (`and`/`or`).
Funx has `p_all/1` and `p_any/1`, which take lists of predicates and lets us declare the grouping logic:
```elixir
positive_and_even? = Funx.Predicate.p_all([positive?, even?])
positive_or_even? = Funx.Predicate.p_any([positive?, even?])
positive_and_even?.(1) # false
positive_or_even?.(1) # true
```
Funx also has a predicate DSL, which can be a bit easier to read, particularly for more complex logic.
```elixir
use Funx.Predicate
positive_and_even? =
pred do
positive?
even?
end
positive_or_even? =
pred do
any do
positive?
even?
end
end
positive_and_even?.(1) # false
positive_or_even?.(1) # true
```
Let’s look at a role-playing game.
## The Rules
First, we define our game’s rules:
| Predicate | Rule |
| ----------------------- | --------------------------------- |
| `poisoned?` | Poison is active |
| `poison_resistant?` | Blessing grants poison resistance |
| `poison_danger?` | Poisoned AND NOT resistant |
| `bleeding?` | Bleeding is NOT staunched |
| `severe_bleeding?` | Bleeding AND moderate+ |
| `wet?` | Water exposure is wet OR soaked |
| `charge_building?` | Electrical charge building |
| `electrocution_danger?` | Wet AND charge building |
| `exhausted?` | Stamina below 25% |
| `collapsed?` | Stamina below 10% |
| `death_spiral?` | Exhausted AND bleeding |
| `mortal_danger?` | Any mortal danger |
| `can_staunch?` | Bleeding AND has bandage |
| `can_cure_poison?` | Poisoned AND has antidote |
We can express these with predicates:
```elixir
defmodule Status do
defstruct [:poison, :bleeding, :exposure, :stamina, :blessing, :inventory]
use Funx.Predicate
def poisoned? do
pred do
check [:poison, :active], fn active -> active == true end
end
end
def bleeding? do
pred do
check [:bleeding, :staunched], fn staunched -> staunched == false end
end
end
def poison_resistant? do
pred do
check [:blessing, :grants], fn grants -> :poison_resistance in grants end
end
end
def poison_danger? do
pred do
poisoned?()
negate poison_resistant?()
end
end
def severe_bleeding? do
pred do
bleeding?()
check [:bleeding, :severity], fn severity -> severity in [:moderate, :severe, :critical] end
end
end
def wet? do
pred do
check [:exposure, :water], fn water -> water in [:wet, :soaked] end
end
end
def charge_building? do
pred do
check [:exposure, :electricity], fn electricity -> electricity == :building end
end
end
def electrocution_danger? do
pred do
wet?()
charge_building?()
end
end
def exhausted? do
pred do
check :stamina, fn s -> s.current / s.max < 0.25 end
end
end
def collapsed? do
pred do
check :stamina, fn s -> s.current / s.max < 0.1 end
end
end
def death_spiral? do
pred do
exhausted?()
bleeding?()
end
end
def mortal_danger? do
pred do
any do
electrocution_danger?()
death_spiral?()
severe_bleeding?()
collapsed?()
end
end
end
def can_staunch? do
pred do
bleeding?()
check [:inventory, :bandage], fn count -> count > 0 end
end
end
def can_cure_poison? do
pred do
poisoned?()
check [:inventory, :antidote], fn count -> count > 0 end
end
end
end
```
Take a minute to look at this code. These are free predicates. They describe our domain rules without being embedded in control flow, and they are not executed until we interpret them.
When we return in six months, that separation matters. We can quickly see what facts exist, how they build on one another, and where to make a change when the rules evolve, without having to hunt through application logic.
If we have done our job correctly, our subject matter experts should be able to read through these functions and confirm the rules.
## The Character
A character has a name and a status:
```elixir
defmodule Character do
alias Funx.Optics.Lens
defstruct [:name, :status]
def status_lens, do: Lens.key(:status)
def status_check(%__MODULE__{} = character) do
status = Lens.view!(character, status_lens())
%{
poisoned: Status.poisoned?.(status),
poison_resistant: Status.poison_resistant?.(status),
poison_danger: Status.poison_danger?.(status),
bleeding: Status.bleeding?.(status),
severe_bleeding: Status.severe_bleeding?.(status),
electrocution_danger: Status.electrocution_danger?.(status),
exhausted: Status.exhausted?.(status),
collapsed: Status.collapsed?.(status),
death_spiral: Status.death_spiral?.(status),
mortal_danger: Status.mortal_danger?.(status)
}
end
def actions(%__MODULE__{} = character) do
status = Lens.view!(character, status_lens())
%{
can_staunch: Status.can_staunch?.(status),
can_cure_poison: Status.can_cure_poison?.(status)
}
end
end
```
Here, we are interpreting all of our predicates in two functions, `status_check/1` and `actions/1`.
### A character in trouble
Let's start with a character:
```elixir
warrior = %Character{
name: "Wounded Warrior",
status: %Status{
poison: %{active: true, source: :spider, severity: :moderate},
bleeding: %{severity: :light, staunched: false},
exposure: %{water: :soaked, electricity: :building},
stamina: %{current: 20, max: 100},
blessing: %{grants: [:poison_resistance]},
inventory: %{antidote: 1, bandage: 2}
}
}
```
When we apply the `status_check/1`:
```elixir
Character.status_check(warrior)
```
We find our character is in danger:
* Poisoned, but resistant: no poison danger
* Bleeding, but light: no severe bleeding
* Soaked + charge building: electrocution danger
* Exhausted + bleeding: death spiral
* Mortal danger: true
```elixir
Character.actions(warrior)
```
Fortunately, our warrior has some options: they `can_staunch` and `can_cure_poison`.
Let's have them escape the water and apply a bandage:
```elixir
warrior_after = %Character{
name: "Wounded Warrior",
status: %Status{
poison: %{active: true, source: :spider, severity: :moderate},
bleeding: %{severity: :light, staunched: true},
exposure: %{water: :dry, electricity: :building},
stamina: %{current: 20, max: 100},
blessing: %{grants: [:poison_resistance]},
inventory: %{antidote: 1, bandage: 1}
}
}
```
Now when we check their status:
```elixir
Character.status_check(warrior_after)
```
They are no longer in immediate danger:
* Electrocution danger: false
* Death spiral: false
* Mortal danger: false
```elixir
Character.actions(warrior_after)
```
Even though they still have a bandage available, they are no longer bleeding, so `can_staunch` is false. They still have an antidote, so `can_cure_poison` remains true.
## Conclusion
We want our rules to read like rules. We want them named, composed, and grouped in a way we can scan quickly. We want our rules to reflect our domain's shared language. And when the rules change, we want to be able to quickly dive into the code, make the change, and trust everything built on top will hold.