vuka

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 4 Imported by: 0

README

Vuka

Vuka

Vuka is Go with Result and Option, ? error propagation, pattern matching, function and method overloading, and typed attributes, with Elixir-style multi-clause functions on the way. It transpiles to plain Go and builds with the go command you already have.

func area(c Circle) float64 { return math.Pi * c.R * c.R }

@export("RectArea")
func area(r Rect) float64 { return r.W * r.H }

func (c *Counter) Add(by int)    { c.n += by }
func (c *Counter) Add(by string) { c.n += len(by) }

@cache.Memo{TTL: 5 * time.Minute}
func lookup(id int) User { … }
func lookup(arg string) Result[User] {
	id := strconv.Atoi(arg)?          // Go's (T, error) works directly
	u := find(id)?
	return Ok(u)
}

match lookup(arg) {
case Ok(u):
	fmt.Println(u.Name)
case Err(&NotFound{ID: id}):
	fmt.Println("missing user", id)
case Err(e) if errors.Is(e, strconv.ErrSyntax):
	fmt.Println("not a number:", arg)
case Err(e):
	fmt.Println("error:", e)
}
go install github.com/vuka-lang/vuka/cmd/vuka@latest
vuka new hello && cd hello && vuka run .

A Vuka project is a Go module: .vuka and .go files sit side by side in the same packages and call each other, the way Kotlin and Java share a project. Dependencies are Go's: go get a module and import it from any .vuka file.

Editors

vuka lsp is a language server for .vuka files: gopls behind a proxy that keeps the generated Go open in gopls as unsaved buffers (nothing is written to your tree) and maps every position both ways. Completion, hover, signature help, go to definition, references, rename, outline, code actions and inlay hints all work; Vuka's own errors and Go's type errors show on the .vuka lines. Overloads show under the name you wrote (area, not area__Circle).

  • VS Code: the extension in editors/vscode (npm install && npx vsce package, then install the .vsix).
  • Any other editor: run vuka lsp over stdio for the vuka file type (needs gopls: go install golang.org/x/tools/gopls@latest).

Built to track Go

Vuka owns only its additions. Each one is found by go/scanner and lowered to Go in place; everything else passes through untouched, to be parsed and type-checked by the standard library's go/parser and go/types. When Go adds syntax, Vuka already accepts it.

  • Install Vuka with your own toolchain (go install, or go get -tool and go tool vuka). Its parser is the stdlib of the Go that built it, so it matches yours.
  • TestStdlibPassesThrough feeds every file of $GOROOT/src through Vuka and requires byte-identical output. Run it on a new Go release to find out what, if anything, has to change.
  • The generated code is plain Go; its only runtime is the small Result/Option package. //line directives point compile errors and panics at the .vuka source, down to the column.

What works

Result and Option live in the runtime package github.com/vuka-lang/vuka (its root; nothing else in it). Vuka code writes Result[User], Ok(u), Err(e), Some(x) and None unqualified, and the transpiler adds the import wherever the package doesn't declare those names itself. Err and None take their type from where the value goes (return Err(e) in a function returning Result[User]). Result's error is Go's error, so it meets Go code halfway: vuka.Of(os.ReadFile(p)) wraps a Go call, r.Get() hands back (T, error). Your module needs the dependency: go get github.com/vuka-lang/vuka.

? ends a statement: x := f()?, x = f()?, var x = f()?, return f()? or f()?. The operand is Go's (T…, error), an error, a Result, or an Option (in a function returning an Option). On failure the function returns: zero values plus the error, Err(e), or None, whichever its results call for. The operand stays where it is, so nothing is reordered.

match is a statement of cases, tried in order:

pattern matches
_, default anything
x anything, binding it to x (a constant's name compares instead)
0, "a", Max, pkg.Const, true an equal value
^x the value of the variable x (Elixir's pin)
Ok(p), Err(p), Some(p), None a Result or Option, then p inside
T{F: p, …}, &T{…} a struct's fields; against an interface, a type test too
1, 2, 3 any of them

case p if cond: adds a guard. A match must be exhaustive (Ok and Err, Some and None, true and false, or a case that takes anything), and a case after one that matches everything is an error; both are reported before Go sees the code.

Overloading. Functions and methods may share a name when their parameter types differ. Each declaration is renamed (area__Circle, Add__int_int), and each call goes to the overload that best fits its arguments' static types: identical types beat an untyped constant's default type, which beats any other assignable value. A tie is a compile error that lists the candidates. Calls whose argument types come from other overloaded calls resolve too.

Attributes come before a top-level declaration:

@doc("…") becomes the declaration's doc comment
@deprecated("…") adds a Deprecated: paragraph
@export("Name") generates Name, a wrapper with the declaration's signature: how Go code reaches one overload
@T / @pkg.T{…} any Go type, bare or as a composite literal; the generated code type-checks it

Decorators wrap a declaration, Python-style. The simplest kind works on any function, whatever its signature:

decorator logged(c) {
	fmt.Println("calling", c.Name, c.Args)
	c.Next()                                   // run the function (or the next decorator)
	fmt.Println("returned", c.Results)
}

decorator retry(times int)(c) {             // with parameters
	for i := 0; i < times; i++ {
		if c.Next(); c.Err() == nil {
			return
		}
	}
}

@retry(3)
@logged
func charge(ctx context.Context, id int) (Receipt, error) { … }

@logged
func (s *Store) Save(u User) error { … }   // methods too

decorator name(c) { … } is shorthand for func name(c *vuka.Call), a vuka.Decorator; plain Go functions of that type work the same, in .vuka or .go files. A decorator with parameters (retry(3)) is built once per decorated function, so state it keeps (a cache, a counter) isn't shared.

*vuka.Call
c.Name, c.Receiver "main.charge"; the receiver of a method
c.Args, c.Results arguments (change them before Next) and results
c.Next() run the rest; call it again to retry, or not at all
c.Return(v…) answer without running the function (caches, mocks)
c.Err(), c.SetErr(err) the trailing error result
c.Context() the context.Context argument
vuka.Arg[T](c, i) argument i as a T
c.Attr(&x), vuka.Attr[T](c) a typed attribute on the same declaration
@Perm("orders.write")
@guard                                      // reads it: var p Perm; c.Attr(&p)
func save(ctx context.Context, o Order) error { … }

A type decorator receives the type: func Model(t *vuka.Type) gets t.Name, t.Reflect and t.Attr, at init. Packing a call into a *vuka.Call costs a small allocation; for hot paths, a typed decorator func(F) F (or for a type, a generic func[T]) has none and checks every type. Vuka picks the form from the decorator's type, so both mix freely, and a decorator that fits neither is a compile error on its @ line. Functions keep their name for a wrapper that builds the decorated function once; recursion goes through it; init, overloads, generic functions and type ( … ) groups can be decorated.

Static fields and methods belong to a type, as in Kotlin or Java:

type User struct {
	Name string

	static Table   = "users"
	static created int            // private: lowercase, as in Go
	static const Max = 100
}

func User.New(name string) *User {
	User.created++
	return &User{Name: name}
}

u := User.New("ada")
fmt.Println(User.Table, User.Max)

They lower to plain package-level Go: User_Table, _User_created, func User_New, which is also how Go code reaches them. A static can't share a name with a field or method (in Go, User.Save already means a method).

A static on a generic type has one value per instantiation, and statics are reached through embedding. With a base type whose type parameter is named Self, Vuka fills in the embedding type, and methods taking self *Self get the whole outer value. That's enough for a Django-style model API:

// package orm
type Model[Self any] struct {
	static Objects = Manager[Self]{}
}

func (m *Model[Self]) Save(self *Self, ctx context.Context) error { … }

// your models
type User struct {
	orm.Model               // = orm.Model[User]
	Name string
}

users := User.Objects.All(ctx)   // the User manager, from orm.Model
err := u.Save(ctx)               // Save gets u itself as self

orm.Model[User] written out works the same. Self is filled in only for a type parameter of that name, so other generics never get a type argument you didn't write. A type embedding User reaches User's statics, as Go promotes fields; self there is the embedded User. Calls that pass self must not have side effects in the receiver expression (Vuka uses it twice).

CLI

vuka new <dir> [module path]
vuka build|run|test|vet|install [go flags] [packages]
vuka gen [-check] [-o build]
vuka gen -inplace [-check] [dir | dir/...]
vuka lsp

build, run and the rest transpile the module into a temporary overlay (go build -overlay), so no generated file lands among your sources.

The build module

Like Kotlin's build/, vuka build and vuka gen keep a build/ directory: a complete Go module with the same module path, where every .vuka file is replaced by its generated Go and everything else is copied (.go files, embedded assets, testdata; relative replace paths in go.mod adjusted). Inside it, plain Go tools need no Vuka at all:

vuka gen
cd build && go build ./... && go test ./... && go vet ./...

The sync is incremental (only changed files are written, deleted sources are removed), compile errors in build/ point at the .vuka lines, and a .vuka-build marker means vuka never syncs over a directory it didn't make. vuka new adds /build/ to .gitignore. vuka gen -check fails when build/ is out of date, for CI.

Roadmap

  1. Enums (sum types) with exhaustive match; lambdas, ?., ??, tuples, functional helpers (the rest of Dingo's set)
  2. Multi-clause functions with patterns and guards; arity overloading for default arguments; Elixir-style module attributes (@max 3, read as @max)
  3. @derive and generator attributes; vuka fmt; lowering to native Go when a Go release adds an equivalent feature

Libraries

Other modules go get your repository root, not build/. To publish a Vuka library for Go users, write the generated files beside the sources and commit them (vuka gen -inplace, and vuka gen -inplace -check in CI).

Known limits

  • Overloads are resolved within a package; calling another package's overloads goes through its @export names.
  • Generic functions can't be overloaded yet.
  • Go's own error messages show mangled names (area__int).
  • An overloaded method can't satisfy an interface method of the plain name.
  • ? ends a statement; it can't sit inside a larger expression, or in an if, for or switch header.
  • Struct patterns of generic types aren't supported yet.

Documentation

Overview

Package vuka is the runtime of Vuka programs: Result and Option.

Vuka code writes Result[User], Ok(u), Err(e), Some(x) and None unqualified; the transpiler points them here. Go code uses the package like any other.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Arg added in v0.2.0

func Arg[T any](c *Call, i int) T

Arg is argument i as a T.

func As added in v0.2.0

func As[T any](v any) T

As converts a value held in an any back to T; nil is T's zero value. It panics, naming both types, when a decorator put something else there.

func Attr added in v0.2.0

func Attr[T any](c *Call) (T, bool)

Attr is the declaration's typed attribute of type T.

func Instance

func Instance[F any](s *Instances, build func() F) F

Instance returns the instantiation of a generic decorated function whose type is F, building it the first time.

Types

type Call added in v0.2.0

type Call struct {
	Name     string
	Receiver any   // the receiver, for a method
	Args     []any // the arguments; a variadic parameter is one slice
	Results  []any // the results, once Next has run (or Return was called)
	// contains filtered or unexported fields
}

Call is one call of a decorated function, passed down its decorators.

func NewCall added in v0.2.0

func NewCall(fn *Func, chain []Decorator, recv any, args []any, invoke func(c *Call) []any) *Call

NewCall starts a call through chain, ending in invoke. Generated code calls it.

func (*Call) Attr added in v0.2.0

func (c *Call) Attr(ptr any) bool

Attr fills ptr with the declaration's typed attribute of ptr's element type, reporting whether there is one: var r Route; c.Attr(&r).

func (*Call) Context added in v0.2.0

func (c *Call) Context() context.Context

Context is the call's context.Context argument, or context.Background().

func (*Call) Err added in v0.2.0

func (c *Call) Err() error

Err is the function's error result, or nil.

func (*Call) Next added in v0.2.0

func (c *Call) Next()

Next runs the rest of the chain: the next decorator, or the function itself. It can be called more than once.

func (*Call) Return added in v0.2.0

func (c *Call) Return(results ...any)

Return sets the results without running the function; the rest of the chain is skipped unless Next is called.

func (*Call) Run added in v0.2.0

func (c *Call) Run()

Run runs the chain and checks what it left in Results. Generated code calls it.

func (*Call) SetErr added in v0.2.0

func (c *Call) SetErr(err error)

SetErr replaces the error result. Before Next, it makes the call fail with zero values and err.

type Decorated

type Decorated[F any] struct {
	// contains filtered or unexported fields
}

Decorated holds a decorated function, built from its decorators on first use. Vuka generates one per decorated function or method.

func (*Decorated[F]) Get

func (d *Decorated[F]) Get(build func() F) F

Get returns the decorated function, building it the first time.

type Decorator added in v0.2.0

type Decorator func(c *Call)

Decorator wraps a call of any function: it runs code around c.Next(), may change c.Args before it, read or replace c.Results after it, call it again (retries) or not at all (caches). Written once, it decorates any function or method:

func logged(c *vuka.Call) {
	fmt.Println("calling", c.Name, c.Args)
	c.Next()
}

A decorator with parameters is a function returning one; it runs once per decorated function, so state it keeps isn't shared between functions.

type Func added in v0.2.0

type Func struct {
	Name     string // "main.charge", "Store.Save"
	Attrs    []any  // the declaration's typed attributes
	ErrIndex int    // the trailing error result's index, or -1
	CtxIndex int    // the context.Context argument's index, or -1
	Zero     func() []any
}

Func describes a decorated function. Vuka generates one per function.

type Instances

type Instances struct {
	// contains filtered or unexported fields
}

Instances holds a generic decorated function's instantiations, each built from its decorators on first use.

type Option

type Option[T any] struct {
	// contains filtered or unexported fields
}

Option is a value that may be absent. The zero Option is None.

func FromPtr

func FromPtr[T any](p *T) Option[T]

FromPtr is None for a nil pointer and Some(*p) otherwise.

func MapOption

func MapOption[T, U any](o Option[T], f func(T) U) Option[U]

MapOption applies f to a present value.

func None

func None[T any]() Option[T]

None is an absent value. Vuka code writes plain None; the transpiler fills in T from where the value goes.

func Some

func Some[T any](v T) Option[T]

Some is a present value.

func (Option[T]) Get

func (o Option[T]) Get() (T, bool)

Get is Go's comma-ok form: v, ok := o.Get().

func (Option[T]) IsNone

func (o Option[T]) IsNone() bool

func (Option[T]) IsSome

func (o Option[T]) IsSome() bool

func (Option[T]) String

func (o Option[T]) String() string

func (Option[T]) Unwrap

func (o Option[T]) Unwrap() T

Unwrap is the value; it panics when o is None.

func (Option[T]) UnwrapOr

func (o Option[T]) UnwrapOr(def T) T

UnwrapOr is the value, or def when o is None.

func (Option[T]) Value

func (o Option[T]) Value() T

Value is the value, or T's zero value when o is None.

type Result

type Result[T any] struct {
	// contains filtered or unexported fields
}

Result is a value or an error. It is Ok when it holds no error; the zero Result is Ok with T's zero value.

func AndThen

func AndThen[T, U any](r Result[T], f func(T) Result[U]) Result[U]

AndThen chains a step that can fail.

func Err

func Err[T any](err error) Result[T]

Err is a failed Result. err must not be nil.

func Map

func Map[T, U any](r Result[T], f func(T) U) Result[U]

Map applies f to an Ok value.

func Of

func Of[T any](v T, err error) Result[T]

Of turns Go's (T, error) into a Result: vuka.Of(os.ReadFile(name)).

func Ok

func Ok[T any](v T) Result[T]

Ok is a successful Result.

func (Result[T]) Err

func (r Result[T]) Err() error

Err is the error, or nil when r succeeded.

func (Result[T]) Get

func (r Result[T]) Get() (T, error)

Get turns r back into Go's (T, error).

func (Result[T]) IsErr

func (r Result[T]) IsErr() bool

func (Result[T]) IsOk

func (r Result[T]) IsOk() bool

func (Result[T]) String

func (r Result[T]) String() string

func (Result[T]) Unwrap

func (r Result[T]) Unwrap() T

Unwrap is the value; it panics when r failed.

func (Result[T]) UnwrapOr

func (r Result[T]) UnwrapOr(def T) T

UnwrapOr is the value, or def when r failed.

func (Result[T]) Value

func (r Result[T]) Value() T

Value is the value, or T's zero value when r failed.

type Static added in v0.2.0

type Static[V any] struct {
	V V
}

Static is the storage of a static field of a generic type: one per instantiation, so Model[User] and Model[Order] each have their own.

func StaticOf added in v0.2.0

func StaticOf[K, V any](s *Statics, init func() V) *Static[V]

StaticOf returns the static field of the instantiated type K, built by init on first use. Generated code calls it.

type Statics added in v0.2.0

type Statics struct {
	// contains filtered or unexported fields
}

Statics holds one generic static field's instantiations.

type Type added in v0.2.0

type Type struct {
	Name    string // "main.User"
	Reflect reflect.Type
	Attrs   []any // the declaration's typed attributes
}

Type describes a decorated type, for a type decorator:

func Model(t *vuka.Type) { registry[t.Name] = t.Reflect }

func TypeOf added in v0.2.0

func TypeOf[T any](name string, attrs ...any) *Type

TypeOf describes T for its decorators. Generated code calls it.

func (*Type) Attr added in v0.2.0

func (t *Type) Attr(ptr any) bool

Attr fills ptr with the type's typed attribute of ptr's element type.

Directories

Path Synopsis
cmd
vuka command
Command vuka transpiles Vuka source to Go and runs the go command over it.
Command vuka transpiles Vuka source to Go and runs the go command over it.
vukavet module
internal
load
Package load finds Vuka packages in a module and supplies what transpiling them needs from the go command: build-context file selection, export data for imports, and the overlay that puts generated files in front of the compiler.
Package load finds Vuka packages in a module and supplies what transpiling them needs from the go command: build-context file selection, export data for imports, and the overlay that puts generated files in front of the compiler.
Package transpile turns Vuka source into Go.
Package transpile turns Vuka source into Go.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL