How to read these expressions precisely, is a must-know.
I decided to exhaustively experiment to be sure to understand this
important part, as it’s very common.
Instead of defining a single signature for empty, I defined multiple, as
use showtype to see how it interprets it.
I noticed the type shown is not the same, when doing something like this…
val v = f()
val _ = showtype(v)
…and something like this…
val _ = showtype(f())
That’s for the readable introduction, here is the full test case (not meant
to be useful with real case, just useful to precisely understand the
expressions):
extern fn empty1(): set
extern fn empty2(): [s: set] set s
extern fn empty3(): {i: item} set
extern fn empty4(): {i: item} [s: set] set s
extern fn empty5(): [s: set] {i: item} set s
extern fn empty6(): [s: set] {i: item | ~has(s, i)} set s
val s = empty1()
val _ = showtype(empty1())
val _ = showtype(s)
val s = empty2()
val _ = showtype(empty2())
val _ = showtype(s)
val s = empty3()
val _ = showtype(empty3())
val _ = showtype(s)
val s = empty4()
val _ = showtype(empty4())
val _ = showtype(s)
val s = empty5()
val _ = showtype(empty5())
val _ = showtype(s)
val s = empty6()
val _ = showtype(empty6())
val _ = showtype(s)
As an example, in the first group of test, showtype(empty1()) says
(dropping the sort to make it shorter):
S2Ecst(set)
while showtype(s) says:
S2Eapp(S2Ecst(set); S2Evar(s))
Why isn’t it the same? As there is no type annotation on s, why does it
change?
I noticed showtype(f()) always shows the exact same as in the signature
of f, but not the same on a value which is bound to f().