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)]
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)
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()?
}
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)
}
}
Arguments are mapped into $0|$1...
In this example, a becomes $0 when using the short form.
func(a, b int) int { return a + b }
{ $0 + $1 }
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))
}