向HN:AGL展示一种可编译为Go的玩具语言
Show HN: AGL a toy language that compiles to Go

原始链接: https://github.com/alaingilbert/agl

AGL是一种编译为Go的语言,利用Go熟悉的语法并进行修改以增强功能。主要功能包括单个返回值(支持`Option[T]和`Result[T])、用于多个值的`Tuple`类型以及通过`?`还有!`运营商。 AGL引入了简洁的匿名函数、内置数组方法(Map/Reduce/Filter/Find/Sum)和运算符重载。它还包括枚举类型和模式匹配语法“match”,用于处理“Option”和“Result”类型。“or_break”和“or_concontinue”在基于“None”或“Err”值的循环内提供控制流。 值得注意的功能扩展到具有泛型类型参数的方法。AGL代码编译为Go,可以直接通过`AGL-run`命令执行,也可以内置到可执行二进制文件中。Shell的“shebang”支持支持用AGL编写的脚本。包含LSP支持的VSCode扩展。

Alain Gilbert在短短两周内创建了AGL,一种编译Go的玩具语言。AGL修改了Go的语法,以强制函数返回单值,从而促进了Result/Option类型和错误传播。它还引入了简洁的、类型推断的匿名函数,以简化Map/Reduce/Filter等函数的使用。 虽然受到Borgo、Swift和Vlang等语言的启发,AGL仍然允许nil指针与现有的Go库互操作,尽管Alain希望最终删除它们。目前,使用非标准Go库需要手动重新定义类型,尽管他正在研究自动包装器生成。 Hacker News的讨论对该语言的设计选择提出了质疑,特别是可选值的传播与短路以及错误处理机制。阿兰承认这一快速发展,并欢迎对未来改进的反馈。总体而言,AGL在Go语言增强方面提供了一个有前景的实验,重点是改善开发人员体验和现代错误处理范式。
相关文章

原文

AGL

AGL is a language that compiles to Go.
It uses Go's syntax, in fact its lexer/parser is a fork of the original Go implementation, with a few modifications
The main differences are:

  • Functions return only a single value. This makes it possible to use types like Option[T] and Result[T], and to support automatic error propagation via an operator.
  • To make returning multiple values easy, a Tuple type has been introduced. For example: Result[(u8, string, bool)]
  • AGL can be used as a scripting language

Notable change: number types are int i8 i16 i32 i64 uint u8 u16 u32 u64 f32 f64

  • Tuple
  • Enum
  • Error propagation operators (? for Option[T] / ! for Result[T])
  • Concise anonymous function with type inferred arguments (other := someArr.Filter({ $0 % 2 == 0 }))
  • Array built-in Map/Reduce/Filter/Find/Sum methods
  • Operator overloading
  • Compile down to Go code
  • VSCode extension & LSP (language server protocol)
  • Shell "shebang" support
go build               // Build the "agl" executable
./agl main.agl         // Output Go code in stdout
./agl run main.agl     // Run the code directly and output the result in stdout
./agl build main.agl   // Create a main.go file
package main

import "fmt"

func getInt() int! { // `int!` means the function return a `Result[int]`
    return Ok(42)
}

func intermediate() int! {
    num := getInt()! // Propagate 'Err' value to the caller
    return Ok(num + 1)
}

func main() {
    num := intermediate()! // crash on 'Err' value
    fmt.Println(num)
}
package main

import "fmt"

func maybeInt() int? { // `int?` means the the function return an `Option[int]`
    return Some(42)
}

func intermediate() int? {
    num := maybeInt()? // Propagate 'None' value to the caller
    return Some(num + 1)
}

func main() {
    num := intermediate()? // crash on 'None' value
    fmt.Println(num)
}
package main

type Person struct { Name string }

func (p Person) MaybeSelf() Person? {
    return Some(p)
}

func main() {
    bob := Person{Name: "bob"}
    bob.MaybeSelf()?.MaybeSelf()?.MaybeSelf()?
}

If Some(val) := ... { to use a Option[T]/Result[T] value safely

This pattern works with any of Ok|Err|Some

func maybeInt() int? { Some(42) } // Implicit return when a single expression is present

func main() {
    if Some(num) := maybeInt() {
        fmt.Println(num)
    }
}
package main

import "fmt"

func getInt() int! { Ok(42) }

func maybeInt() int? { Some(42) }

func main() {
    match getInt() {
    case Ok(num):
        fmt.Println("Num:", num)
    case Err(err):
        fmt.Println("Error:", err)
    }

    match maybeInt() {
    case Some(num):
        fmt.Println("Num:", num)
    case None:
        fmt.Println("No value")
    }
}

or_break/or_continue will break/continue on a None/Err value

package main

import "fmt"
import "time"

func test(i int) int? {
    if i >= 2 {
        return None
    }
    return Some(i)
}

func main() {
    for i := 0; i < 10; i++ {
        res := test(i) or_break // `res` has type `int`
        fmt.Println(res)        // will print the value `0` and `1`
        time.Sleep(time.Second)
    }
}

Short anonymous function (type inferred)

Arguments are mapped into $0|$1...
In this example, x becomes $0 when using the short form.

Lang Expression
Go utils.Filter(arr, func(x int) bool { return x % 2 == 0 })
AGL arr.Filter({ $0 % 2 == 0 })

Since the function is expected to return something and there is only one expression, it will be returned automatically.

package main

type Person struct {
    Name string
    Age int
}

func main() {
    arr := []int{1, 2, 3, 4, 5}
    sum := arr.Filter({ $0 % 2 == 0 }).Map({ $0 + 1 }).Sum()
    assert(sum == 8)

    p1 := Person{Name: "foo", Age: 18}
    p2 := Person{Name: "bar", Age: 19}
    people := []Person{p1, p2}
    names := people.Map({ $0.Name }).Joined(", ")
    sumAge := people.Map({ $0.Age }).Sum()
    assert(names == "foo, bar")
    assert(sumAge == 37)
}
package main

import "fmt"

type IpAddr enum {
    v4(u8, u8, u8, u8)
    v6(string)
}

func main() {
    // enum values can be destructured
    addr1 := IpAddr.v4(127, 0, 0, 1)
    a, b, c, d := addr1

    // tuple can be destructured
    tuple := (1, "hello", true)
    e, f, g := tuple

    fmt.Println(a, b, c, d, e, f, g)
}
package main

type Person struct {
    Name string
    Age int
}

func (p Person) == (other Person) bool {
    return p.Age == other.Age
}

func main() {
    p1 := Person{Name: "foo", Age: 42}
    p2 := Person{Name: "bar", Age: 42}
    assert(p1 == p2) // People of the same age are obviously equal!
}
package main

import "fmt"

func (v agl.Vec[T]) Even() []T {
    out := make([]T, 0)
    for _, el := range v {
        if el % 2 == 0 {
            out = append(out, el)
        }
    }
    return out
}

func main() {
    arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
    res := arr.Even().Filter({ $0 <= 6 }).Map({ $0 + 1 }) 
    //         ^^^^^^ new method available
    fmt.Println(res) // [3 5 7]
}

Methods can have generic type parameters

func (v agl.Vec[T]) MyMap[R any](clb func(T) R) []R {
    out := make([]R, 0)
    for _, el := range v {
        out = append(out, clb(el))
    }
    return out
}

You can also extend for a specific type of vector

func (v agl.Vec[string]) MyJoined(sep string) string {
    return strings.Join(v, sep)
}
package main

import (
    "fmt"
    "os"
)

func main() {
    os.WriteFile("test.txt", []byte("test"), 0755)!
    by := os.ReadFile("test.txt")!
    fmt.Println(string(by))
}
package main

import (
    "fmt"
    "net/http"
    "io"
)

func main() {
    req := http.NewRequest(http.MethodGet, "https://google.com", None)!
    c := http.Client{}
    resp := c.Do(req)!
    defer resp.Body.Close()
    by := io.ReadAll(resp.Body)!
    fmt.Println(string(by))
}
#!/usr/bin/env agl run
package main

import "fmt"

func main() {
    fmt.Println("Hello AGL!")
}
$ chmod +x hello.agl
$ ./hello.agl
Hello AGL!
联系我们 contact @ memedata.com