1. Wstęp
Expr to dynamiczne rozwiązanie konfiguracyjne zaprojektowane dla języka Go, znane ze swojej prostoty składni i potężnych funkcji wydajnościowych. Rdzeń silnika wyrażeń Expr skupia się na bezpieczeństwie, szybkości i intuicyjności, co sprawia, że jest odpowiedni do scenariuszy takich jak kontrola dostępu, filtrowanie danych i zarządzanie zasobami. Gdy jest zastosowany do Go, Expr znacząco zwiększa zdolność aplikacji do obsługi dynamicznych reguł. W przeciwieństwie do interpreterów lub silników skryptów w innych językach, Expr stosuje statyczną kontrolę typów i generuje bajtkod do wykonania, zapewniając zarówno wydajność, jak i bezpieczeństwo.
2. Instalowanie Expr
Możesz zainstalować silnik wyrażeń Expr, używając narzędzia do zarządzania pakietami języka Go go get
:
go get github.com/expr-lang/expr
To polecenie pobierze pliki biblioteki Expr i zainstaluje je w twoim projekcie Go, umożliwiając importowanie i używanie Expr w twoim kodzie Go.
3. Szybki start
3.1. Kompilowanie i Uruchamianie Podstawowych Wyrażeń
Zaczniemy od podstawowego przykładu: napisania prostego wyrażenia, skompilowania go, a następnie uruchomienia, aby uzyskać wynik.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// Kompilacja podstawowego wyrażenia dodawania
program, err := expr.Compile(`2 + 2`)
if err != nil {
panic(err)
}
// Uruchamianie skompilowanego wyrażenia bez przekazywania środowiska, ponieważ tutaj nie są potrzebne zmienne
output, err := expr.Run(program, nil)
if err != nil {
panic(err)
}
// Wydrukowanie wyniku
fmt.Println(output) // Wyświetla 4
}
W tym przykładzie wyrażenie 2 + 2
jest kompilowane do wykonywalnego bajtkodu, który następnie jest wykonywany w celu uzyskania wyniku.
3.2. Użycie Zmiennych w Wyrażeniach
Następnie stworzymy środowisko zawierające zmienne, napiszemy wyrażenie używające tych zmiennych, skompilujemy i uruchomimy to wyrażenie.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// Tworzenie środowiska z zmiennymi
env := map[string]interface{}{
"foo": 100,
"bar": 200,
}
// Kompilacja wyrażenia z użyciem zmiennych ze środowiska
program, err := expr.Compile(`foo + bar`, expr.Env(env))
if err != nil {
panic(err)
}
// Uruchamianie wyrażenia
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
// Wydrukowanie wyniku
fmt.Println(output) // Wyświetla 300
}
W tym przykładzie środowisko env
zawiera zmienne foo
i bar
. Wyrażenie foo + bar
wnioskuje typy foo
i bar
z środowiska podczas kompilacji, a następnie używa wartości tych zmiennych w czasie wykonywania, aby obliczyć wynik wyrażenia.
4. Składnia Expr w Szczegółach
4.1. Zmienne i Literały
Silnik wyrażeń Expr obsługuje zwykłe literały typów danych, w tym liczby, ciągi znaków i wartości logiczne. Literały to wartości danych bezpośrednio zapisane w kodzie, takie jak 42
, "hello"
i true
.
Liczby
W Expr możesz bezpośrednio zapisywać liczby całkowite i zmiennoprzecinkowe:
42 // Reprezentuje liczbę całkowitą 42
3.14 // Reprezentuje liczbę zmiennoprzecinkową 3.14
Ciągi znaków
Literały łańcuchów znaków są zamknięte w podwójnych cudzysłowach "
lub tzw. backticks ``. Na przykład:
"hello, world" // Łańcuch znaków zamknięty w podwójnych cudzysłowach, obsługuje znaki ucieczki
`hello, world` // Łańcuch znaków zamknięty w backtickach, zachowuje format łańcucha znaków bez obsługi znaków ucieczki
Wartości logiczne
Istnieją tylko dwie wartości logiczne, true
i false
, reprezentujące logiczne prawda i fałsz:
true // Wartość logiczna prawda
false // Wartość logiczna fałsz
Zmienne
Expr pozwala również na definiowanie zmiennych w środowisku, a następnie odwoływanie się do tych zmiennych w wyrażeniu. Na przykład:
env := map[string]interface{}{
"wiek": 25,
"imie": "Alice",
}
Następnie w wyrażeniu możesz odwołać się do wiek
i imie
:
wiek > 18 // Sprawdź, czy wiek jest większy niż 18
imie == "Alice" // Sprawdź, czy imie jest równe "Alice"
4.2. Operatory
Silnik wyrażeń Expr obsługuje różne operatory, w tym operatory arytmetyczne, logiczne, porównania i zbiorów, itp.
Operatory arytmetyczne i logiczne
Operatory arytmetyczne obejmują dodawanie (+
), odejmowanie (-
), mnożenie (*
), dzielenie (/
) i modulo (%
). Operatory logiczne obejmują logiczne AND (&&
), logiczne OR (||
) i logiczne NOT (!
), na przykład:
2 + 2 // Wynik to 4
7 % 3 // Wynik to 1
!true // Wynik to false
wiek >= 18 && imie == "Alice" // Sprawdź, czy wiek nie jest mniejszy niż 18 i czy imię jest równe "Alice"
Operatory porównania
Operatory porównania obejmują równy (==
), różny od (!=
), mniejszy niż (<
), mniejszy lub równy (<=
), większy niż (>
), i większy lub równy (>=
), używane do porównywania dwóch wartości:
wiek == 25 // Sprawdź, czy wiek jest równy 25
wiek != 18 // Sprawdź, czy wiek nie jest równy 18
wiek > 20 // Sprawdź, czy wiek jest większy niż 20
Operatory zbiorów
Expr udostępnia także operatory do pracy ze zbiorami, takie jak in
do sprawdzania, czy element znajduje się w zbiorze. Zbiory mogą być tablicami, wycinkami (slices) lub mapami:
"user" in ["user", "admin"] // true, ponieważ "user" znajduje się w tablicy
3 in {1: true, 2: false} // false, ponieważ 3 nie jest kluczem w mapie
Istnieją także zaawansowane funkcje operacji na zbiorach, takie jak all
, any
, one
i none
, które wymagają użycia anonimowych funkcji (lambda):
all(tweets, {.Len <= 240}) // Sprawdź, czy pole Len we wszystkich tweetach nie przekracza 240
any(tweets, {.Len > 200}) // Sprawdź, czy istnieje pole Len w tweetach, które przekracza 200
Operator elementu
W języku wyrażeń Expr operator elementu pozwala nam uzyskać dostęp do właściwości struct
w języku Go. Ta funkcja pozwala Expr bezpośrednio manipulować złożonymi strukturami danych, co czyni ją bardzo elastyczną i praktyczną.
Użycie operatora elementu jest bardzo proste, wystarczy użyć operatora .
po którym podajemy nazwę właściwości. Na przykład, jeśli mamy następującą struct
:
type User struct {
Name string
Age int
}
Można napisać wyrażenie, aby uzyskać dostęp do właściwości Name
struktury User
w ten sposób:
env := map[string]interface{}{
"user": User{Name: "Alice", Age: 25},
}
kod := `user.Name`
program, err := expr.Compile(kod, expr.Env(env))
if err != nil {
panic(err)
}
wynik, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(wynik) // Wynik: Alice
Obsługa wartości nil
Podczas dostępu do właściwości może się zdarzyć, że obiekt jest nil
. Expr zapewnia bezpieczny dostęp do właściwości, dzięki czemu nawet jeśli struktura lub zagnieżdżona właściwość jest nil
, nie spowoduje to błędu w czasie wykonania.
Użyj operatora ?.
do odwoływania się do właściwości. Jeśli obiekt jest nil, zwróci nil zamiast generować błąd.
author.User?.Name
Równoważne wyrażenie
author.User != nil ? author.User.Name : nil
Użycie operatora ??
służy głównie do zwracania wartości domyślnych:
author.User?.Name ?? "Anonymous"
Równoważne wyrażenie
author.User != nil ? author.User.Name : "Anonymous"
Operator potoku
Operator potoku (|
) w Expr służy do przekazywania wyniku jednego wyrażenia jako parametru do innego wyrażenia. Jest to podobne do działania operatora potoku w powłoce Unix, umożliwiające łańcuchowe łączenie wielu modułów funkcjonalnych w procesie przetwarzania. W Expr użycie tego operatora może tworzyć wyrażenia bardziej klarowne i zwięzłe.
Na przykład, jeśli mamy funkcję do pobrania imienia użytkownika i szablon na powitanie:
env := map[string]interface{}{
"user": User{Name: "Bob", Age: 30},
"get_name": func(u User) string { return u.Name },
"greet_msg": "Cześć, %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) // Wynik: Cześć, Bob!
W tym przykładzie najpierw pobieramy imię użytkownika przez get_name(user)
, a następnie przekazujemy imię do funkcji sprintf
używając operatora potoku |
, aby wygenerować ostateczną wiadomość powitalną.
Użycie operatora potoku może zmodularyzować nasz kod, poprawić jego ponowne wykorzystanie oraz ułatwić czytelność wyrażeń.
4.3 Funkcje
Expr obsługuje wbudowane funkcje oraz funkcje niestandardowe, co sprawia, że wyrażenia stają się bardziej potężne i elastyczne.
Użycie wbudowanych funkcji
Wbudowane funkcje takie jak len
, all
, none
, any
, itp. mogą być używane bezpośrednio w wyrażeniu.
// Przykład użycia wbudowanej funkcji
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
panic(err)
}
// Uwaga: tutaj env musi zawierać zmienną users, a każdy użytkownik musi mieć właściwość Age
output, err := expr.Run(program, env)
fmt.Print(output) // Jeśli wszyscy użytkownicy w env są w wieku 18 lat lub starsi, zwróci true
Jak definiować i używać funkcji niestandardowych
W Expr możesz tworzyć funkcje niestandardowe, przekazując definicje funkcji w mapowaniu środowiskowym.
// Przykład funkcji niestandardowej
env := map[string]interface{}{
"greet": func(name string) string {
return fmt.Sprintf("Cześć, %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) // Zwraca Cześć, World!
Podczas korzystania z funkcji w Expr, możesz zmodularyzować swój kod i włączyć w wyrażenia złożoną logikę. Poprzez połączenie zmiennych, operatorów i funkcji, Expr staje się potężnym i łatwym w użyciu narzędziem. Pamiętaj zawsze o zachowaniu bezpieczeństwa typów przy budowaniu środowiska Expr i uruchamianiu wyrażeń.
5. Dokumentacja wbudowanych funkcji
Silnik wyrażeń Expr udostępnia programistom bogaty zestaw wbudowanych funkcji do obsługi różnorodnych złożonych scenariuszy. Poniżej przedstawimy te wbudowane funkcje i ich użycie.
all
Funkcja all
służy do sprawdzenia, czy wszystkie elementy w kolekcji spełniają określone warunki. Przyjmuje dwa parametry: kolekcję i wyrażenie warunkowe.
// Sprawdzenie, czy wszystkie tweety mają długość treści mniejszą niż 240 znaków
code := `all(tweets, len(.Content) < 240)`
any
Podobnie jak funkcja all
, funkcja any
służy do sprawdzenia, czy którykolwiek element w kolekcji spełnia warunek.
// Sprawdzenie, czy którykolwiek z tweeta ma długość treści większą niż 240 znaków
code := `any(tweets, len(.Content) > 240)`
none
Funkcja none
służy do sprawdzenia, czy żaden z elementów w kolekcji nie spełnia warunku.
// Upewnienie się, że żaden z tweety nie jest oznaczony jako powtórzony
code := `none(tweets, .IsRepeated)`
one
Funkcja one
służy do potwierdzenia, że tylko jeden element w kolekcji spełnia warunek.
// Sprawdzenie, czy tylko jeden tweet zawiera określone słowo kluczowe
code := `one(tweets, contains(.Content, "keyword"))`
filter
Funkcja filter
służy do filtrowania elementów kolekcji spełniających określony warunek.
// Filtrowanie wszystkich oznaczonych jako priorytetowe tweety
code := `filter(tweets, .IsPriority)`
map
Funkcja map
służy do transformowania elementów w kolekcji zgodnie z określoną metodą.
// Formatowanie czasu publikacji wszystkich tweety
code := `map(tweets, {.PublishTime: Format(.Date)})`
len
Funkcja len
służy do zwracania długości kolekcji lub ciągu znaków.
// Pobieranie długości nazwy użytkownika
code := `len(username)`
contains
Funkcja contains
służy do sprawdzania, czy ciąg znaków zawiera podciąg lub czy kolekcja zawiera określony element.
// Sprawdzenie, czy nazwa użytkownika zawiera niedozwolone znaki
code := `contains(username, "niedozwolone znaki")`
Wymienione powyżej funkcje są tylko częścią wbudowanych funkcji dostarczanych przez silnik wyrażeń Expr. Dzięki tym potężnym funkcjom możesz elastyczniej i efektywniej zarządzać danymi i logiką. Aby uzyskać bardziej szczegółową listę funkcji oraz instrukcje dotyczące używania, zapoznaj się z oficjalną dokumentacją Expr.