Meow Programming Language v0.20.0: A Value Read Out of Go Remembers Where It Came From

Released 2026-08-19. nab go lets a .nyan program reach a Go package with no wrapper written for it. In v0.20.0 the value that comes back stops forgetting what it is.

Reading a Go value was what made it unusable

The rule ran backwards: the more of a Go type Meow could read, the less of it was left to use. A *url.URL was read into a basket, and once it was a basket the methods of the URL were gone.

nab go "net/url" tag u

nyan p = u.parse("https://example.com/a/b?x=1")
nya(p["host"])      # example.com   — readable
nya(p.string())     # Hiss! Cannot call string on a Map, nya~

Four shapes lost something this way:

Read asWhat was lost
*url.URL → basket.string(), .hostname(), .query()
time.Time → its text.year(), .format(), .before(), .add()
time.Duration → an integer.minutes(), .seconds()
url.Values → basket.get(), .encode()

What is read now remembers what it was read out of

A member Meow has nothing for is asked of the Go value itself, so both questions have an answer:

nab go "net/url" tag u
nab go "time"

nyan p = u.parse("https://example.com/a/b?x=1")
nya(p["host"])       # example.com
nya(p.hostname())    # example.com

nyan d = time.ParseDuration("90m")
nya(d)               # 5400000000000
nya(d.minutes())     # 90

Only what was read remembers. A basket a program wrote itself remembers nothing, and a plain string or a plain number keeps nothing either — there is no more of it to reach.

Handing a value on keeps what the reading did not say

A time.Time used to cross into the next call as RFC 3339 text and be parsed back, arriving up to a second wrong. It now crosses as the time.Time it is:

nab go "time" tag t

nyan a = t.Date(2026, 1, 2, 3, 4, 5, 500000000, t.UTC)
nyan b = t.Date(2026, 1, 2, 3, 4, 5, 0, t.UTC)
nya(a)          # 2026-01-02T03:04:05Z
nya(b)          # 2026-01-02T03:04:05Z   — the same text
nya(a.sub(b))   # 500000000              — not the same time

A method with nothing to say gives back what it was called on

Go has many methods called to do rather than to say — Set, Add, Reset, Close. They all answered catnap, which left the doing with nowhere to show. The change now arrives as a new value beside the old one, which is how a language of unchanging values says that something happened:

nab go "net/url" tag u

nyan q = u.ParseQuery("x=1&y=2")
nya(q.set("z", "9"))    # {x: [1], y: [2], z: [9]}
nya(q)                  # {x: [1], y: [2]}

Having no answer is still not the same as answering with nothing: a search that found nothing answers catnap, and a method that failed answers with the furball.

Two smaller changes ride along. A value read out of Go now satisfies a Go interface it implements, so a named integer whose String() is callable is accepted where a fmt.Stringer is asked for. And nya(basket) sorts its keys, which it had not done — it iterated a Go map, so it printed differently between runs.

Upgrading

Nothing to change. Every guard that refused before still refuses: a program-written basket still gives Cannot call string on a Map, a plain string still refuses, and d == 5400000000000 is still true. Code that worked around the gap by pulling fields out of a basket keeps working; it can now be written as the method call it wanted to be.

brew upgrade meow
# or
go install github.com/135yshr/meow/cmd/meow@v0.20.0

See Importing a Go package in the specification for what comes back and how it is read.