Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Tablex

livebooks/tablex-intro.livemd

Tablex

Mix.install([
  {:tablex, "~> 0.3"}
])

Tablex Formatted Decision Tables

Tablex is first a format for presenting decision tables. It’s designed to balance human readability and parser-friendlity.

Here’s an example table to present a decision to make for a proxy relay:

F current_node  destination_domain  || next_relay
1 CA1           www.example.com     || US1
2 -             -                   || -

We can create such a table in Elixir with Tablex library:

routing_table =
  Tablex.new("""
  F current_node  destination_domain  || next_relay
  1 CA1           www.example.com     || US1
  2 -             -                   || -
  """)

Making a decision

This first thing we can do is to make a decision accordingly.

If nothing is present in the input:

Tablex.decide(
  routing_table,
  []
)

If the current node is “CA1” and destination is “www.example.com”:

Tablex.decide(
  routing_table,
  current_node: "CA1",
  destination_domain: "www.example.com"
)

Otherwise, it returns the last any rule instead:

Tablex.decide(
  routing_table,
  current_node: "HK5",
  destination_domain: "www.example.com"
)

Manipulating a Decision Table Programmably

Extracting rules based on a context:

Tablex.Rules.get_rules(routing_table, [])
Tablex.Rules.get_rules(routing_table, current_node: "CA1", destination_domain: "www.example.com")

Inserting new rules:

routing_table =
  routing_table
  |> Tablex.Rules.update_rule_by_input(
    %{current_node: "CA1", destination_domain: "*.mx"},
    %{next_relay: "MX1"}
  )
  |> Tablex.Optimizer.optimize()

routing_table |> Tablex.Formatter.to_s() |> IO.puts()

There’s no API to delete rules, but it can be accomplished by updating, since optimzer can remove unnecessary rules:

routing_table =
  Tablex.Rules.update_rule_by_input(
    routing_table,
    %{current_node: "CA1", destination_domain: "*.mx"},
    %{next_relay: :any}
  )
  # Optimizer will merge duplicated rules
  |> Tablex.Optimizer.optimize()

routing_table |> Tablex.Formatter.to_s() |> IO.puts()

Just for fun

defmodule FibWithTablex do
  @table Tablex.new("""
         F x || fib
         1 1 || 1
         2 >1 || `x * f.(x - 1)`
         """)

  def fib(x) do
    Tablex.decide(@table,
      x: x,
      f: fn x ->
        fib(x) |> Map.fetch!(:fib)
      end
    )
  end
end
FibWithTablex.fib(1)
FibWithTablex.fib(10)