Exploring Life's Ultimate Question with CEL

Exploring Life's Ultimate Question with CEL

The ability to express and evaluate constraints, policies and conditions using a machine-readable language is not something new, the roots lie back in the Advanced Boolean Expression Language which was used in hardware verification.

Below is a sample program for describing the half-adder

module my_first_circuit;
title 'ee200 assignment 1'
EE200XY device 'XC4003E';



" input pins
A, B pin 3, 5;

" output pins
SUM, Carry_out pin 15, 18 istype 'com';

equations

SUM = (A & !B) # (!A & B) ;
Carry_out = A & B;

end my_first_circuit;

evolving to software in the Java ecosystem as an Expression Language

<c:if test="${sessionScope.cart.numberOfItems > 0}">
  ...
</c:if>

notice how "${sessionScope.cart.numberOfItems > 0}" expression is represented as a string that allows the user to evaluate a condition depending on the value of numberOfItems in cart object from the session.

Common Expression Language is one such attempt from Google to write a language-agnostic expression language and evaluation engine.

A sample rule from cel-spec

account.balance >= transaction.withdrawal
    || (account.overdraftProtection
    && account.overdraftLimit >= transaction.withdrawal  - account.balance)

CEL allows us to specify expressions and provides libraries to evaluate these expressions. There are two implementations as of today cel-go and cel-cpp

CEL support has been integrated into Kubernetes at webhook validation, match condition and authorization. This has sparked my interest in learning how CEL is implemented and works internally. This series is not primarily focused on writing CEL rules in Kubernetes but on how CEL itself is implemented.

So let’s write the hello world equivalent of CEL and evaluate a simple expression, we are trying to compare an input number and check if it is equal to 42 or not.

number == 42

If you are not familiar with 42 and the Ultimate Question of Life, the Universe, and Everything. Check out this short video

Prepare the go module

mkdir meaningoflife && cd ./meaningoflife && go mod init hitchhiker
go: creating new go.mod: module hitchhiker

Add main.go

package main

import (
    "fmt"
    "github.com/google/cel-go/cel"
    "log"
)

func main() {
    // define an environment with 
    // a number variable of integer type
    // for your program
    env, err := cel.NewEnv(cel.Variable("number", cel.IntType))
    if err != nil {
        log.Fatalln(err)
    }

    // compile the expression
    // this step as expected generates 
    // Abstract Syntax Tree for the parsed expression
    ast, iss := env.Compile(`number == 42`)
    if iss.Err() != nil {
        log.Fatalln(iss.Err())
    }

    // generate a program using a given AST
    prg, err := env.Program(ast)
    if err != nil {
        log.Fatalln(err)
    }

    // prepare data for your evaluation
    evalInput := map[string]interface{}{
        "number": 69,
    }

    // perform evaluation and capture results
    out, _, err := prg.Eval(evalInput)
    if err != nil {
        log.Fatalln(err)
    }

    fmt.Println(fmt.Sprintf("Is %d the answer for the Ultimate Question of Life, the Universe, and Everything? %v\n", evalInput["number"], out))
}

Build and Run

go mod tidy 
go: finding module for package github.com/google/cel-go/cel
go: found github.com/google/cel-go/cel in github.com/google/cel-go v0.22.1
go build && ./hitchhiker
Is 69 the answer for the Ultimate Question of Life, the Universe, and Everything? false

Modify evalInput number to 42

    // prepare data for your evaluation
    evalInput := map[string]interface{}{
        "number": 42,
    }

build and run again

go build && ./hitchhiker 
Is 42 the answer for the Ultimate Question of Life, the Universe, and Everything? true

In the next article we will explore cel-spec and different properties of Common Expression Language and why turning incompleteness is an advertised aspect of this language.