M2
M2 copied to clipboard
Catching uncaught type errors
In the documentation node Tutorial: Divisors, there's this code:
i54 : effective = (D) -> (
LB := globalSections D;
L := LB#0; -- the matrix of numerators
if numgens source L == 0
then error(toString D + " is not effective")
else divisor sectionIdeal(L_(0,0), LB#1, D));
i55 : effective(2 R - P)
o55 = Divisor{ideal (z, x), ideal 1}
o55 : Divisor
which seems fine on the surface, until you realize that toString D + " is not effective" is an error, since String + String is not defined! Perhaps this used to be defined, but was removed at some point and because that branch in the code was never tested in the documentation, nobody realized that this is an error now.
I'm curious whether other languages have mechanisms to detect type errors like this. @DanGrayson was there ever a plan for the interpreter to catch errors like this using the typicalValues hash table or pseudocode or disassemble?
I understand that a user may define a function like this and define the missing methods later, but is there a way to check if every method call in a function is well-defined at a given point in time? e.g. isWellDefined effective?
For instance, disassemble effective yields a lisp like string and I think this section contains the error:
(adjacent (global-fetch 410) (2-OP + (adjacent (global-fetch 1067) (fetch 0 0)) " is not effective"))
I can guess what adjacent and 2-OP mean and I presume global-fetch 410 is error and global-fetch 1067 is toString. Is there anyway to get the function corresponding to these numbers in the top level? If so, then from typicalValues#toString it would be apparent that the binary operator + is not defined for two strings.
This would be a very useful check for packages, and to make sure changes in the engine don't break untested code.
@pzinn since you've been looking at this code, do you have any insight on this?
it's not so clear how to use pseudocode for this:
one can isolate the offending part of the code, but then one can't evaluate it because it contains a local variable...
But the type of the local variables should be determinable (at least in some cases, and maybe eventually in most/all cases), no?
how? M2 is not a typed language... except for arguments of methods I suppose?
Like this:
i2 : typicalValues#toString
o2 = String
o2 : Type
That's the whole point of tvalues.m2 and typicalvalues.m2 and all that.
okay. a related thing which annoys me is the content of robust.m2, all these "fake" methods that produce an error.
for example a script might go through the code above, conclude that the two arguments of + are strings, then try
i1 : lookup(symbol +,String,String)
o1 = FunctionClosure[/home/pzinn/M2/M2/Macaulay2/m2/robust.m2:93:41-105:69]
o1 : FunctionClosure
and sure enough, there is a method, so no problem...
That's a good point! Maybe if we had this feature, we could catch bugs like "abc" + "def" earlier and not have any need for the "robust" stuff.
Incidentally (since it would in particular resolve the issue I pointed out above): As part 2 of my series of debugging-related PRs, I would like to get rid of most of the stuff in robust.m2 (and possibly some in methods.m2) so that errors are
- handled entirely at the d level
- printed entirely at the m2 level
and not a mixture of both like right now. I've already done part of the work but there's still a lot to do...
- handled entirely at the d level
That would be appreciated.
- printed entirely at the m2 level
Why is this useful / what do you mean by it?