1. Giới thiệu
Expr là một giải pháp cấu hình động được thiết kế cho ngôn ngữ Go, nổi tiếng với cú pháp đơn giản và các tính năng hiệu suất mạnh mẽ. Lõi của máy tính biểu thức Expr tập trung vào tính an toàn, tốc độ và tính trực quan, làm cho nó phù hợp cho các kịch bản như kiểm soát truy cập, lọc dữ liệu và quản lý tài nguyên. Khi áp dụng vào Go, Expr cải thiện đáng kể khả năng xử lý các quy tắc động của ứng dụng. Khác với trình thông dịch hoặc máy chạy script trong các ngôn ngữ khác, Expr áp dụng kiểm tra kiểu tĩnh và tạo bytecode để thực thi, đảm bảo cả hiệu suất và an ninh.
2. Cài đặt Expr
Bạn có thể cài đặt máy tính biểu thức Expr bằng cách sử dụng công cụ quản lý gói ngôn ngữ Go go get
:
go get github.com/expr-lang/expr
Lệnh này sẽ tải các tệp thư viện Expr và cài đặt chúng vào dự án Go của bạn, cho phép bạn nhập và sử dụng Expr trong mã Go của bạn.
3. Bắt Đầu Nhanh
3.1 Biên dịch và Chạy Biểu thức Cơ Bản
Hãy bắt đầu với một ví dụ cơ bản: viết một biểu thức đơn giản, biên dịch nó và sau đó chạy nó để có được kết quả.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// Biên dịch một biểu thức cộng cơ bản
chương_trình, lỗi := expr.Compile(`2 + 2`)
if lỗi != nil {
panic(lỗi)
}
// Chạy biểu thức đã biên dịch mà không truyền môi trường, vì không cần biến ở đây
kết_quả, lỗi := expr.Run(chương_trình, nil)
if lỗi != nil {
panic(lỗi)
}
// In kết quả
fmt.Println(kết_quả) // Kết quả 4
}
Trong ví dụ này, biểu thức 2 + 2
được biên dịch thành bytecode thực thi, sau đó được thực thi để tạo ra kết quả.
3.2 Sử dụng Biểu thức Biến
Tiếp theo, chúng ta sẽ tạo một môi trường chứa biến, viết một biểu thức sử dụng các biến này, biên dịch và chạy biểu thức này.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// Tạo một môi trường chứa các biến
env := map[string]interface{}{
"foo": 100,
"bar": 200,
}
// Biên dịch một biểu thức sử dụng các biến từ môi trường
program, lỗi := expr.Compile(`foo + bar`, expr.Env(env))
if lỗi != nil {
panic(lỗi)
}
// Chạy biểu thức
kết_quả, lỗi := expr.Run(program, env)
if lỗi != nil {
panic(lỗi)
}
// In kết quả
fmt.Println(kết_quả) // Kết quả 300
}
Trong ví dụ này, môi trường env
chứa các biến foo
và bar
. Biểu thức foo + bar
suy ra các kiểu của foo
và bar
từ môi trường trong quá trình biên dịch, và sử dụng giá trị của các biến này tại thời gian chạy để đánh giá kết quả của biểu thức.
4. Cú Pháp của Expr Chi Tiết
4.1 Biến và Hằng Số
Máy tính biểu thức Expr có thể xử lý các hằng số của các kiểu dữ liệu phổ biến, bao gồm số, chuỗi và giá trị boolean. Hằng số là giá trị dữ liệu được viết trực tiếp trong mã, chẳng hạn như 42
, "hello"
, và true
.
Số
Trong Expr, bạn có thể viết trực tiếp số nguyên và số thập phân:
42 // Biểu diễn số nguyên 42
3.14 // Biểu diễn số thập phân 3.14
Chuỗi
Hằng số chuỗi được bao quanh bởi dấu ngoặc kép "
hoặc dấu huyền ``. Ví dụ:
"xin chào, thế giới" // Chuỗi được bao quanh bởi dấu ngoặc kép, hỗ trợ ký tự esc
`xin chào, thế giới` // Chuỗi được bao quanh bởi dấu huyền, giữ nguyên định dạng chuỗi mà không hỗ trợ ký tự esc
Giá trị Boolean
Chỉ có hai giá trị boolean, true
và false
, biểu thị cho giá trị logic đúng và sai:
true // Giá trị boolean đúng
false // Giá trị boolean sai
Biến
Expr cũng cho phép định nghĩa các biến trong môi trường, sau đó tham chiếu đến các biến này trong biểu thức. Ví dụ:
môi_trường := map[string]interface{}{
"tuổi": 25,
"tên": "Alice",
}
Sau đó trong biểu thức, bạn có thể tham chiếu đến tuổi
và tên
:
tuổi > 18 // Kiểm tra xem tuổi có lớn hơn 18 không
tên == "Alice" // Xác định xem tên có bằng "Alice" không
4.2 Toán Tử
Máy tính biểu thức Expr hỗ trợ các toán tử khác nhau, bao gồm toán tử số học, toán tử logic, toán tử so sánh và toán tử set, v.v.
Toán tử Số học và Logic
Toán tử số học bao gồm phép cộng (+
), phép trừ (-
), phép nhân (*
), phép chia (/
), và phép modulo (%
). Toán tử logic bao gồm AND logic (&&
), OR logic (||
), và NOT logic (!
), ví dụ:
2 + 2 // Kết quả là 4
7 % 3 // Kết quả là 1
!true // Kết quả là false
age >= 18 && name == "Alice" // Kiểm tra nếu tuổi không nhỏ hơn 18 và nếu tên bằng "Alice"
Toán tử So Sánh
Toán tử so sánh bao gồm bằng (==
), khác bằng (!=
), nhỏ hơn (<
), nhỏ hơn hoặc bằng (<=
), lớn hơn (>
), và lớn hơn hoặc bằng (>=
), được sử dụng để so sánh hai giá trị:
age == 25 // Kiểm tra nếu tuổi bằng 25
age != 18 // Kiểm tra nếu tuổi không bằng 18
age > 20 // Kiểm tra nếu tuổi lớn hơn 20
Toán tử Set
Expr cũng cung cấp một số toán tử để làm việc với các tập hợp, như in
để kiểm tra nếu một phần tử có trong tập hợp. Các tập hợp có thể là mảng, slice, hoặc bản đồ (map):
"user" in ["user", "admin"] // đúng, vì "user" có trong mảng
3 in {1: true, 2: false} // sai, vì 3 không phải là một khóa trong bản đồ
Ngoài ra còn có một số hàm toán tử set nâng cao, như all
, any
, one
, và none
, yêu cầu sử dụng hàm vô danh (lambda):
all(tweets, {.Len <= 240}) // Kiểm tra nếu trường Len của tất cả tweet không vượt quá 240
any(tweets, {.Len > 200}) // Kiểm tra nếu tồn tại một trường Len trong tweet vượt quá 200
Toán tử thành viên
Trong ngôn ngữ biểu thức Expr, toán tử thành viên cho phép chúng ta truy cập các thuộc tính của struct
trong ngôn ngữ Go. Tính năng này cho phép Expr trực tiếp thao tác các cấu trúc dữ liệu phức tạp, làm cho nó rất linh hoạt và thực tế.
Sử dụng toán tử thành viên rất đơn giản, chỉ cần sử dụng toán tử .
theo sau là tên thuộc tính. Ví dụ, nếu chúng ta có cấu trúc struct
như sau:
type User struct {
Name string
Age int
}
Bạn có thể viết một biểu thức để truy cập thuộc tính Name
của cấu trúc User
như sau:
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) // Output: Alice
Xử lý Giá trị null
Khi truy cập các thuộc tính, bạn có thể gặp phải tình huống mà đối tượng là nil
. Expr cung cấp việc truy cập thuộc tính an toàn, vì vậy ngay cả khi cấu trúc hoặc thuộc tính lồng nhau là nil
, nó cũng sẽ không gây ra lỗi panic ở thời gian chạy.
Sử dụng toán tử ?.
để tham chiếu thuộc tính. Nếu đối tượng là nil, nó sẽ trả về nil thay vì gây ra lỗi.
author.User?.Name
Biểu thức tương đương
author.User != nil ? author.User.Name : nil
Sử dụng toán tử ??
chủ yếu là để trả về giá trị mặc định:
author.User?.Name ?? "Anonymous"
Biểu thức tương đương
author.User != nil ? author.User.Name : "Anonymous"
Toán tử Pipe
Toán tử pipe (|
) trong Expr được sử dụng để chuyển kết quả của một biểu thức như một tham số đầu vào cho biểu thức khác. Điều này tương tự như phép pipe trong Unix shell, cho phép nhiều module chức năng được ghép lại với nhau để tạo thành một đường ống xử lý. Trong Expr, việc sử dụng toán tử pipe có thể tạo ra các biểu thức rõ ràng và ngắn gọn hơn.
Ví dụ, nếu chúng ta có một hàm để lấy tên của người dùng và một mẫu cho tin nhắn chào mừng:
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) // Output: Hello, Bob!
Trong ví dụ này, chúng ta trước tiên lấy tên của người dùng thông qua get_name(user)
, sau đó chuyển tên đó cho hàm sprintf
sử dụng toán tử pipe |
để tạo ra tin nhắn chào mừng cuối cùng.
Việc sử dụng toán tử pipe có thể modul hóa mã của chúng ta, cải thiện khả năng tái sử dụng mã và làm cho các biểu thức dễ đọc hơn.
4.3 Hàm
Expr hỗ trợ các hàm tích hợp sẵn và hàm tùy chỉnh, làm cho các biểu thức mạnh mẽ và linh hoạt hơn.
Sử dụng Hàm Tích Hợp Sẵn
Các hàm tích hợp sẵn như len
, all
, none
, any
, v.v.. có thể được sử dụng trực tiếp trong biểu thức.
// Ví dụ về việc sử dụng một hàm tích hợp sẵn
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
panic(err)
}
// Lưu ý: ở đây env cần chứa biến users, và mỗi người dùng phải có thuộc tính Age
output, err := expr.Run(program, env)
fmt.Print(output) // Nếu tất cả người dùng trong env đều đủ 18 tuổi trở lên, nó sẽ trả về true
Cách định nghĩa và sử dụng hàm tùy chỉnh
Trong Expr, bạn có thể tạo các hàm tùy chỉnh bằng cách truyền các định nghĩa hàm vào bản đồ môi trường.
// Ví dụ về hàm tùy chỉnh
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) // Trả về Hello, World!
Khi sử dụng các hàm trong Expr, bạn có thể modul hóa mã của mình và tích hợp logic phức tạp vào các biểu thức. Bằng cách kết hợp biến, toán tử và hàm, Expr trở thành một công cụ mạnh mẽ và dễ sử dụng. Hãy luôn đảm bảo an toàn kiểu khi xây dựng môi trường Expr và chạy các biểu thức.
5. Tài Liệu Hàm Tích Hợp Sẵn
Bộ máy biểu thức Expr cung cấp cho các nhà phát triển một bộ hàm tích hợp sẵn phong phú để xử lý các tình huống phức tạp khác nhau. Dưới đây là chi tiết về các hàm tích hợp sẵn này và cách sử dụng chúng.
all
Hàm all
có thể được sử dụng để kiểm tra xem tất cả các phần tử trong một bộ sưu tập có thỏa mãn một điều kiện nhất định hay không. Nó lấy hai tham số: bộ sưu tập và biểu thức điều kiện.
// Kiểm tra xem tất cả các tweet có độ dài nội dung nhỏ hơn 240 kí tự không
code := `all(tweets, len(.Content) < 240)`
any
Tương tự như all
, hàm any
được sử dụng để kiểm tra xem có bất kỳ phần tử nào trong một bộ sưu tập thỏa mãn một điều kiện hay không.
// Kiểm tra xem có bất kỳ tweet nào có độ dài nội dung lớn hơn 240 kí tự không
code := `any(tweets, len(.Content) > 240)`
none
Hàm none
được sử dụng để kiểm tra xem không có phần tử nào trong một bộ sưu tập thỏa mãn một điều kiện hay không.
// Đảm bảo không có tweet nào bị lặp lại
code := `none(tweets, .IsRepeated)`
one
Hàm one
được sử dụng để xác nhận rằng chỉ có một phần tử trong một bộ sưu tập thỏa mãn một điều kiện.
// Kiểm tra xem chỉ có một tweet chứa một từ khóa cụ thể
code := `one(tweets, contains(.Content, "keyword"))`
filter
Hàm filter
có thể lọc ra các phần tử trong một bộ sưu tập thỏa mãn một điều kiện nhất định.
// Lọc ra tất cả các tweet được đánh dấu là ưu tiên
code := `filter(tweets, .IsPriority)`
map
Hàm map
được sử dụng để biến đổi các phần tử trong một bộ sưu tập theo một phương pháp cụ thể.
// Định dạng thời gian đăng tải của tất cả các tweet
code := `map(tweets, {.PublishTime: Format(.Date)})`
len
(độ dài)
Hàm len
được sử dụng để trả về độ dài của một tập hợp hoặc chuỗi.
// Lấy độ dài của tên người dùng
code := `len(username)`
contains
(chứa)
Hàm contains
được sử dụng để kiểm tra xem một chuỗi có chứa một chuỗi con hay không hoặc nếu một tập hợp có chứa một phần tử cụ thể.
// Kiểm tra xem tên người dùng có chứa ký tự cấm hay không
code := `contains(username, "ký tự cấm")`
Những hàm được đề cập ở trên chỉ là một phần của các hàm tích hợp được cung cấp bởi trình điều khiển biểu thức Expr. Với những hàm mạnh mẽ này, bạn có thể xử lý dữ liệu và logic một cách linh hoạt và hiệu quả hơn. Để biết danh sách chi tiết hơn về các hàm và hướng dẫn sử dụng, vui lòng tham khảo tài liệu chính thức về Expr.