qhwa

qhwa

Formular - A tiny DSL engine / code evaluator (configuration as code)

Hi, all,

I just published Formular package. It is a tiny library that evaluates a piece of Elixir code.

Online documentation

On the shoulder of Elixir’s Code module

Given a piece of Elixir code (as a string, or AST), Formular runs it with Elixir’s Code module under some security limitations.

So far, the limitations are:

  • No calling module functions;
  • No calling exit;
  • No sending messages.

Indeed, the whole library consists of only one thin module, thanks to the power Elixir has already shipped out of the box.

Motivation

Formular was developed to support some dynamic configuration scenarios. For example, in a scene of an online book store, the discount of a book can be dynamically configured as a piece of code, then evaluated by Formular:

iex> discount_formula = ~s"
...>   case order do
...>     # old books get a big promotion
...>     %{book: %{year: year}} when year < 2000 ->
...>       0.5
...>   
...>     %{book: %{tags: tags}} ->
...>       # Elixir books!
...>       if ~s{elixir} in tags do
...>         0.9
...>       else
...>         1.0
...>       end
...>
...>     _ ->
...>       1.0
...>   end
...> "
...>
...> book_order = %{
...>   book: %{
...>     title: "Elixir in Action", year: 2019, tags: ["elixir"]
...>   }
...> }
...>
...> Formular.eval(discount_formula, [order: book_order])
{:ok, 0.9}

In such a way, the discount calculation code, which changes frequently, is separated away from the stable business flow, and the primary code is probably more generic and flexible.

I’ve been using it in production for a while so I publish it today in case others may find it useful too.

Cheers!

Most Liked

hauleth

hauleth

DoS (atom exhaustion):

Formular.eval(~S|for a <- %Range{first: 0, last: 100_000, step: 1}, do: :"#{a}"|, [])

I needed to create range manually, as you do not export ../2 operator.

qhwa

qhwa

Two new versions have been released:

  • v0.2.2

    • brings performance improvements to 0.2.x
  • v0.3.0

    • allows limiting the execution time and heap size
    • allows compiling a code string to an Elixir module which brings more performance improvements,
    • adds new API Formula.used_vars/2 to extract the used variable names in the formula.

Highlights:

Performance improvements:

A simple benchmark on a simple formula results in this:

Name                      ips        average  deviation         median         99th %
compiled_module      947.84 K        1.06 μs  ±5536.01%        0.83 μs        1.45 μs
eval                  14.53 K       68.82 μs     ±8.93%       68.69 μs       87.93 μs
eval_ast               4.86 K      205.58 μs    ±18.43%      193.89 μs      333.89 μs

Comparison: 
compiled_module      947.84 K
eval                  14.53 K - 65.23x slower +67.76 μs
eval_ast               4.86 K - 194.85x slower +204.52 μs

The compilation approach is about 80x faster than the original eval approach in v0.2.2, and 300x faster than versions prior to v0.2.2.

Restricted evaluation:

  • limited execution time:

    Formular.eval(code, timeout: :timer.seconds(5))
    
  • limited max heap size by word:

    Formular.eval(code, max_heap_size: 15_000)
    

If limited, the evaluation will be run in a separate process.

Extract used vars API:

This can be helpful if you need to build some UI arround the formula.

iex> Formular.used_vars("a + b - 1")
[:a, :b]
mat-hek

mat-hek

Membrane Core Team

Nice, though I’m curious if you tried using Sand?

Where Next?

Popular in Announcing Top

versilov
Could not wait for the missing Elixir ML libraries to appear, so, I wrote one myself, taking https://github.com/sdwolfz/exlearn as a foun...
New
wfgilman
I’ve cleaned up and open sourced three financial libraries I was using for my company. They are bindings for the APIs of these three comp...
New
mspanc
I am pleased to announce an initial release of the Membrane Framework - an Elixir-based framework with special focus on processing multim...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 30380 241
New
RobertDober
Earmark is a pure-Elixir Markdown converter. It is intended to be used as a library (just call Earmark.as_html), but can also be used as...
239 12905 134
New
Qqwy
TypeCheck: Fast and flexible runtime type-checking for your Elixir projects. Core ideas Type- and function specifications are const...
336 14713 100
New
zoltanszogyenyi
Hey everyone :waving_hand: Excited to join this forum - I am one of the founders and current project maintainers of a popular and open-s...
New

Other popular topics Top

baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54996 245
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44608 311
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement