Documentation
¶
Overview ¶
Package lift uses generic gadgets to lift types to values, referred to in documentation as type enumerations or type enumeration symbols. When lifted as values, type enumerations can predicate branching or matching logic over the universe of types.
The package intends to make production and consumption of type enumeration symbols narrow and ergonomic. The most fundamental component of lift is the Sym type, an exported type enumeraion symbol. See the README.md file for further exposition on internal mechanics.
The Map type is provided as an immediate and highly general application of type enumeration symbols, using them as map keys.
Function-flavored type enumerations (e.g. from func(T), distinct from T) can predicate runtime dispatch gadgetry. For example, the lift/conv package leverages lift to index conversion functions from given source and destination types. Package examples explore other gadgetry, roughly in order of increasing elaboration.
Example (A_fizzbuzz) ¶
This example demonstrates lifting a function. If the lifted function is passed a Sym that doesn't match the desired type, the return is zero valued.
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
// This example demonstrates lifting a function.
// If the lifted function is passed a [Sym] that doesn't match the desired type,
// the return is zero valued.
func main() {
sayFizz := liftFizzBuzz(func(_ fizz) string {
return "fizz"
})
sayBuzz := liftFizzBuzz(func(_ buzz) string {
return "buzz"
})
for i := 1; i < 31; i++ {
f, b := parseFizzBuzz(i)
if res := sayFizz(f) + sayBuzz(b); res != "" {
fmt.Println(i, res)
}
}
}
type fizz struct{}
type buzz struct{}
func liftFizzBuzz[T any](fn func(T) string) func(lift.Sym) string {
return func(sym lift.Sym) string {
if t, ok := lift.Unwrap[T](sym); ok {
return fn(t)
}
return ""
}
}
func parseFizzBuzz(i int) (f, b lift.Sym) {
if i%3 == 0 {
f = lift.Wrap(fizz{})
}
if i%5 == 0 {
b = lift.Wrap(buzz{})
}
return
}
Output: 3 fizz 5 buzz 6 fizz 9 fizz 10 buzz 12 fizz 15 fizzbuzz 18 fizz 20 buzz 21 fizz 24 fizz 25 buzz 27 fizz 30 fizzbuzz
Example (B_fish) ¶
This example demonstrates a Map with Sym values. The Map is a dispatch table; a Sym may be looked up, and a related function returned.
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
// "recievers"
type fish string
type octopus struct {
arms int
}
// "methods"
fishSlap := func(sym lift.Sym) string {
f := lift.MustUnwrap[fish](sym)
return fmt.Sprintf("%sslap", f)
}
octopusSlap := func(sym lift.Sym) (slap string) {
o := lift.MustUnwrap[octopus](sym)
for i := 0; i < o.arms; i++ {
slap += "octoslap"
}
return
}
// a dispatch map
marineLifeSlap := lift.NewMap[func(lift.Sym) string](
lift.Def[fish](fishSlap),
lift.Def[octopus](octopusSlap),
)
// the demonstration:
symbols := []lift.Sym{
lift.Wrap(fish("trout")),
lift.Wrap(fish("salmon")),
lift.Wrap(octopus{8}),
}
for _, sym := range symbols {
slap, _ := lift.LoadSym(marineLifeSlap, sym)
fmt.Printf("boom! %s!\n", slap(sym))
}
}
Output: boom! troutslap! boom! salmonslap! boom! octoslapoctoslapoctoslapoctoslapoctoslapoctoslapoctoslapoctoslap!
Example (C_eventhandlers) ¶
This example shows a switch-based dispatch table, yielding different logic than a Map would.
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
// This example shows a switch-based dispatch table, yielding different logic than a [Map] would.
func main() {
// brewing some dummy objects...
photo := &file{path: "tableflip.jif", data: "(╯°□°)╯︵ ┻━┻"}
home := &folder{path: "home", locked: false}
sys := &folder{path: "system", locked: true}
// brewing some dummy events...
events := []event{
newEvent(mouseClick{}, sys, lift.Empty{}),
newEvent(mouseClick{}, photo, lift.Empty{}),
newEvent(mouseDrop{}, photo, home),
newEvent(mouseDrop{}, photo, sys),
newEvent(mouseClick{}, home, lift.Empty{}),
}
for _, ev := range events {
eventDispatch(ev)
}
}
// GADGETS
type opFunc[OP any, SRC any, DST any] func(OP, SRC, DST) bool
type evFunc = func(event) bool
type event struct {
op, src, dst lift.Sym
signature lift.Sym
}
func newEvent[OP any, SRC any, DST any](op OP, src SRC, dst DST) event {
return event{
op: lift.Wrap(op),
src: lift.Wrap(src),
dst: lift.Wrap(dst),
signature: lift.T[opFunc[OP, SRC, DST]](),
}
}
func liftHandler[OP any, SRC any, DST any](fn opFunc[OP, SRC, DST]) evFunc {
return func(ev event) bool {
op, opOk := lift.Unwrap[OP](ev.op)
src, srcOk := lift.Unwrap[SRC](ev.src)
dst, dstOk := lift.Unwrap[DST](ev.dst)
if !opOk || !srcOk || !dstOk {
return false
}
return fn(op, src, dst)
}
}
// DUMMY TYPES
// dummy mouse event types
type mouseClick struct{}
type mouseDrop struct{}
// dummy file types
type file struct {
path, data string
}
type folder struct {
path string
locked bool
files []*file
}
// HANDLERS & DISPATCH
func rejectLockedFolder(ev event) (rejected bool) {
if dir, isDir := lift.Unwrap[*folder](ev.src); isDir && dir.locked {
return true
}
if dir, isDir := lift.Unwrap[*folder](ev.dst); isDir && dir.locked {
return true
}
return false
}
func openFile(op mouseClick, f *file, _ lift.Empty) bool {
fmt.Printf("%s:\n\t%s\n", f.path, f.data)
return true
}
func listFiles(op mouseClick, dir *folder, _ lift.Empty) bool {
fmt.Printf("%s:\n", dir.path)
for _, f := range dir.files {
fmt.Println("\t", f.path)
}
return true
}
func moveFile(op mouseDrop, f *file, dir *folder) bool {
dir.files = append(dir.files, f)
return true
}
func eventDispatch(ev event) {
switch {
case rejectLockedFolder(ev):
case liftHandler(openFile)(ev):
case liftHandler(listFiles)(ev):
case liftHandler(moveFile)(ev):
}
}
Output: tableflip.jif: (╯°□°)╯︵ ┻━┻ home: tableflip.jif
Example (D_calculator) ¶
This example emulates a pocket calculator, modeled as a finite state machine. Current state is maintained by a Map of transition functions. Inputs are parsed to Sym. The evaluaton loop takes one Sym, finds the associated edge in the calculator state, and dispatches that function.
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
// This example emulates a pocket calculator, modeled as a finite state machine.
// Current state is maintained by a [Map] of transition functions. Inputs are parsed to [Sym].
// The evaluaton loop takes one [Sym], finds the associated edge in the calculator state, and
// dispatches that function.
func main() {
calculate("1+2*3=-4=C/=-5C-56=7+8=9=")
// 1+
// > 1
// 2*
// > 3
// 3=
// > 9
// -4=
// > 5
// C/
// > 0
// =
// > DIVZERO!
// -5C-
// > 0
// 56=
// > -56
// 7+
// > 7
// 8=
// > 15
// 9=
// > 9
}
func calculate(input string) {
c := newCalculator()
for _, r := range input {
sym := parseCalculatorKey(r)
edge, _ := lift.LoadSym(c.state, sym)
edge(c, sym)
}
}
// CALCULATOR
type calc struct {
state lift.Map[edgeFunc]
acc, res int
op
}
type edgeFunc = func(*calc, lift.Sym)
type op = func(*calc) error
func newCalculator() *calc {
c := new(calc)
c.state = lift.NewMap[edgeFunc](
lift.Def[keyC](clear),
lift.Def[keyEq](eq),
)
c.reset()
c.enterStart()
return c
}
// STATES
func (c *calc) enterStart() {
c.state.Store(
lift.Def[keyOp](eval),
lift.Def[keyNum](beginAcc),
)
}
func (c *calc) enterAccumulate() {
c.state.Store(
lift.Def[keyOp](eval),
lift.Def[keyNum](acc),
)
}
func (c *calc) enterEvaluated() {
c.state.Store(
lift.Def[keyOp](store),
lift.Def[keyNum](resetAcc),
)
}
func (c *calc) enterErr() {
c.state.Store(
lift.Def[keyOp](nop),
lift.Def[keyNum](nop),
)
}
// EDGES
func clear(c *calc, _ lift.Sym) {
c.reset()
c.enterStart()
}
func eq(c *calc, _ lift.Sym) {
if err := c.evaluate(); err != nil {
c.enterErr()
return
}
c.enterEvaluated()
}
func eval(c *calc, sym lift.Sym) {
if err := c.evaluate(); err != nil {
c.enterErr()
return
}
store(c, sym)
}
func store(c *calc, sym lift.Sym) {
c.op = lift.MustUnwrap[keyOp](sym)
c.enterStart()
}
func acc(c *calc, sym lift.Sym) {
digit := lift.MustUnwrap[keyNum](sym)
c.acc *= 10
c.acc += digit
c.enterAccumulate()
}
func beginAcc(c *calc, sym lift.Sym) {
digit := lift.MustUnwrap[keyNum](sym)
if digit == 0 {
return
}
c.acc = 0
acc(c, sym)
}
func resetAcc(c *calc, sym lift.Sym) {
c.reset()
beginAcc(c, sym)
}
func nop(c *calc, _ lift.Sym) {}
// METHODS
func (c *calc) evaluate() error {
fmt.Print("\n> ")
if err := c.op(c); err != nil {
fmt.Println(err.Error())
return err
}
fmt.Printf("%8d\n", c.res)
return nil
}
func (c *calc) reset() {
c.acc, c.res = 0, 0
c.op = (*calc).add
}
func (c *calc) add() error {
c.res += c.acc
return nil
}
func (c *calc) sub() error {
c.res -= c.acc
return nil
}
func (c *calc) mul() error {
c.res *= c.acc
return nil
}
func (c *calc) div() error {
if c.acc == 0 {
return fmt.Errorf("DIVZERO!")
}
c.res /= c.acc
return nil
}
// PARSING
type keyC struct{}
type keyEq struct{}
type keyOp = func(*calc) error
type keyNum = int
func parseCalculatorKey(r rune) lift.Sym {
fmt.Printf("%c", r)
switch r {
case 'C':
return lift.Wrap(keyC{})
case '=':
return lift.Wrap(keyEq{})
case '+':
return lift.Wrap((*calc).add)
case '-':
return lift.Wrap((*calc).sub)
case '*':
return lift.Wrap((*calc).mul)
case '/':
return lift.Wrap((*calc).div)
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return lift.Wrap(keyNum(r - '0'))
default:
return lift.Wrap(keyC{})
}
}
Example (E_emptyAnyNil) ¶
Corner cases of Sym around empty-ish or any-ish values are reasonable. Sym is an interface type, so the zero value of a Sym is nil-ish, and will cause panic.
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("oops!", r)
}
}()
items := lift.NewMap[string](
lift.Def[struct{}]("the empty struct"),
lift.Def[lift.Empty]("the lift.Empty struct"),
lift.Def[any]("anything"),
lift.Def[lift.Sym]("the type enumeration of lift.Sym"),
)
report := func(tag string, item string) {
fmt.Printf("%-12s %s,\n", tag, item)
}
// Three kinds of the empty struct{}
empty := struct{}{}
item, _ := lift.LoadTypeOf(items, empty)
report("empty i", item)
item, _ = lift.Load[lift.Empty](items)
report("empty ii", item)
type local struct{}
item, _ = lift.LoadTypeOf(items, local{})
report("empty iii", item)
// Three kinds of any
item, _ = lift.Load[any](items)
report("any i", item)
item, _ = lift.LoadTypeOf(items, any("other thing"))
report("any ii", item)
item, _ = lift.LoadSym(items, lift.Any)
report("any iii", item)
// LoadTypeOf infers the type enumeration of lift.Sym from anything wrapped
item, _ = lift.LoadTypeOf(items, lift.Wrap(any(nil)))
report("sym i", item)
// Doesn't crash yet, as we're passing the type enumeration of lift.Sym
var NilSym lift.Sym
item, _ = lift.LoadTypeOf(items, NilSym)
report("sym ii", item)
// PANIC ENSUES - we've passed a raw nil
item, _ = lift.LoadSym(items, NilSym)
report("nil i", item)
// (unreached) PANIC ENSUES:
items.Store(
lift.DefSym(NilSym, "panic"),
)
}
Output: empty i the empty struct, empty ii the lift.Empty struct, empty iii , any i anything, any ii anything, any iii anything, sym i the type enumeration of lift.Sym, sym ii the type enumeration of lift.Sym, oops! runtime error: invalid memory address or nil pointer dereference
Index ¶
- Variables
- func EnumIs[T any](sym Sym) bool
- func Load[K any, V any](m Map[V]) (v V, ok bool)
- func LoadSym[V any](m Map[V], sym Sym) (v V, ok bool)
- func LoadTypeOf[K any, V any](m Map[V], _ K) (v V, ok bool)
- func MustUnwrap[T any](sym Sym) T
- func MustUnwrapAs[T any](sym Sym) T
- func Unwrap[T any](sym Sym) (t T, ok bool)
- func UnwrapAs[T any](sym Sym) (t T, ok bool)
- type Empty
- type Entry
- type Map
- type Sym
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Any = enum[any]{}
Any is the type enumeration symbol of interface{}
Functions ¶
func EnumIs ¶
EnumIs determines equivalence of two type enumerations: one derived from T, the other from the argument
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
a := lift.Wrap(false)
fmt.Println(lift.EnumIs[bool](a))
}
Output: true
func Load ¶
Load returns a value from a Map, if found.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
masks := lift.NewMap[int](
lift.Def[uint8](0xff),
)
mask, _ := lift.Load[uint8](masks)
fmt.Println(mask&0x1234_5678 == 0x78)
if _, ok := lift.Load[string](masks); !ok {
fmt.Println("oops")
}
}
Output: true oops
func LoadSym ¶
LoadSym resembles Load, where the type enumeration key is lifted in the second argument.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
type hammer struct{}
type screwdriver struct{}
tools := lift.NewMap[int](
lift.Def[hammer](2),
lift.Def[screwdriver](17),
)
var count int
for _, tool := range tools.Keys() {
n, _ := lift.LoadSym(tools, tool)
count += n
}
fmt.Printf("I have %d tools", count)
}
Output: I have 19 tools
func LoadTypeOf ¶
LoadTypeOf resembles Load, with type parameter K inferred from a second argument.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
fmtrs := lift.NewMap[string](
lift.Def[uint8]("%08x"),
)
x := uint8(127)
fmtr, _ := lift.LoadTypeOf(fmtrs, x)
fmt.Printf(fmtr, x)
}
Output: 0000007f
func MustUnwrap ¶
MustUnwrap is a fail-fast version of Unwrap. Unwrapping is expected to succed, and failure to unwrap panics.
func MustUnwrapAs ¶
MustUnwrapAs is a fail-fast version of UnwrapAs. Unwrapping is expected to succed, and failure to unwrap panics.
func Unwrap ¶
Unwrap recovers a value of type T. It is successful when the Sym to unwrap was produced by Wrap, and T precisely matches the flavor of T inferred by Wrap.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
bits := byte(0)
sym := lift.Wrap(bits)
if _, ok := lift.Unwrap[byte](sym); ok {
fmt.Print("got a byte")
}
}
Output: got a byte
func UnwrapAs ¶
UnwrapAs resembles Unwrap, but is successful when the wrapped value satisfies an interface type T.
Example (Interface) ¶
This works. A pointer to a strings.Builder is an io.Writer.
package main
import (
"fmt"
"io"
"strings"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
var b strings.Builder
sym := lift.Wrap(&b)
if _, ok := lift.UnwrapAs[io.Writer](sym); ok {
fmt.Printf("unwrapped a Writer!")
}
}
Output: unwrapped a Writer!
Example (NoConversion) ¶
This won't work. UnwrapAs performs type assertion, not conversion.
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
type bit bool
sym := lift.Wrap(bit(true))
if bit, ok := lift.UnwrapAs[bool](sym); ok {
fmt.Printf("%v\n", bit)
} else {
fmt.Println("?")
}
}
Output: ?
Types ¶
type Entry ¶
type Entry[V any] struct { // contains filtered or unexported fields }
Entry encapsulates a definition of a single Map association.
func Def ¶
Def constructs Map entries.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
type text struct{}
type mauve struct{}
crayons := lift.NewMap[int](
lift.Def[text](0x222222),
lift.Def[mauve](0xa17188),
)
headline, _ := lift.Load[mauve](crayons)
paragraph, _ := lift.Load[text](crayons)
fmt.Printf("#%06x, #%06x", headline, paragraph)
}
Output: #a17188, #222222
func DefSym ¶
DefSym constructs Map entries. Unlike Def, the key flavor is already lifted in the Sym.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
items := lift.NewMap[string](
lift.DefSym(lift.Any, "could be anything"),
)
fish := lift.Wrap(any("it's a fish!"))
anything, _ := lift.LoadSym(items, fish)
fmt.Printf(anything)
}
Output: could be anything
type Map ¶
type Map[V any] struct { // contains filtered or unexported fields }
Map defines associations between type enumerations and values of type V.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
// Map creation
m := lift.NewMap[string](
lift.Def[int]("red"),
lift.Def[float64]("blue"),
)
type vector2d [2]float64
// Storing to a Map
m.Store(
lift.Def[complex128]("C"),
lift.Def[vector2d]("R2"),
)
// Loading from a Map
intColor, _ := lift.LoadTypeOf(m, 1)
floatColor, _ := lift.LoadSym(m, lift.TypeOf(1.0))
complexDomain, _ := lift.Load[complex128](m)
vector2dDomain, _ := lift.Load[vector2d](m)
fmt.Printf("ints are %s, float64s are %s\n", intColor, floatColor)
fmt.Printf("complex128s live in %s (not %s)", complexDomain, vector2dDomain)
}
Output: ints are red, float64s are blue complex128s live in C (not R2)
func (Map[V]) Delete ¶
Delete removes a variadic list of type enumerations from a Map.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
m := lift.NewMap[bool](
lift.Def[rune](true),
lift.Def[string](true),
)
m.Delete(lift.T[rune]())
if _, ok := lift.Load[rune](m); !ok {
fmt.Println("No entry found")
}
}
Output: No entry found
func (Map[V]) Store ¶
Store stores a variadic list of entries in a Map.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
type Queen struct{}
black := lift.NewMap[rune]()
black.Store(
lift.Def[Queen]('♛'),
)
piece, _ := lift.Load[Queen](black)
fmt.Printf("%c", piece)
}
Output: ♛
type Sym ¶
type Sym interface {
// contains filtered or unexported methods
}
A Sym is an interface protecting internal methods for producing type enumeration symbols.
func T ¶
The (function) T returns a (type) T-flavored type enumeration symbol.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
r := lift.T[rune]()
b := lift.T[byte]()
fmt.Println(r == b)
}
Output: false
Example (Alias) ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
i := lift.T[int32]()
r := lift.T[rune]()
// because rune is an alias of int32, the output is "true"
fmt.Println(i == r)
}
Output: true
func TypeOf ¶
TypeOf returns a T-flavored type enumeration symbol.
Example ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
t := lift.T[string]()
fmt.Println(t == lift.TypeOf(""))
fmt.Println(t == lift.TypeOf("twine"))
fmt.Println(t == lift.TypeOf([]rune{}))
}
Output: true true false
func Wrap ¶
Wrap produces a Sym, like T or TypeOf. Unlike T or TypeOf, the result may be unwrapped to recover a wrapped value.
Example ¶
package main
import (
"fmt"
"math"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
π := lift.Wrap(math.Pi)
pi, _ := lift.Unwrap[float64](π)
fmt.Println(pi == math.Pi)
}
Output: true
Example (Equivalence) ¶
package main
import (
"fmt"
"github.com/AndrewHarrisSPU/lift"
)
func main() {
sym := lift.Wrap('?')
// the wrapped symbol is not == to rune's type enumeration symbol
fmt.Println(lift.T[rune]() == sym)
// EnumIs[rune] evaluates true, however
fmt.Println(lift.EnumIs[rune](sym))
}
Output: false true