Ino is a DSL to generate data structures for golang.
go install github.com/nihei9/ino/cmd/ino@latest
First, save your source code written in Ino to a file with the extension .ino
. In this example, let's say you saved the file named example.ino
.
Next, convert the source code in Ino to golang using ino
command. --package
flag allows you to change the package name. The default package name is main
.
ino --package example
Then you will get files example.go
and ino_builtin.go
.
You can also use go:generate
directive.
//go:generate ino --package example
Source code is Unicode text encoded in UTF-8.
Identifiers name types and variables. The form of identifiers is [A-Za-z][A-Za-z0-9]*
in the regular expression. Identifiers in Ino are limited in the number of characters we can use compared to golang.
The following keywords may not be used as identifiers.
data
Ino currently supports Int
and String
built-in types, corresponding to int
and string
of golang, respectively.
data
keyword allows you to generate a sum type.
data Pen
= BallpointPen String
| FountainPen
;
The following three public APIs are available.
type Pen interface
func BallpointPen(String) Pen
: returns aBallpointPen
variant ofPen
typefunc FountainPen() Pen
: returns aFountainPen
variant ofPen
type
Now, you can check that a value of Pen
is which variant using OK
method and can retrieve fields using Fields
method.
Fields
method isn't available in FountainPen
because it has no fields.
bpB := BallpointPen(String("Black"))
ok := bpB.Maybe().BallpointPen().OK() // true
ok = bpB.Maybe().FountainPen().OK() // false
color, ok := bpB.Maybe().BallpointPen().Fields() // String("Black"), true
color, ok = bpB.Maybe().FountainPen().Fields() // String(""), false
fp := FountainPen()
ok = fp.Maybe().FountainPen().OK() // true
ok = fp.Maybe().BallpointPen().OK() // false
The following helpful APIs are also available.
func NewPenCaseSet[U any](...*case_Pen[U]) (*PenCaseSet[U], error)
andfunc (s *PenCaseSet[U]) Match(x Pen) (U, error)
: run pattern matching.func ApplyToBallpointPen[T any](x Pen, f func(String) T) (T, bool)
: whenx
is aBallpointPen
variant, appliesf
tox
's fields and retuns the result andtrue
.func ApplyToFountainPen[T any](x Pen, f func() T) (T, bool)
: whenx
is aFountainPen
variant, runsf
and returns the result andtrue
.
The result type of Match
method and ApplyTo*
function are polymorphic, so you can use an arbitrary type for the result type of the callback function.
Pattern matching:
New*CaseSet
function performs exhaustiveness checking, but the checking guarantees only the exhaustiveness of tags. In other words, the exhaustive of parameters is not guaranteed.
cases, err := NewPenCaseSet(
CaseBallpointPen(BallpointPen(String("Black")), func(color String) string {
return "ballpoint-"+string(color)
}),
CaseFountainPen(FountainPen(), func() string {
return "fountain"
}),
)
if err != nil {
// When the above cases are not exhaustive pattern, an error occurs.
}
pen := BallpointPen(String("Black"))
result, err := cases.Match(pen)
if err != nil {
// When `pen` doesn't match any of the patterns, an error occurs.
}
fmt.Print(result) // ballpoint-Black
ApplyTo*
:
color2Num := func(color String) int {
switch color {
case "Black": return 1
default: return 9
}
}
bpB := BallpointPen(String("Black"))
bpR := BallpointPen(String("Red"))
fp := FountainPen()
v, ok := ApplyToBallpointPen(bpB, color2Num) // 1, true
v, ok = ApplyToBallpointPen(bpR, color2Num) // 9, true
v, ok = ApplyToBallpointPen(fp, color2Num) // 0, false
You can define polymorphic types using type variables.
Type variables can follow a data type name. In this example, a
is a type variable.
data Option a
= None
| Some a
;
type Option[T Eqer] interface
func None[T Eqer]() Option[T]
: returns aNone
variant ofOption
typefunc Some[T Eqer](p1 T) Option[T]
: returns aSome
variant ofOption
type
s1 := Some(Int(100)) // Option[Int]
n := None[Int]() // Option[Int]
s2 := Some(Some(String("Hello"))) // Option[Option[String]]
num, ok := s1.Maybe().Some().Fields() // Int(100), true
opt, ok := s2.Maybe().Some().Fields() // Some(String("Hello")), true
str, ok := opt.Maybe().Some().Fields() // String("Hello"), true
Pattern matching APIs and ApplyTo*
functions are also available.
func NewOptionCaseSet[T Eqer, U any](...*case_Option[T, U]) (*OptionCaseSet[T, U], error)
andfunc (s *OptionCaseSet[T, U]) Match(x Option[T]) (U, error)
func ApplyToNone[T Eqer, U any](x Option[T], f func() U) (U, bool)
func ApplyToSome[T Eqer, U any](x Option[T], f func(T) U) (U, bool)
Polymorphic-type literals must be enclosed in parentheses.
data List a
= Nil
| Cons a (List a)
;