Invariants.jl icon indicating copy to clipboard operation
Invariants.jl copied to clipboard

Basic interface discussion

Open lorenzoh opened this issue 2 years ago • 10 comments

I just pushed some preliminary docs, @rafaqz would be great to get some feedback on the high-level API before I move the core pieces to a base package :)

See https://lorenzoh.github.io/Invariants.jl/dev/documents/docs/guide.md and https://lorenzoh.github.io/Invariants.jl/dev/documents/docs/examples.md

lorenzoh avatar Jun 16 '22 09:06 lorenzoh

Lgtm

Only thing I wondered is if there should be a check(Bool, invariant, x) that returns a Bool instead if throwing the error.

rafaqz avatar Jun 17 '22 16:06 rafaqz

Will release a first version then 👍

There is, see https://lorenzoh.github.io/Invariants.jl/dev/sourcefiles/Invariants/src/check.jl

lorenzoh avatar Jun 18 '22 06:06 lorenzoh

Now that inv(input) and convert(Bool, inv(input)) can be used to check in an invariant, the last remaining part of the interface left to resolve is how to check an invariant and throw an error if it is violated. There is a check_throw function right now, but maybe a cleaner usage that doesn't introduce a new symbol is possible.

One way could be throw(inv(input)) that throws an InvariantException only if the invariant is violated.

Thoughts @rafaqz ?

EDIT: adding a method to throw isn't possible since it's built-in. But happy to hear other suggestions

lorenzoh avatar Jul 04 '22 11:07 lorenzoh

Yes exactly, I was wondering how to handle this. You can probably use rethrow ? and locally packages can then define:

_throw(name, ::Bool) = error("$name failed)
_throw(name, x) = rethrow(x)

Then

convert(Bool, result) || _throw(testname, result)

And that will handle Bool or more complex objects that define their own rethrow method.

Edit: this could also allow you to use try ... catch in invariant to e.g. catch method errors then rethrow them after printing the invariant messages. I've actually been wondering how to do this too.

rafaqz avatar Jul 04 '22 12:07 rafaqz

check_throw already does this and throws a InvariantException which ensures that the error messages are displayed as rich text. I was thinking more of an alternative syntax that doesn't require importing check_throw.

How about check(Exception, invariant, input)/invariant(Exception, input)?

lorenzoh avatar Jul 04 '22 14:07 lorenzoh

What does check_throw already do? Sorry I don't understand.

I can only call base methods from Interfaces.jl because I intend to not depend on Invariants.jl - keeping things decoupled is best. I'm trying to facilitate users using invariant if they want to.

So check and check_throw are not options I have available. Nor is passing Exception to anything.

Simply defining rethrow(::CheckReturn) may be enough for my needs without an Invariants.jl dep.

rafaqz avatar Jul 04 '22 17:07 rafaqz

Although not hard to understand, the "Combining invariants" part in the docs seems not that intuitive to me (perhaps it's because the codes has 4 levels of identation)

Instead of

inv_indexing = invariant(
    "`xs` is indexable",
    [
        Invariants.hasmethod_invariant(length, :xs),
        invariant("`xs is not empty`") do (; xs)
            isempty(xs) ? "`xs` is empty!" : nothing
        end,
        Invariants.hasmethod_invariant(Base.getindex, :xs, :idx => 1),
    ],
    inputfn = xs -> (; xs)
)
check(inv_indexing, [1, 2, 3])

how about flattening the codes by overriding && and ||?

has_length = Invariants.hasmethod_invariant(length, :xs)
has_getindex = Invariants.hasmethod_invariant(Base.getindex, :xs, :idx => 1)
is_array_like = invariant("is arraylike", has_length && has_getindex)
non_empty =  invariant("`xs is not empty`") do (; xs)
    isempty(xs) ? "`xs` is empty!" : nothing
end

inv_indexing = invariant("`xs` is indexable", non_empty && is_array_like)
check(inv_indexing, [1, 2, 3])

The existence of inputfn seems not very friendly to composibility at first glance, but I'm not sure how to make it better.

johnnychen94 avatar Jul 19 '22 17:07 johnnychen94

Splitting it up into multiple lines makes it much more readable, that's a good idea!

How does inputfn make it less composable?

lorenzoh avatar Jul 27 '22 14:07 lorenzoh

How does inputfn make it less composable?

We might need to configure inputfn for each invariant, is it possible to compose multiple invariants together without messing up with inputfn?

johnnychen94 avatar Jul 27 '22 14:07 johnnychen94

You can wrap any invariant in a ˋinvariant(inv; inputfn)ˋ and then compose those. Does that solve what you're trying to do?

lorenzoh avatar Jul 28 '22 09:07 lorenzoh