sum type generator for go 1.18+
For a module with natsu
in the go.mod
file, a sum type can be generated by adding the following to a .go
source file:
//go:generate go run github.com/butzopower/natsu <pkg> <union> <sumTypeToGenerate>
For example:
package models
type Cat struct { NumLives int }
type Dog struct { Tricks []string }
//go:generate go run github.com/butzopower/natsu my/package/models pet Pet
type pet interface {
Cat | Dog
}
Running go generate
in this directory will generate a file pet.go
that can be used like so:
package main
import "my/packages/models"
func main() {
var petCat models.Pet
var petDog models.Pet
petCat = models.PetOf(models.Cat{NumLives: 9})
petDog = models.PetOf(models.Dog{Tricks: []string{"sit", "stay"}})
exec := models.PetExecutor().
WithCat(func(cat models.Cat) { println("the cat has " + cat.NumLives + " lives")}).
WithDog(func(dog models.Dog) {
println("our dog knows the following tricks:")
for _, trick := range dog.Tricks {
println(" - " + trick)
}
})
exec.Exec(petCat) // output:
// the cat has 9 lives
exec.Exec(petDog) // output:
// our dog knows the following tricks:
// - sit
// - stay
mapFn := models.PetMapper[string]().
WithCat(func(cat models.Cat) string {
return "<div class=\"cat\">Lives: " + cat.NumLives + "</div>"
}).
WithDog(func(dog models.Dog) string {
var sb strings.Builder
sb.WriteString("<ul>")
for _, trick := range dog.Tricks {
sb.WriteString("<li>" + trick + "</li>")
}
sb.WriteString("</ul>")
return sb.String()
})
catElement := mapFn.Map(petCat)
dogElement := mapFn.Map(petCat)
print(catElement) // <div class="cat">Lives: 9</div>
print(dogElement) // <ul><li>sit</li><li>stay</li></ul>
}
What benefit does a sum type provide over a type switch?
Consider the below example of function constrained by a type union that uses a type switch:
type Cat struct {
Name string
SharpClaws bool
}
type Dog struct {
Name string
Trained bool
}
type Pet interface {
Cat | Dog
}
func Cuddle[T Pet](pet T) {
switch p := any(pet).(type) { // ❌ required as can not switch on type constraint
case Cat:
if p.SharpClaws {
print("ow, it scratched me")
}
case Dog:
if !p.Trained {
print("ah, it slobbered me")
}
case string: // ❌ allowed as can not check exhaustively
print("uh wut")
default: // ❌ required as can not check exhaustively
print("there is no pet?")
}
}
func main() {
Cuddle(Cat{Name: "Tex", SharpClaws: true})
Cuddle(Dog{Name: "Fifi", Trained: false})
// ✅ does not compile: string does not implement Pet
Cuddle("strings are what cats play with")
}