I created a custom List-Datatype similar to the tutorial on https://ericpony.github.io/z3py-tutorial/advanced-examples.htm:
def ListSort(sort):
# Declare a List of sort elements
listType = Datatype('List(%s)' % str(sort))
listType.declare('cons', ('head', sort), ('tail', listType))
listType.declare('empty')
return listType.create()
IntListSort = ListSort(IntSort())
I now want to use this type, but when solving the following formula, I get a weird result:
solve(Int("x") == 1 + IntListSort.head(IntListSort.empty))
Output
[x = 1 + head(empty)]
While this sounds like a perfectly reasonable result when just looking at the code, this was definitely not my intention. head(empty) shouldn't really return an element of sort IntSort(). head(List) shouldn't even be available for empty lists, only for lists created using cons.
This might not be an issue for programs where I manually craft the equations to be solved, but I need to use this in a more complex context, and my program just gave me such an "integer" when looking for a counter example, but such a result is not really helpful.
Is there a way to restrict head such that it can only be applied to lists constructed using cons?
One idea I thought about was to create a separate type called EmptyListSort, but I would still need to allow ListSorts to be empty as well, so this would lead back to the same issue...
SMTLib is a logic of total-functions. When you define an algebraic data-type, then all the functions that get defined along with that data-type (i.e.,
head,tailin your example) are always total. That is, they can be applied to any list value, regardless of how that value is constructed.There's nothing in the type-system that'd allow you to prohibit this. (Doing so would require a notion of dependent types, and SMTLib's type system is nowhere near that sophisticated.)
Then, you might ask, what happens if you apply
headtoempty: The logic leaves it underspecified: That is, the solver can pick any value it wants for the result ofhead(empty). It can even pick different values if you use this expression twice in different contexts.So, how does one avoid the problem you're having? Well, you have to add your own constraints when you apply
head. That is, you have to program such that ifheadis called onempty, you force the solver to be in anunsatstate. To do so, I'd define a custom version of head, that asserts the required condition:This definition of
customHeadis equivalent tohead, except it forces all the arguments it receives to beconsnodes. Now you can use it like this:And this prints:
That is, it "catches" the bad case you wanted to avoid, by forcing the solver to return
unsat. This may or may not be suitable for your eventual goal; but this is the only way to model such behavior.And here is what happens if we pass a
conslist:This prints:
as you'd expect.