Absent values

A field the source didn't state is absent, which isn't the same as empty, and how to ask about one.

A source can leave a field out. A datasheet row might state a maximum and no minimum, and a package might declare no license. jaala keeps that apart from a field stated as the empty string, because “not stated” and “stated as nothing” are different facts, and a query that mixed them up would answer the wrong question without saying so. As a host, you serve an absent field as ns.Absent(), which is a bit more work than an empty string.

package guide_test

import (
	"context"
	"fmt"

	"github.com/panyam/jaala/datalog"
	"github.com/panyam/jaala/ns"
)

// An absent value is a field the source didn't state. It sorts before
// everything else, it isn't the same as "", and absent(?x) asks about it.
func Example_absent() {
	b, ctx := base(), context.Background()
	for _, text := range []string{
		`pkg(?p, ?l) => ?l, ?p`,
		`pkg(?p, ?l), absent(?l) => ?p`,
		`pkg(?p, ?l), not absent(?l) => ?p`,
		`pkg(?p, "") => ?p`,
	} {
		rows, err := datalog.SemiNaive{}.Eval(ctx, datalog.MustParse(text), b)
		if err != nil {
			fmt.Println(err)
			continue
		}
		fmt.Println(text)
		for _, r := range rows {
			if l, ok := r.Bind["l"]; ok {
				fmt.Println("  ", r.Bind["p"].S, show(l))
			} else {
				fmt.Println("  ", r.Bind["p"].S)
			}
		}
	}
	// Output:
	// pkg(?p, ?l) => ?l, ?p
	//    api absent
	//    log ""
	//    util "Apache-2.0"
	//    app "MIT"
	// pkg(?p, ?l), absent(?l) => ?p
	//    api
	// pkg(?p, ?l), not absent(?l) => ?p
	//    app
	//    log
	//    util
	// pkg(?p, "") => ?p
	//    log
}

// show prints a value as a host might: quoted text, or "absent".
func show(v ns.Value) string {
	if v.Absent {
		return "absent"
	}
	return fmt.Sprintf("%q", v.S)
}

How an absent value behaves ΒΆ

  • It sorts before every number and every piece of text, so in the default order absent rows come first.
  • It isn’t equal to "". pkg(?p, "") finds log, which states an empty license, and not api, which states none. Grouping keeps them apart too.
  • absent(?x) holds for an absent value and nothing else, so it’s the way to ask for one. not absent(?x) reads “this field is stated”, which you’ll probably want about as often.
  • ValidateBound checks a query before the host has the values it will bind. It stands an absent value in for each one, which neither the type check nor a closed set of allowed values refuses, so the check judges the query and not a placeholder.