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
endWe 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"
}
}
Summary
Builder Functions
Builds an expression that requires all children to be true.
Builds an expression that requires both children to be true.
Builds an expression that requires at least one child to be true.
Builds an expression that requires at least one of two children to be true.
Builds a check.
Builds an expression that is always false.
Builds an expression that always evaluates to the same value.
Builds an expression that evaluates to true unless both children are true.
Negates the given expression.
Builds an expression that requires all of its children to be false.
Builds an expression that evaluates to true if both children are false.
Builds an expression that is always true.
Builds the exclusive or of the given expressions.
Evaluation Functions
Collects all tagged results from an expression into a list.
Collects the success or error results of an expression into a list.
Evaluates the given expression and returns :ok or an error tuple.
Evaluates the given expression and raises an exception if it is not satisfied.
Evaluates the given expression and returns the result as a boolean.
Evaluates the given expression with eval_tree/2 and collects the results
into a list with collect_results/2.
Evaluates the given expression with eval_tree/2 and collects the results
into a list with collect_results/2.
Evaluates the given expression with eval_tree_all/2 and collects the results
into a list with collect_results/2.
Evaluates the given expression with eval_tree_all/2 and collects the results
into a list with collect_results/2.
Evaluates the given expression and returns the expression annotated with evaluation results.
Evaluates the given expression and returns the expression annotated with evaluation results or raises an error.
Evaluates the full given expression and returns the expression annotated with the expression results.
Evaluates the full given expression and returns the expression annotated with evaluation results or raises an error.
Filters the given enumerable to only retain the items that satisfy the given expression.
Filters the given enumerable to only retain the items that do not satisfy the given expression.
Converts a check result to a boolean.
Optimization Functions
Performs boolean algebra transformations on the given expression.
Types
The evaluation context as passed to the evaluation functions.
A boolean expression tree.
An expression result value that results in a failed evaluation.
The result of an expression evaluation.
An expression result value that results in a successful evaluation.
Builder Functions
@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: []}
]
}
@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: []}
]
}
@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: []}
]
}
@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: []}
]
}
@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]}
@spec fail(falsy()) :: Spek.Literal.t()
Builds an expression that is always false.
Raises an ArgumentError if the result is not false, :error, or an error
tuple. Use pass/1 for a truthy result, or literal/1 to derive the outcome
from the result.
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}
@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}
@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: []}
]
}
}
@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: []}
}
@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: []}
]
}
}
@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: []}
]
}
}
@spec pass(truthy()) :: Spek.Literal.t()
Builds an expression that is always true.
Raises an ArgumentError if the result is not true, :ok, or an ok tuple.
Use fail/1 for a falsy result, or literal/1 to derive the outcome from
the result.
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}
@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: []}
]
}
]
}
Evaluation Functions
@spec collect_results(expression()) :: [term()]
Collects all tagged results from an expression into a list.
Results are returned without their :ok / :error tags, in evaluation order,
and are deduplicated.
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"]Tagged results within Not expressions are collected as well.
iex> Spek.collect_results(
...> %Not{
...> satisfied?: true,
...> expression: %Literal{result: {:error, "bad"}, satisfied?: false}
...> }
...> )
["bad"]
@spec collect_results(expression(), :ok | :error) :: [term()]
Collects the success or error results of an expression into a list.
Results are returned in evaluation order and are deduplicated.
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)
[]
@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"}}
@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
@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
@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"
}
}
@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
@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"
}
}
@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
@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"
}
}
@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
@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"
}
}
@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
@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"]
@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"]
Converts a check result to a boolean.
The evaluation functions apply this mapping to whatever a check function
returns, and the functions generated by Spek.Macros.defcheck/2 call it
directly. Use it when you write a check function by hand and need the boolean
outcome of a result.
Raises an ArgumentError if the value is not a valid result.
Examples
iex> to_boolean(true)
true
iex> to_boolean(:ok)
true
iex> to_boolean({:ok, "proxy_stream_ready"})
true
iex> to_boolean(false)
false
iex> to_boolean(:error)
false
iex> to_boolean({:error, :codec_not_supported})
false
Optimization Functions
@spec optimize(expression()) :: expression()
Performs boolean algebra transformations on the given expression.
The transformations are applied repeatedly until no further optimization applies.
Optimization may remove, deduplicate and reorder checks, so check functions must be free of side effects.
Some transformations remove a sub-expression whose result cannot change the
outcome, and eval_collect/2 and the other collecting functions do not
return the results of what was removed. Given A or (A and B), a failing A
makes the outcome independent of B, so the optimized expression returns the
result of A alone.
The identity, annihilation, absorption and complement laws remove sub-expressions this way. Deduplication, factoring, De Morgan's laws, double negation elimination and the negation of a literal rewrite the expression without removing anything, and leave the collected results unchanged.
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 |
| Complement (AND) | A and (not A) = false |
| Complement (OR) | A or (not A) = true |
| 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 |
Types
@type context() :: term()
The evaluation context as passed to the evaluation functions.
If a check uses a {:ctx, key} argument, the context must be a map or a
keyword list containing that key.
@type expression() :: Spek.AllOf.t() | Spek.AnyOf.t() | Spek.Check.t() | Spek.Literal.t() | Spek.Not.t()
A boolean expression tree.
@type falsy() :: false | :error | {:error, term()}
An expression result value that results in a failed evaluation.
The result of an expression evaluation.
@type truthy() :: true | :ok | {:ok, term()}
An expression result value that results in a successful evaluation.