# `Spek`
[🔗](https://github.com/woylie/spek/blob/0.2.0/lib/spek.ex#L1)

Spek is a boolean expression engine for Elixir.

It allows you to model, optimize, and evaluate rules using composable
expressions.

This module contains all expression builder, evaluation, and optimization
functions. Please refer to the readme for details.

## Example

Given this check module:

    defmodule DeviceChecks do
      import Spek.Macros

      defcheck device_online(device, reason: :device_offline) do
        device.online?
      end

      defcheck battery_above_20(device, reason: :battery_too_low) do
        device.battery_level > 20
      end

      defcheck charging(device) do
        device.charging?
      end

      defcheck low_power_mode_enabled(device) do
        device.low_power_mode?
      end
    end

We can compose, optimize, and evaluate rules like this:

    iex> import Spek
    iex> battery_safe =
    ...>   all_of([
    ...>     DeviceChecks.device_online_check(),
    ...>     DeviceChecks.battery_above_20_check()
    ...>   ])
    iex> charging_safe =
    ...>   all_of([
    ...>     DeviceChecks.device_online_check(),
    ...>     DeviceChecks.charging_check()
    ...>   ])
    iex> rule =
    ...>   any_of([
    ...>     battery_safe,
    ...>     charging_safe,
    ...>     DeviceChecks.low_power_mode_enabled_check()
    ...>   ])
    %Spek.AnyOf{
      children: [
        %Spek.AllOf{
          children: [
            %Spek.Check{
              module: DeviceChecks,
              fun: :device_online,
              args: [:ctx]
            },
            %Spek.Check{
              module: DeviceChecks,
              fun: :battery_above_20,
              args: [:ctx]
            }
          ]
        },
        %Spek.AllOf{
          children: [
            %Spek.Check{
              module: DeviceChecks,
              fun: :device_online,
              args: [:ctx]
            },
            %Spek.Check{
              module: DeviceChecks,
              fun: :charging,
              args: [:ctx]
            }
          ]
        },
        %Spek.Check{
          module: DeviceChecks,
          fun: :low_power_mode_enabled,
          args: [:ctx]
        }
      ]
    }
    iex> rule = optimize(rule)
    %Spek.AnyOf{
      children: [
        %Spek.AllOf{
          children: [
            %Spek.Check{
              module: DeviceChecks,
              fun: :device_online,
              args: [:ctx]
            },
            %Spek.AnyOf{
              children: [
                %Spek.Check{
                  module: DeviceChecks,
                  fun: :battery_above_20,
                  args: [:ctx]
                },
                %Spek.Check{
                  module: DeviceChecks,
                  fun: :charging,
                  args: [:ctx]
                }
              ]
            }
          ]
        },
        %Spek.Check{
          module: DeviceChecks,
          fun: :low_power_mode_enabled,
          args: [:ctx]
        }
      ]
    }
    iex> device = %{
    ...>   online?: true,
    ...>   battery_level: 12,
    ...>   charging?: false,
    ...>   low_power_mode?: false
    ...> }
    iex> Spek.eval?(rule, device)
    false
    iex> device = %{
    ...>   online?: false,
    ...>   battery_level: 25,
    ...>   charging?: false,
    ...>   low_power_mode?: false
    ...> }
    iex> Spek.eval_tree(rule, device)
    {
      :error,
      %Spek.EvaluationError{
        expression: %Spek.AnyOf{
          children: [
            %Spek.AllOf{
              satisfied?: false,
              children: [
                %Spek.Check{
                  module: DeviceChecks,
                  fun: :device_online,
                  args: [:ctx],
                  result: {:error, :device_offline},
                  satisfied?: false
                }
              ]
            },
            %Spek.Check{
              module: DeviceChecks,
              fun: :low_power_mode_enabled,
              args: [:ctx],
              result: {:error, :failed},
              satisfied?: false
            }
          ],
          satisfied?: false
        },
        message: "rule evaluation failed"
      }
    }

# `all_of`

```elixir
@spec all_of([expression()]) :: Spek.AllOf.t()
```

Builds an expression that requires all children to be true.

## Example

    iex> all_of([
    ...>   check(MyModule, :session_active, []),
    ...>   check(MyModule, :permissions_valid, [])
    ...> ])
    %Spek.AllOf{
      children: [
        %Spek.Check{module: MyModule, fun: :session_active, args: []},
        %Spek.Check{module: MyModule, fun: :permissions_valid, args: []}
      ]
    }

# `all_of`

```elixir
@spec all_of(expression(), expression()) :: Spek.AllOf.t()
```

Builds an expression that requires both children to be true.

## Example

    iex> all_of(
    ...>   check(RenderingChecks, :color_profile_valid, []),
    ...>   check(RenderingChecks, :frame_rate_supported, [])
    ...> )
    %Spek.AllOf{
      children: [
        %Spek.Check{module: RenderingChecks, fun: :color_profile_valid, args: []},
        %Spek.Check{module: RenderingChecks, fun: :frame_rate_supported, args: []}
      ]
    }

# `any_of`

```elixir
@spec any_of([expression()]) :: Spek.AnyOf.t()
```

Builds an expression that requires at least one child to be true.

## Example

    iex> any_of([
    ...>   check(AudioPipelineChecks, :waveform_detected, []),
    ...>   check(AudioPipelineChecks, :silence_threshold_exceeded, [])
    ...> ])
    %Spek.AnyOf{
      children: [
        %Spek.Check{module: AudioPipelineChecks, fun: :waveform_detected, args: []},
        %Spek.Check{module: AudioPipelineChecks, fun: :silence_threshold_exceeded, args: []}
      ]
    }

# `any_of`

```elixir
@spec any_of(expression(), expression()) :: Spek.AnyOf.t()
```

Builds an expression that requires at least one of two children to be true.

## Example

    iex> any_of(
    ...>   check(SubtitleChecks, :burn_in_detected, []),
    ...>   check(SubtitleChecks, :timecode_aligned, [])
    ...> )
    %Spek.AnyOf{
      children: [
        %Spek.Check{module: SubtitleChecks, fun: :burn_in_detected, args: []},
        %Spek.Check{module: SubtitleChecks, fun: :timecode_aligned, args: []}
      ]
    }

# `check`

```elixir
@spec check(module(), fun(), Spek.Check.args()) :: Spek.Check.t()
```

Builds a check.

## Example

    iex> check(WeatherChecks, :temperature_below_freezing, [0])
    %Spek.Check{module: WeatherChecks, fun: :temperature_below_freezing, args: [0]}

# `fail`

```elixir
@spec fail(falsy()) :: Spek.Literal.t()
```

Builds an expression that is always false. 

## Example

    iex> fail()
    %Spek.Literal{result: false, satisfied?: false}

    iex> fail(:error)
    %Spek.Literal{result: :error, satisfied?: false}

    iex> fail({:error, :insufficient_lighting})
    %Spek.Literal{result: {:error, :insufficient_lighting}, satisfied?: false}

# `literal`

```elixir
@spec literal(result()) :: Spek.Literal.t()
```

Builds an expression that always evaluates to the same value.

## Examples

    iex> literal(true)
    %Spek.Literal{result: true, satisfied?: true}
    
    iex> literal(:ok)
    %Spek.Literal{result: :ok, satisfied?: true}

    iex> literal({:ok, "render_queue_ready"})
    %Spek.Literal{result: {:ok, "render_queue_ready"}, satisfied?: true}

    iex> literal(false)
    %Spek.Literal{result: false, satisfied?: false}
    
    iex> literal(:error)
    %Spek.Literal{result: :error, satisfied?: false}
    
    iex> literal({:error, :codec_not_supported})
    %Spek.Literal{result: {:error, :codec_not_supported}, satisfied?: false}

# `nand`

```elixir
@spec nand(expression(), expression()) :: expression()
```

Builds an expression that evaluates to true unless both children are true.

## Example

    iex> nand(check(RenderChecks, :gpu_available, []), check(RenderChecks, :texture_cache_warm, []))
    %Spek.Not{
      expression: %Spek.AllOf{
        children: [
          %Spek.Check{module: RenderChecks, fun: :gpu_available, args: []},
          %Spek.Check{module: RenderChecks, fun: :texture_cache_warm, args: []}
        ]
      }
    }

# `negate`

```elixir
@spec negate(expression()) :: Spek.Not.t()
```

Negates the given expression.

## Examples

    iex> negate(literal(true))
    %Spek.Not{expression: %Spek.Literal{result: true, satisfied?: true}}

    iex> negate(check(EncodingChecks, :keyframe_aligned, []))
    %Spek.Not{
      expression: %Spek.Check{module: EncodingChecks, fun: :keyframe_aligned, args: []}
    }

# `none`

```elixir
@spec none([expression()]) :: expression()
```

Builds an expression that requires all of its children to be false.

## Example

    iex> none([check(AudioChecks, :noise_floor_exceeded, []), check(AudioChecks, :clipping_detected, [])])
    %Spek.Not{
      expression: %Spek.AnyOf{
        children: [
          %Spek.Check{module: AudioChecks, fun: :noise_floor_exceeded, args: []},
          %Spek.Check{module: AudioChecks, fun: :clipping_detected, args: []}
        ]
      }
    }

# `nor`

```elixir
@spec nor(expression(), expression()) :: expression()
```

Builds an expression that evaluates to true if both children are false.

## Example

    iex> nor(check(VideoChecks, :frame_dropped, []), check(VideoChecks, :desync_detected, []))
    %Spek.Not{
      expression: %Spek.AnyOf{
        children: [
          %Spek.Check{module: VideoChecks, fun: :frame_dropped, args: []},
          %Spek.Check{module: VideoChecks, fun: :desync_detected, args: []}
        ]
      }
    }

# `pass`

```elixir
@spec pass(truthy()) :: Spek.Literal.t()
```

Builds an expression that is always true. 

## Example

    iex> pass()
    %Spek.Literal{result: true, satisfied?: true}
    
    iex> pass(:ok)
    %Spek.Literal{result: :ok, satisfied?: true}
    
    iex> pass({:ok, "proxy_stream_ready"})
    %Spek.Literal{result: {:ok, "proxy_stream_ready"}, satisfied?: true}

# `xor`

```elixir
@spec xor(expression(), expression()) :: expression()
```

Builds the exclusive or of the given expressions.

## Example

    iex> xor(check(PipelineChecks, :transcode_complete, []), check(PipelineChecks, :thumbnail_generated, []))
    %Spek.AnyOf{
      children: [
        %Spek.AllOf{
          children: [
            %Spek.Check{module: PipelineChecks, fun: :transcode_complete, args: []},
            %Spek.Not{
              expression: %Spek.Check{
                module: PipelineChecks,
                fun: :thumbnail_generated,
                args: []
              }
            }
          ]
        },
        %Spek.AllOf{
          children: [
            %Spek.Not{
              expression: %Spek.Check{
                module: PipelineChecks,
                fun: :transcode_complete,
                args: []
              }
            },
            %Spek.Check{module: PipelineChecks, fun: :thumbnail_generated, args: []}
          ]
        }
      ]
    }

# `collect_results`

```elixir
@spec collect_results(expression()) :: [term()]
```

Collects all tagged results from an expression into a list.

Results are returned without their `:ok` / `:error` tags.

## Examples

Literals and checks contribute only tagged tuple payloads. The plain result
values `:ok`, `:error`, `true` and `false` are not returned.

    iex> Spek.collect_results(
    ...>   %Literal{result: {:ok, "good"}, satisfied?: true}
    ...> )
    ["good"]

    iex> Spek.collect_results(
    ...>   %Literal{result: true, satisfied?: true}
    ...> )
    []

    iex> Spek.collect_results(
    ...>   %Check{
    ...>     module: Checks,
    ...>     fun: :some_check,
    ...>     args: [],
    ...>     result: {:error, "bad"},
    ...>     satisfied?: false
    ...>   }
    ...> )
    ["bad"]

The results of nested expressions are flattened.

    iex> expression =
    ...>   %AllOf{
    ...>     children: [
    ...>       %Literal{result: {:ok, "a"}, satisfied?: true},
    ...>       %Literal{result: {:error, "b"}, satisfied?: false},
    ...>       %Check{
    ...>         module: Checks,
    ...>         fun: :some_check,
    ...>         args: [],
    ...>         result: {:ok, "c"},
    ...>         satisfied?: true
    ...>       }
    ...>     ]
    ...>   }
    iex> Spek.collect_results(expression)
    ["a", "b", "c"]

# `collect_results`

```elixir
@spec collect_results(expression(), :ok | :error) :: [term()]
```

Collects the success or error results of an expression into a list.

## Examples

The returned list only contains results using `:ok` or `:error` tuples.

    iex> Spek.collect_results(
    ...>   %Literal{result: {:ok, "good"}, satisfied?: true},
    ...>   :ok
    ...> )
    ["good"]

    iex> Spek.collect_results(
    ...>   %Check{
    ...>     module: Checks,
    ...>     fun: :some_check,
    ...>     args: [],
    ...>     result: {:error, "bad"},
    ...>     satisfied?: false
    ...>   },
    ...>   :error
    ...> )
    ["bad"]

The plain result values `:ok`, `:error`, `true` and `false` are not returned.

    iex> Spek.collect_results(
    ...>   %Check{
    ...>     module: Checks,
    ...>     fun: :some_check,
    ...>     args: [],
    ...>     result: false,
    ...>     satisfied?: false
    ...>   },
    ...>   :error
    ...> )
    []

The results of nested expressions are flattened.

    iex> expression =
    ...>   %AllOf{
    ...>     children: [
    ...>       %Literal{result: {:ok, "a"}, satisfied?: true},
    ...>       %Literal{result: {:error, "b"}, satisfied?: false},
    ...>       %Check{
    ...>         module: Checks,
    ...>         fun: :some_check,
    ...>         args: [],
    ...>         result: {:ok, "c"},
    ...>         satisfied?: true
    ...>       }
    ...>     ]
    ...>   }
    iex> Spek.collect_results(expression, :ok)
    ["a", "c"]
    iex> Spek.collect_results(expression, :error)
    ["b"]

`Not` reverses which tagged results are returned:

    iex> expression =
    ...>   %Not{
    ...>     satisfied?: true,
    ...>     expression: %Literal{
    ...>       result: {:error, "bad"},
    ...>       satisfied?: false
    ...>     }
    ...>   }
    iex> Spek.collect_results(expression, :ok)
    ["bad"]
    iex> Spek.collect_results(expression, :error)
    []

# `eval`

```elixir
@spec eval(expression(), context()) :: :ok | {:error, Spek.EvaluationError.t()}
```

Evaluates the given expression and returns `:ok` or an error tuple.

Stops early as soon as the final outcome is determined.

## Examples

    iex> eval(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "scene_042_render_complete"
    ...> )
    :ok

    iex> eval(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "shot_042_render_complete"
    ...> )
    {:error, %Spek.EvaluationError{message: "rule evaluation failed"}}

# `eval!`

```elixir
@spec eval!(expression(), context()) :: :ok | no_return()
```

Evaluates the given expression and raises an exception if it is not satisfied.

Stops early as soon as the final outcome is determined.

## Examples

    iex> eval!(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "scene_042_render_complete"
    ...> )
    :ok

    iex> eval!(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "shot_042_render_complete"
    ...> )
    ** (Spek.EvaluationError) rule evaluation failed

# `eval?`

```elixir
@spec eval?(expression(), context()) :: boolean()
```

Evaluates the given expression and returns the result as a boolean.

Stops early as soon as the final outcome is determined.

## Examples

    iex> eval?(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "scene_042_render_complete"
    ...> )
    true

    iex> eval?(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "shot_042_render_complete"
    ...> )
    false

# `eval_collect`

```elixir
@spec eval_collect(expression(), context()) ::
  {:ok, [term()]} | {:error, Spek.EvaluationError.t()}
```

Evaluates the given expression with `eval_tree/2` and collects the results
into a list with `collect_results/2`.

In the success case, an `:ok` tuple with the list of success results is
returned.

In the error case, an `:error` tuple with a `Spek.EvaluationError` struct that
contains both the evaluation tree and the list of error results.

## Examples

    iex> eval_collect(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01-12"
    ...> )
    {:ok, [~D[2000-01-12]]}

    iex> eval_collect(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01.12"
    ...> )
    {
      :error,
      %Spek.EvaluationError{
        expression: %Spek.Check{
          module: Date,
          fun: :from_iso8601,
          args: [:ctx],
          result: {:error, :invalid_format},
          satisfied?: false
        },
        results: [:invalid_format],
        message: "rule evaluation failed"
      }
    }

# `eval_collect!`

```elixir
@spec eval_collect!(expression(), context()) :: [term()] | no_return()
```

Evaluates the given expression with `eval_tree/2` and collects the results
into a list with `collect_results/2`.

In the success case, the list of success results is returned.

In the error case, a `Spek.EvaluationError` exception is raised that
contains both the evaluation tree and the list of error results.

## Examples

    iex> eval_collect!(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01-12"
    ...> )
    [~D[2000-01-12]]

    iex> eval_collect!(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01.12"
    ...> )
    ** (Spek.EvaluationError) rule evaluation failed

# `eval_collect_all`

```elixir
@spec eval_collect_all(expression(), context()) ::
  {:ok, [term()]} | {:error, Spek.EvaluationError.t()}
```

Evaluates the given expression with `eval_tree_all/2` and collects the results
into a list with `collect_results/2`.

In the success case, an `:ok` tuple with the list of success results is
returned.

In the error case, an `:error` tuple with a `Spek.EvaluationError` struct that
contains both the evaluation tree and the list of error results.

## Examples

    iex> eval_collect_all(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01-12"
    ...> )
    {:ok, [~D[2000-01-12]]}

    iex> eval_collect_all(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01.12"
    ...> )
    {
      :error,
      %Spek.EvaluationError{
        expression: %Spek.Check{
          module: Date,
          fun: :from_iso8601,
          args: [:ctx],
          result: {:error, :invalid_format},
          satisfied?: false
        },
        results: [:invalid_format],
        message: "rule evaluation failed"
      }
    }

# `eval_collect_all!`

```elixir
@spec eval_collect_all!(expression(), context()) :: [term()] | no_return()
```

Evaluates the given expression with `eval_tree_all/2` and collects the results
into a list with `collect_results/2`.

In the success case, the list of success results is returned.

In the error case, a `Spek.EvaluationError` exception is raised that
contains both the evaluation tree and the list of error results.

## Examples

    iex> eval_collect_all!(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01-12"
    ...> )
    [~D[2000-01-12]]

    iex> eval_collect_all!(
    ...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
    ...>   "2000-01.12"
    ...> )
    ** (Spek.EvaluationError) rule evaluation failed

# `eval_tree`

```elixir
@spec eval_tree(expression(), context()) ::
  {:ok, expression()} | {:error, Spek.EvaluationError.t()}
```

Evaluates the given expression and returns the expression annotated with
evaluation results.

Stops early as soon as the final outcome is determined. The returned
expression only contains the evaluated parts.

## Examples

    iex> eval_tree(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "scene_042_render_complete"
    ...> )
    {
      :ok,
      %Spek.Check{
        module: String,
        fun: :starts_with?,
        args: [:ctx, "scene_"],
        result: true,
        satisfied?: true
      }
    }

    iex> eval_tree(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "shot_042_render_complete"
    ...> )
    {
      :error,
      %Spek.EvaluationError{
        expression: %Spek.Check{
          module: String,
          fun: :starts_with?,
          args: [:ctx, "scene_"],
          result: false,
          satisfied?: false
        },
        message: "rule evaluation failed"
      }
    }

# `eval_tree!`

```elixir
@spec eval_tree!(expression(), context()) :: expression() | no_return()
```

Evaluates the given expression and returns the expression annotated with
evaluation results or raises an error.

Stops early as soon as the final outcome is determined. The returned
expression only contains the evaluated parts.

Raises an exception if the rule is not satisfied. Unlike `eval!/2`, the
exception contains the evaluated expression.

## Examples

    iex> eval_tree!(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "scene_042_render_complete"
    ...> )
    %Spek.Check{
      module: String,
      fun: :starts_with?,
      args: [:ctx, "scene_"],
      result: true,
      satisfied?: true
    }

    iex> eval_tree!(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "shot_042_render_complete"
    ...> )
    ** (Spek.EvaluationError) rule evaluation failed

# `eval_tree_all`

```elixir
@spec eval_tree_all(expression(), context()) ::
  {:ok, expression()} | {:error, Spek.EvaluationError.t()}
```

Evaluates the full given expression and returns the expression annotated with
the expression results.

Always evaluates the entire expression, even if the final outcome could be
determined earlier.

## Examples

    iex> eval_tree_all(
    ...>   all_of([
    ...>     %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>     %Check{module: String, fun: :ends_with?, args: [:ctx, "_render_complete"]}
    ...>   ]),
    ...>   "scene_042_render_complete"
    ...> )
    {
      :ok,
      %Spek.AllOf{
        children: [
          %Spek.Check{
            module: String,
            fun: :starts_with?,
            args: [:ctx, "scene_"],
            result: true,
            satisfied?: true
          },
          %Spek.Check{
            module: String,
            fun: :ends_with?,
            args: [:ctx, "_render_complete"],
            result: true,
            satisfied?: true
          }
        ],
        satisfied?: true
      }
    }

    iex> eval_tree_all(
    ...>   all_of([
    ...>     %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>     %Check{module: String, fun: :ends_with?, args: [:ctx, "_render_complete"]}
    ...>   ]),
    ...>   "shot_042_render_complete"
    ...> )
    {
      :error,
      %Spek.EvaluationError{
        expression: %Spek.AllOf{
          children: [
            %Spek.Check{
              module: String,
              fun: :starts_with?,
              args: [:ctx, "scene_"],
              result: false,
              satisfied?: false
            },
            %Spek.Check{
              module: String,
              fun: :ends_with?,
              args: [:ctx, "_render_complete"],
              result: true,
              satisfied?: true
            }
          ],
          satisfied?: false
        },
        message: "rule evaluation failed"
      }
    }

# `eval_tree_all!`

```elixir
@spec eval_tree_all!(expression(), context()) :: expression() | no_return()
```

Evaluates the full given expression and returns the expression annotated with
evaluation results or raises an error.

Raises if the expression is not satisfied. Unlike `eval!/2`, the raised
exception contains the evaluated expression.

Always evaluates the entire expression, even if the final outcome could be
determined earlier.

## Examples

    iex> eval_tree_all!(
    ...>   any_of([
    ...>     %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>     %Check{module: String, fun: :ends_with?, args: [:ctx, "_render_complete"]}
    ...>   ]),
    ...>   "scene_042_render_complete"
    ...> )
    %Spek.AnyOf{
      satisfied?: true,
      children: [
        %Spek.Check{
          module: String,
          fun: :starts_with?,
          args: [:ctx, "scene_"],
          result: true,
          satisfied?: true
        },
        %Spek.Check{
          module: String,
          fun: :ends_with?,
          args: [:ctx, "_render_complete"],
          result: true,
          satisfied?: true
        }
      ]
    }

    iex> eval_tree_all!(
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
    ...>   "shot_042_render_complete"
    ...> )
    ** (Spek.EvaluationError) rule evaluation failed

# `filter`

```elixir
@spec filter(Enumerable.t(), expression()) :: Enumerable.t()
```

Filters the given enumerable to only retain the items that satisfy the given
expression.

## Example

    iex> filter(
    ...>   ["scene_042_render_complete", "shot_017_proxy_ready"],
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]}
    ...> )
    ["scene_042_render_complete"]

# `reject`

```elixir
@spec reject(Enumerable.t(), expression()) :: Enumerable.t()
```

Filters the given enumerable to only retain the items that _do not_ satisfy
the given expression.

## Example

    iex> reject(
    ...>   ["scene_042_render_complete", "shot_017_proxy_ready"],
    ...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]}
    ...> )
    ["shot_017_proxy_ready"]

# `optimize`

```elixir
@spec optimize(expression()) :: expression()
```

Performs boolean algebra transformations on the given expression.

## Examples

    iex> Spek.optimize(%AnyOf{
    ...>   children: [
    ...>     %AllOf{
    ...>       children: [
    ...>         %Check{module: RenderChecks, fun: :gpu_available, args: []},
    ...>         %Check{module: RenderChecks, fun: :texture_cache_warm, args: []}
    ...>       ]
    ...>     },
    ...>     %AllOf{
    ...>       children: [
    ...>         %Check{module: RenderChecks, fun: :color_space_valid, args: []},
    ...>         %Check{module: RenderChecks, fun: :gpu_available, args: []}
    ...>       ]
    ...>     },
    ...>     %Check{module: RenderChecks, fun: :fallback_renderer_enabled, args: []}
    ...>   ]
    ...> })
    %AnyOf{
      children: [
        %AllOf{
          children: [
            %Check{module: RenderChecks, fun: :gpu_available, args: []},
            %AnyOf{
              children: [
                %Check{module: RenderChecks, fun: :texture_cache_warm, args: []},
                %Check{module: RenderChecks, fun: :color_space_valid, args: []}
              ]
            }
          ]
        },
        %Check{module: RenderChecks, fun: :fallback_renderer_enabled, args: []}
      ]
    }

## Optimizations

| Optimization | Formula |
|---|---|
| Identity (AND) | `A and true = A` |
| Identity (OR) | `A or false = A` |
| Annihilation (AND) | `A and false = false` |
| Annihilation (OR) | `A or true = true` |
| Double negation elimination | `not (not A) = A` |
| Negation of literals | `not true = false`, `not false = true` |
| De Morgan’s law (AND) | `not (A and B) = (not A) or (not B)` |
| De Morgan’s law (OR) | `not (A or B) = (not A) and (not B)` |
| Empty conjunction | `allof() = true` |
| Empty disjunction | `anyof() = false` |
| Single-child conjunction elimination | `allof(A) = A` |
| Single-child disjunction elimination | `anyof(A) = A` |
| Deduplication (AND) | `A and A = A` |
| Deduplication (OR) | `A or A = A` |
| Factoring OR over AND | `(A and B) or (A and C) = A and (B or C)` |
| Factoring AND over OR | `(A or B) and (A or C) = A or (B and C)` |
| Absorption (OR) | `A or (A and B) = A` |
| Absorption (AND) | `A and (A or B) = A` |

# `context`

```elixir
@type context() :: term()
```

The evaluation context as passed to the evaluation functions.

# `expression`

```elixir
@type expression() ::
  Spek.AllOf.t()
  | Spek.AnyOf.t()
  | Spek.Check.t()
  | Spek.Literal.t()
  | Spek.Not.t()
```

A boolean expression tree.

# `falsy`

```elixir
@type falsy() :: false | :error | {:error, term()}
```

An expression result value that results in a failed evaluation.

# `result`

```elixir
@type result() :: truthy() | falsy()
```

The result of an expression evaluation.

# `truthy`

```elixir
@type truthy() :: true | :ok | {:ok, term()}
```

An expression result value that results in a successful evaluation.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
