1. はじめに

Expr は、シンプルな構文と強力なパフォーマンス機能で知られるGo言語向けのダイナミックな構成ソリューションです。Expr 式エンジンのコアは安全性、速度、直感性に焦点を当てており、アクセス制御、データフィルタリング、リソース管理などのシナリオに適しています。Go言語に適用すると、Expr はアプリケーションが動的なルールを処理する能力を大幅に向上させます。他の言語のインタプリタやスクリプトエンジンとは異なり、Expr は静的型チェックを採用し、実行用のバイトコードを生成してパフォーマンスとセキュリティの両方を確保しています。

2. Expr のインストール

Go言語のパッケージ管理ツール go get を使用して、Expr 式エンジンをインストールすることができます:

go get github.com/expr-lang/expr

このコマンドにより、Expr ライブラリファイルがダウンロードされ、Goプロジェクトにインストールされます。これにより、GoコードでExpr をインポートして使用することができます。

3. クイックスタート

3.1 基本的な式のコンパイルと実行

まずは基本的な例から始めましょう:簡単な式を書いて、それをコンパイルし、そして実行してその結果を得ます。

package main

import (
	"fmt"
	"github.com/expr-lang/expr"
)

func main() {
	// 基本的な加算式のコンパイル
	program, err := expr.Compile(`2 + 2`)
	if err != nil {
		panic(err)
	}

	// 変数が必要ないため、環境を渡さずにコンパイルされた式を実行する
	output, err := expr.Run(program, nil)
	if err != nil {
		panic(err)
	}

	// 結果の出力
	fmt.Println(output)  // 4 が出力される
}

この例では、式 2 + 2 が実行可能なバイトコードにコンパイルされ、それが実行されて出力が生成されます。

3.2 変数を使用した式の作成

次に、変数を含む環境を作成し、その変数を使用する式を書いてコンパイルし、実行します。

package main

import (
	"fmt"
	"github.com/expr-lang/expr"
)

func main() {
	// 変数を含む環境の作成
	env := map[string]interface{}{
		"foo": 100,
		"bar": 200,
	}

	// 環境の変数を使用する式のコンパイル
	program, err := expr.Compile(`foo + bar`, expr.Env(env))
	if err != nil {
		panic(err)
	}

	// 式の実行
	output, err := expr.Run(program, env)
	if err != nil {
		panic(err)
	}

	// 結果の出力
	fmt.Println(output)  // 300 が出力される
}

この例では、環境 env には変数 foobar が含まれています。式 foo + bar はコンパイル時に環境から foobar の型を推論し、実行時にこれらの変数の値を使用して式の結果を評価します。

4. 詳細なExpr構文

4.1 変数とリテラル

Expr 式エンジンは、数値、文字列、ブール値などの一般的なデータ型リテラルを扱うことができます。リテラルはコードに直接書かれたデータ値で、例えば 42"hello"true などがあります。

数値

Expr では、整数や浮動小数点数を直接書くことができます:

42      // 整数 42 を表します
3.14    // 浮動小数点数 3.14 を表します

文字列

文字列リテラルは、二重引用符 " またはバッククォート `` で囲まれます。例:

"hello, world" // 二重引用符で囲まれた文字列。エスケープ文字がサポートされています
`hello, world` // バッククォートで囲まれた文字列。エスケープ文字をサポートせず、文字列の形式を維持します

ブール値

truefalse の2つのブール値があり、論理的な真と偽を表します:

true   // ブール値 true
false  // ブール値 false

変数

Expr では、環境に変数を定義し、その変数を式で参照することができます。例:

env := map[string]interface{}{
    "age": 25,
    "name": "Alice",
}

そして、式では agename を参照できます:

age > 18  // age が 18 より大きいかをチェックする
name == "Alice"  // name が "Alice" と等しいかを判定する

4.2 演算子

Expr 式エンジンは、算術演算子、論理演算子、比較演算子、集合演算子など、さまざまな演算子をサポートしています。

算術および論理演算子

算術演算子には、加算(+)、減算(-)、乗算(*)、除算(/)、および剰余(%)が含まれます。論理演算子には、論理AND(&&)、論理OR(||)、論理NOT(!)が含まれます。例えば:

2 + 2 // 結果は 4
7 % 3 // 結果は 1
!true // 結果は false
age >= 18 && name == "Alice" // 年齢が18未満でないことをチェックし、名前が"Alice"と等しいかをチェックします

比較演算子

比較演算子には、等しい(==)、等しくない(!=)、より少ない(<)、以下(<=)、より大きい(>)、および以上(>=)が含まれ、2つの値を比較するために使用されます:

age == 25 // 年齢が25と等しいかをチェックします
age != 18 // 年齢が18と異なるかをチェックします
age > 20  // 年齢が20より大きいかをチェックします

集合演算子

Exprには、集合との操作に関するいくつかの演算子もあります。たとえば、「in」は要素が集合に含まれているかどうかを確認します。集合には配列、スライス、またはマップが使用できます:

"user" in ["user", "admin"]  // true, "user" は配列内に含まれているため
3 in {1: true, 2: false}     // false, 3 はマップのキーではないため

また、「all」、「any」、「one」、「none」といった高度な集合演算関数もあり、これらは匿名関数(ラムダ)の使用を必要とします:

all(tweets, {.Len <= 240})  // 全てのツイートのLenフィールドが240を超えないかをチェックします
any(tweets, {.Len > 200})   // ツイート内にLenフィールドが200を超えるものが存在するかをチェックします

メンバ演算子

Expr式言語では、メンバ演算子を使用してGo言語のstructのプロパティにアクセスできます。この機能により、Exprは複雑なデータ構造を直接操作できるため、非常に柔軟で実用的です。

メンバ演算子の使用は非常に簡単で、プロパティ名に続けて.演算子を使用します。たとえば、次のようなstructがある場合:

type User struct {
    Name string
    Age  int
}

User構造体のNameプロパティにアクセスする式を次のように記述できます:

env := map[string]interface{}{
    "user": User{Name: "Alice", Age: 25},
}

code := `user.Name`

program, err := expr.Compile(code, expr.Env(env))
if err != nil {
    panic(err)
}

output, err := expr.Run(program, env)
if err != nil {
    panic(err)
}

fmt.Println(output) // 出力: Alice

nil値の処理

プロパティにアクセスする際に、オブジェクトがnilである場合があります。Exprは安全なプロパティアクセスを提供するため、構造体やネストされたプロパティがnilでもランタイムパニックエラーをスローしません。

?.演算子を使用してプロパティに参照します。オブジェクトがnilの場合、エラーをスローする代わりにnilを返します。

author.User?.Name

同等の式

author.User != nil ? author.User.Name : nil

??演算子の使用は主にデフォルト値を返すために行われます:

author.User?.Name ?? "Anonymous"

同等の式

author.User != nil ? author.User.Name : "Anonymous"

パイプ演算子

Exprのパイプ演算子(|)は、1つの式の結果を別の式のパラメータとして渡すために使用されます。これはUnixシェルのパイプ演算と似ており、複数の機能モジュールを連鎖させて処理パイプラインを形成することができます。Exprでは、これを使用することで、より明確で簡潔な式を作成することができます。

例えば、ユーザーの名前を取得する関数と歓迎メッセージのテンプレートがあるとします:

env := map[string]interface{}{
    "user":      User{Name: "Bob", Age: 30},
    "get_name":  func(u User) string { return u.Name },
    "greet_msg": "Hello, %s!",
}

code := `get_name(user) | sprintf(greet_msg)`

program, err := expr.Compile(code, expr.Env(env))
if err != nil {
    panic(err)
}

output, err := expr.Run(program, env)
if err != nil {
    panic(err)
}

fmt.Println(output) // 出力: Hello, Bob!

この例では、まず get_name(user) を使ってユーザーの名前を取得し、次にパイプ演算子 | を使用してその名前を sprintf 関数に渡し、最終的な歓迎メッセージを生成しています。

パイプ演算子を使用することで、コードをモジュール化し、コードの再利用性を向上させ、式をより読みやすくします。

4.3 関数

Exprは組み込み関数とカスタム関数をサポートしており、式をより強力で柔軟なものにしています。

組み込み関数の使用

lenallnoneanyなどの組み込み関数は、式内で直接使用することができます。

// 組み込み関数の使用例
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
    panic(err)
}

// 注意: ここではenvにusers変数が含まれている必要があり、各ユーザーにはAgeプロパティがある必要があります
output, err := expr.Run(program, env)
fmt.Print(output) // envにある全てのユーザーが18歳以上ならtrueを返します

カスタム関数の定義と使用方法

Exprでは、環境マッピングに関数定義を渡すことでカスタム関数を作成することができます。

// カスタム関数の例
env := map[string]interface{}{
    "greet": func(name string) string {
        return fmt.Sprintf("Hello, %s!", name)
    },
}

program, err := expr.Compile(`greet("World")`, expr.Env(env))
if err != nil {
    panic(err)
}

output, err := expr.Run(program, env)
fmt.Print(output) // Hello, World! を返します

Exprで関数を使用する際は、変数、演算子、関数を組み合わせて強力で使いやすいツールにします。Exprの環境の構築や式の実行時には常に型の安全性を確保することを忘れないでください。

5. 組み込み関数のドキュメント

Expr 式エンジンは、開発者に様々な複雑なシナリオを処理する豊富な組み込み関数を提供しています。以下では、これらの組み込み関数とその使用方法について詳しく説明します。

all

all 関数はコレクション内のすべての要素が与えられた条件を満たしているかどうかを確認するために使用されます。2つのパラメータ、つまりコレクションと条件式を取ります。

// すべてのツイートが内容の長さが240未満かどうかをチェックします
code := `all(tweets, len(.Content) < 240)`

any

all と同様に、any 関数はコレクション内の要素のいずれかが条件を満たしているかをチェックするために使用されます。

// どれかのツイートが内容の長さが240より大きいかどうかをチェックします
code := `any(tweets, len(.Content) > 240)`

none

none 関数はコレクション内の要素が条件を満たさないことを確認するために使用されます。

// リピートされたツイートがないことを確認します
code := `none(tweets, .IsRepeated)`

one

one 関数はコレクション内の要素が1つだけ条件を満たしていることを確認するために使用されます。

// 1つだけのツイートが特定のキーワードを含んでいるかをチェックします
code := `one(tweets, contains(.Content, "keyword"))`

filter

filter 関数は指定された条件を満たすコレクションの要素をフィルタリングするために使用されます。

// 優先度がつけられたツイートをフィルタリングします
code := `filter(tweets, .IsPriority)`

map

map 関数はコレクション内の要素を指定された方法に従って変換するために使用されます。

// すべてのツイートの投稿時間をフォーマットします
code := `map(tweets, {.PublishTime: Format(.Date)})`

len

len関数は、コレクションまたは文字列の長さを返すために使用されます。

// ユーザー名の長さを取得
code := `len(username)`

contains

contains関数は、文字列が特定の部分文字列を含むか、コレクションが特定の要素を含むかどうかをチェックするために使用されます。

// ユーザー名に違法な文字が含まれているかをチェック
code := `contains(username, "違法な文字")`

上記は、Expr式エンジンによって提供される組み込み関数の一部です。これらのパワフルな関数を使用すると、データとロジックを柔軟かつ効率的に処理できます。詳細な関数の一覧と使用方法については、公式Exprドキュメントを参照してください。