1. การแนะนำ

Expr เป็นโซลูชันการกำหนดค่าแบบไดนามิกที่ออกแบบมาสำหรับภาษา Go ซึ่งโดดเด่นด้วยไวยากรง่าย และคุณสมบัติประสิทธิภาพที่มีอยู่ ฝั่งหลักของเครื่องมือการแสดงผล Expr มุ่งไปที่ความปลอดภัย ความเร็ว และความสะดวก ซึ่งทำให้เหมาะสำหรับสถานการณ์ทั้งหมด เช่น การควบคุมการเข้าถึง การกรองข้อมูล และการจัดการทรัพยากร การนำ Expr มาใช้ใน Go จะเสริมความสามารถของแอปพลิเคชันในการจัดการกฎไดนามิกอย่างมาก ไม่เหมือนกับตัวแปลหรือเครื่องมือสคริปต์ในภาษาอื่น ที่ Expr ใช้การตรวจสอบชนิดของข้อมูลแบบสถิตและสร้างไบต์โค้ดสำหรับการประมวลผล ทำให้มั่นใจได้ทั้งการทำงานและความปลอดภัย

2. การติดตั้ง Expr

คุณสามารถติดตั้งเครื่องมือการแสดงผล Expr โดยใช้เครื่องมือการจัดการแพ็กเกจของ Go language คือ go get:

go get github.com/expr-lang/expr

คำสั่งนี้จะดาวน์โหลดไฟล์ไลบรารี Expr และติดตั้งไฟล์เหล่านี้ลงในโปรเจค Go ของคุณ ทำให้คุณสามารถนำ Expr เข้ามาใช้ในโค้ด Go ของคุณได้

3. เริ่มต้นอย่างรวดเร็ว

3.1 คอมไพล์และรันนิ่งส่วนพื้นฐานขององค์ประกอบ

เริ่มต้นด้วยตัวอย่างพื้นฐาน: เขียนสมการง่าย คอมไพล์และรันเพื่อรับผลลัพธ์

package main

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

func main() {
	// คอมไพล์สมการการบวกพื้นฐาน
	โปรแกรม, ข้อผิดพลาด := expr.Compile(`2 + 2`)
	if ข้อผิดพลาด != nil {
		panic(ข้อผิดพลาด)
	}

	// รันสมการที่คอมไพล์ไว้โดยไม่ต้องส่ง environment เนื่องจากไม่จำเป็นต้องใช้ตัวแปรที่นี่
	ผลลัพธ์, ข้อผิดพลาด := expr.Run(โปรแกรม, nil)
	if ข้อผิดพลาด != nil {
		panic(ข้อผิดพลาด)
	}

	// พิมพ์ผลลัพธ์
	fmt.Println(ผลลัพธ์)  // แสดงผลเป็น 4
}

ในตัวอย่างนี้ สมการ 2 + 2 ถูกคอมไพล์เป็นไบต์โค้ดที่สามารถรันได้เพื่อได้ผลลัพธ์

3.2 การใช้งานสมการที่มีตัวแปร

ต่อมา เราจะสร้าง environment ที่มีตัวแปร และเขียนสมการโดยใช้ตัวแปรเหล่านั้น จากนั้นคอมไพล์และรันสมการนี้

package main

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

func main() {
	// สร้าง environment ที่มีตัวแปร
	env := map[string]interface{}{
		"foo": 100,
		"bar": 200,
	}

	// คอมไพล์สมการที่ใช้ตัวแปรจาก environment
	โปรแกรม, ข้อผิดพลาด := expr.Compile(`foo + bar`, expr.Env(env))
	if ข้อผิดพลาด != nil {
		panic(ข้อผิดพลาด)
	}

	// รันสมการ
	ผลลัพธ์, ข้อผิดพลาด := expr.Run(โปรแกรม, env)
	if ข้อผิดพลาด != nil {
		panic(ข้อผิดพลาด)
	}

	// พิมพ์ผลลัพธ์
	fmt.Println(ผลลัพธ์)  // แสดงผลเป็น 300
}

ในตัวอย่างนี้ environment env มีตัวแปร foo และ bar สมการ foo + bar จะใช้ประเภทของ foo และ bar จาก environment ในขณะคอมไพล์ และใช้ค่าของตัวแปรเหล่านั้นในระหว่างการประเมินผลลัพธ์ของสมการ

4. รายละเอียด Syntax ของ Expr

4.1 ตัวแปรและลิเตอรอล

เครื่องมือการแสดงผล Expr สามารถจัดการกับลิเตอรอลของชนิดข้อมูลทั่วไป เช่น ตัวเลข สตริง และค่าบูลีน ลิเตอรอลเป็นค่าข้อมูลที่เขียนเลยไปในโค้ดโดยตรง เช่น 42, "hello", และ true

ตัวเลข

ใน Expr คุณสามารถเขียนจำนวนเต็มและจำนวนทศนิยมโดยตรง:

42      // แทนค่าจำนวนเต็ม 42
3.14    // แทนค่าจำนวนทศนิยม 3.14

สตริง

ลิเตอรอลสตริงถูกล้อมรอบด้วยเครื่องหมายคำพูน " หรือเครื่องหมาย c backticks `` ตัวอย่างเช่น:

"hello, world" // สตริงที่ล้อมรอบด้วยเครื่องหมายคำพูน รองรับการใช้ตัวอักษรพิเศษ
`hello, world` // สตริงที่ล้อมรอบด้วยเครื่องหมาย c backticks รักษารูปแบบของสตริงโดยไม่สนับสนุนตัวอักษรพิเศษ

ค่าบูลีน

มีเพียงสองค่าบูลีน คือ true และ false ที่แทนค่าlogic true และ false:

true   // ค่าบูลีน true
false  // ค่าบูลีน false

ตัวแปร

Expr ยังอนุญาตให้กำหนดตัวแปรใน environment และอ้างถึงตัวแปรเหล่านั้นในสมการ เช่น:

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

จากนั้นในสมการ คุณสามารถอ้างถึง age และ name:

age > 18  // ตรวจสอบว่าอายุมากกว่า 18 หรือไม่
name == "Alice"  // กำหนดว่าชื่อเท่ากับ "Alice"

4.2 ตัวดำเนินการ

Expr เป็นเครื่องมือการแสดงผลที่รองรับตัวดำเนินการต่างๆ เช่น ตัวดำเนินการกวาดวิตร์ เชื่อมต่อดีย์ยังกวาดวิตร์ ตัวดำเนินการเปรียบเทียบ และตัวดำเนินการเซต ฯลฯ

ตัวดำเนินการทางคณิตศาสตร์และตรรกะ

ตัวดำเนินการทางคณิตศาสตร์รวมถึงการบวก (+), การลบ (-), การคูณ (*), การหาร (/), และเศษทศนิยม (%) ตัวดำเนินการทางตรรกะรวมถึง และ (&&), หรือ (||), และ ไม่ (!), ตัวอย่างเช่น:

2 + 2 // ผลลัพธ์คือ 4
7 % 3 // ผลลัพธ์คือ 1
!true // ผลลัพธ์คือ false
age >= 18 && name == "Alice" // ตรวจสอบว่าอายุไม่น้อยกว่า 18 และชื่อเป็น "Alice"

ตัวดำเนินการเปรียบเทียบ

ตัวดำเนินการเปรียบเทียบรวมถึงเท่ากับ (==), ไม่เท่ากับ (!=), น้อยกว่า (<), น้อยกว่าหรือเท่ากับ (<=), มากกว่า (>), และ มากกว่าหรือเท่ากับ (>=), ใช้สำหรับเปรียบเทียบค่าสองค่า:

age == 25 // ตรวจสอบว่าอายุเท่ากับ 25
age != 18 // ตรวจสอบว่าอายุไม่เท่ากับ 18
age > 20  // ตรวจสอบว่าอายุมากกว่า 20

ตัวดำเนินการเซ็ต

Expr ยังมีตัวดำเนินการบางตัวสำหรับใช้กับเซ็ต เช่น in เพื่อตรวจสอบว่าสมาชิกหนึ่งอยู่ในเซ็ตหรือไม่ เซ็ตสามารถเป็นอาร์เรย์ (arrays), สไลซ์ (slices), หรือแม็พ (maps):

"user" in ["user", "admin"]  // true, เพราะ "user" อยู่ในอาร์เรย์
3 in {1: true, 2: false}     // false, เพราะ 3 ไม่ใช่คีย์ในแม็พ

ยังมีฟังก์ชันจัดการเซ็ตขั้นสูงบางตัว เช่น all, any, one, และ none, ซึ่งต้องใช้ฟังก์ชันแบบนามธรรม (lambda):

all(tweets, {.Len <= 240})  // ตรวจสอบว่าฟิลด์ Len ของทวีตทั้งหมดไม่เกิน 240
any(tweets, {.Len > 200})   // ตรวจสอบว่ามีฟิลด์ Len ในทวีตที่เกิน 200

ตัวดำเนินการสมาชิก

ในภาษาแสดงนิพจน์ Expr ตัวดำเนินการสมาชิกช่วยให้เราเข้าถึงคุณสมบัติของ struct ใน Go language คุณสมบัตินี้ช่วยให้ Expr สามารถจัดการโครงสร้างข้อมูลที่ซับซ้อนโดยตรง ทำให้มีความยืดหยุ่นและปฏิบัติได้มาก

การใช้ตัวดำเนินการสมาชิกง่ายมาก เพียงใช้ตัวดำเนินการ . ตามด้วยชื่อคุณสมบัติ เช่น ถ้าเรามี struct ต่อไปนี้:

type User struct {
    Name string
    Age  int
}

คุณสามารถเขียนนิพจน์เพื่อเข้าถึงคุณสมบัติ Name ของโครงสร้าง User อย่างไรก็ตาม:

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"

ตัวดำเนินการท่อ (Pipe Operator)

ตัวดำเนินการท่อ (|) ใน Expr ใช้ส่งผลลัพธ์ของนิพจน์หนึ่งเป็นพารามิเตอร์ให้กับนิพจน์อื่น ๆ ซึ่งคล้ายกับการดำเนินการท่อใน Unix shell ซึ่งอนุญาตให้มีโมดูลฟังก์ชันและโมดูลฟังก์ชันหลายรายการถูกเชื่อมต่อเข้าด้วยกันเพื่อสร้างท่อประมวลผล ใน 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 รองรับฟังก์ชันที่ซึ่งเป็นซับเปอร์เพาเวอรีเดียวกันและฟังก์ชันที่กำหนดเอง ทำให้นิพจนีย์มีความสามารถและความยืดหยุ่นมากขึ้น

การใช้ Built-in Functions

Built-in functions เช่น len, all, none, any, เป็นต้น สามารถใช้โดยตรงในนิพจน์

// ตัวอย่างการใช้ built-in function
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
    panic(err)
}

// หมายเหตุ: ที่นี่ env ต้องมีตัวแปร users และแต่ละ user จะต้องมีคุณสมบัติ Age
output, err := expr.Run(program, env)
fmt.Print(output) // หากทุก user ใน 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 กลายเป็นเครื่องมือที่มีความสามารถและใช้ง่าย อย่าลืมเสมอให้มั่นใจในความปลอดภัยของชนิดเมื่อสร้างแวดล้อม Expr และในการรันนิพจน์

5. คู่มือ Built-in Function

เครื่องมือนิพจน์ Expr มีฟังก์ชันที่กำหนดเองที่มีความหลากหลาย สำหรับการจัดการสถานการณ์ที่ซับซ้อน ต่อไปเราจะรายละเอียดเกี่ยวกับฟังก์ชันที่กำหนดเองเหล่านี้และการใช้งานของมัน

all

ฟังก์ชัน all สามารถใช้ในการตรวจสอบว่าทุกองค์ประกอบในคอลเล็กชันทำให้เงื่อนไขที่กำหนดได้ มันรับพารามิเตอร์สองตัว: คอลเล็กชันและนิพจน์เงื่อนไข

// ตรวจสอบว่าทุกทวีตมีความยาวเนื้อหาน้อยกว่า 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 ใช้ในการยืนยันว่ามีสมาชิกเพียงหนึ่งตัวในคอลเล็กชันทำให้เงื่อนไขที่กำหนดได้

// ตรวจสอบว่ามีเพียงทวีตเท่านั้นที่มีคำหลักเฉพาะ
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, "illegal characters")`

สิ่งที่กล่าวมาข้างต้นนั้นเป็นเพียงส่วนหนึ่งของฟังก์ชันที่ใช้งานได้ภายในเครื่องมือ Expr ด้วยฟังก์ชันที่มีประสิทธิภาพในการจัดการข้อมูลและตรรกะได้อย่างยืดหยุ่นและมีประสิทธิภาพ สำหรับรายการของฟังก์ชันและคำแนะนำในการใช้ กรุณาอ่านเพิ่มเติมที่ เอกสารอ้างอิงทางการของ Expr