alaingilbert/agl
{ "createdAt": "2025-06-10T04:13:38Z", "defaultBranch": "master", "description": "AGL (AnotherGoLang)", "fullName": "alaingilbert/agl", "homepage": "https://alaingilbert.github.io/agl/", "language": "Go", "name": "agl", "pushedAt": "2025-11-16T01:29:11Z", "stargazersCount": 104, "topics": [], "updatedAt": "2025-11-18T21:10:21Z", "url": "https://github.com/alaingilbert/agl"}
AGL (AnotherGoLang)
Section titled “AGL (AnotherGoLang)”Description
Section titled “Description”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]andResult[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
features
Section titled “features”- Tuple / Enum / Set
- Immutable variables/structs by default
- 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)
- SourceMap
- Shell “shebang” support
Community
Section titled “Community”- Discord server https://discord.gg/68FJ767Yna
How to use
Section titled “How to use”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 fileError propagation
Section titled “Error propagation”Result propagation
Section titled “Result propagation”package main
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 print(num)}Option propagation
Section titled “Option propagation”package main
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 print(num)}Propagation chaining
Section titled “Propagation chaining”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 let Some(val) := ... { to use a Option[T]/Result[T] value safely
Section titled “If let Some(val) := ... { to use a Option[T]/Result[T] value safely”This pattern works with any of Ok|Err|Some
package main
func maybeInt() int? { Some(42) } // Implicit return when a single expression is present
func main() { if let Some(num) := maybeInt() { print(num) }}This pattern works with any of Ok|Err|Some
package main
func maybeInt() int? { Some(42) } // Implicit return when a single expression is present
func main() { guard let Some(num) := maybeInt() else { return } // can use guard to unwrap a value guard num < 100 else { return } // can use guard as a reverse if assert(num == 42)}package main
func getInt() int! { Ok(42) }
func maybeInt() int? { Some(42) }
func main() { match getInt() { case Ok(num): print("Num:", num) case Err(err): print("Error:", err) }
match maybeInt() { case Some(num): print("Num:", num) case None: print("No value") }}or_break/or_continue
Section titled “or_break/or_continue”or_break/or_continue will break/continue on a None/Err value
package main
import "time"
func test(i int) int? { if i >= 2 { return None } return Some(i)}
func main() { for i in 0..10 { res := test(i) or_break // `res` has type `int` print(res) // will print the value `0` and `1` time.Sleep(time.Second) }}Short anonymous function (type inferred)
Section titled “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)}You can also name the arguments using the following syntax:
names := people.Map(|person| { person.Name }).Joined(", ")If a single statement is present in the lambda body, the curly brace can be omitted:
names := people.Map(|person| person.Name).Joined(", ")Enum (sum type)
Section titled “Enum (sum type)”package main
type IpAddr enum { V4(u8, u8, u8, u8) V6(string)}
func main() { home := IpAddr.V4(127, 0, 0, 1)
// match branches must be exhaustive match home { case .V4(a, b, c, d): print(a, b, c, d) case .V6(addr): print(addr) }
// if-let can be used to pattern match if let IpAddr.V4(a, b, c, d) := home { print(a, b, c, d) }
// guard-let can also be used to pattern match guard let IpAddr.V4(a, b, c, d) := home else { return } print(a, b, c, d)}Destructuring
Section titled “Destructuring”package main
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
print(a, b, c, d, e, f, g)}For-in
Section titled “For-in”package main
func main() { for el in []int{1, 2, 3} { print(el) } for el in set[int]{1, 2, 3} { print(el) } for (k, v) in map[string]u8{"a": 1, "b": 2, "c": 3} { print(k, v) } for (a, b, c) in []!(int, string, bool){(1, "foo", true), (2, "bar", false)} { print(a, b, c) } for el in 0..3 { // 0 1 2 print(el) } for el in 0..=3 { // 0 1 2 3 print(el) } for el in (0..3).Rev() { // 2 1 0 print(el) } for c in "hello" { print(string(c)) } for (i, c) in "hello".Enumerated() { print(i, string(c)) } for el in []int{1, 2, 3} where el % 2 == 0 { // for-in can have an optional where clause print(el) }}Template string
Section titled “Template string”package main
func main() { name := "bob" age := 42 print(t"{name} is {age} years old") // bob is 42 years old}Operator overloading
Section titled “Operator overloading”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!}Extend built-in types
Section titled “Extend built-in types”package main
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 print(res) // [3 5 7]}Methods can have generic type parameters
func (v agl.Vec[T]) MyMap[R any]!(clb func(T) R) []R { mut 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)}assert & precondition
Section titled “assert & precondition”assert and assertEq will not be part of the release code.
precondition will be part of both debug and release code.
package main
func main() { a := 42 assert(a == 42) assert(a == 42, "some message") assertEq(a, 42) // assertEq gives a better error message containing the values at runtime assertEq(a, 42, "some message") precondition(a == 42) precondition(a == 42, "some message")}ExpressibleByStringLiteral
Section titled “ExpressibleByStringLiteral”package main
type Point struct { X, Y int}
// Point implements ExpressibleByStringLiteralfunc (Point) FromStringLit(s string) Point { (p1, p2) := s.Cut(":")? return Point{X: p1.Int()?, Y: p2.Int()?}}
func PrintPoint(p Point) { print(p.X, p.Y)}
func main() { PrintPoint("1:2") // a string literal can be used to create a Point PrintPoint(Point{X: 1, Y: 2})}Nil-Coalescing operator (??)
Section titled “Nil-Coalescing operator (??)”package main
type Container struct { Val int}
/* a := c1 ?? &Container{Val: 42} // is equivalent to --> a := c1 if a == nil { a = &Container{Val: 42} }*/
func main() { var c1 *Container c2 := &Container{Val: 1} a := c1 ?? &Container{Val: 42} print(a.Val) // 42 b := c2 ?? &Container{Val: 42} print(b.Val) // 1}Nil-Conditional assignment operator (?.)
Section titled “Nil-Conditional assignment operator (?.)”package main
type Container struct { mut Val int}
/* c1?.Val = 42 // is equivalent to --> if c1 != nil { c1.Val = 42 }*/
func main() { var mut c1 *Container mut c2 := &Container{} c1?.Val = 42 c2?.Val = 42 print(c1) // <nil> print(c2) // &{42}}Using Go libraries
Section titled “Using Go libraries”package main
import "agl/os"
func main() { os.WriteFile("test.txt", []byte("test"), 0755)! by := os.ReadFile("test.txt")! print(string(by))}Here is a guide on how to use external libraries
And here is a simple example
Making http request
Section titled “Making http request”package main
import ( "agl/net/http" "agl/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)! print(string(by))}Scripting
Section titled “Scripting”#!/usr/bin/env agl runpackage main
func main() { print("Hello AGL!")}$ chmod +x hello.agl$ ./hello.aglHello AGL!