When I'm debugging code dependent on itree
, frequently it'll run forever rather than telling me a type error. Sometimes I can finally get it right, but I have to get lucky, the machine doesn't give me any hints because it runs forever rather than fails.
Am I not being patient enough? Should I let it search longer?
What are some techniques for speeding things up?
are you getting stuck during execution of tactics or during Qed
? If the former, then you have to carefully consider the tactics you use and their parameters so they don't diverge.
tactic execution and type checking (Qed) are different, e.g., if you use a plugin such as CoqHammer, then thehammer
tactic has to wait for lots of automated theorem provers (and there is nothing that in principle prevents a plugin tactic from ordering the proverbial pizza)
I think it's equivalent to during QED. it's actually in the middle of writing a Definition
of something that returns an itree E unit
the whole interaction trees package seems shock-full of type classes, so it may well be type class resolution that runs amok
that's still not really type checking though, but more like "type search"
see the various typeclass debug commands here and it might make more sense: https://coq.inria.fr/refman/addendum/type-classes.html#settings
Yes this sounds like the typical type class problems. Normally you shouldn't ever have to wait for more than one second for a definition to respond but the itree library is quite brittle on that front, so ill-typed definitions diverge instead of returning an error message. One approach worth trying is to specialize the functions you need up front, in isolated definitions that don't expose type classes in their type.
@Li-yao I don't see any Hint Mode
declarations for typeclasses in the itree source, that could be a contributing factor. Have you considered adding those? (stdpp has many good examples)
I'll definitely look into it :) The current setup is indeed as rudimentary as it gets.
OK if you don't mind I will open an issue
Another very common source of problems is unification not finding a solution and taking a long time to fail. This was a huge issue on math-comp projects for example
but hint modes will help with that too, right? It will prune a lot of fruitless searches
@Karl Palmskog that's unrelated I think, here I'm talking about "pure" unification
but isn't that unlikely [having pure unification problems] when using a mostly typeclass-based library?
anyway, TC and unification experts are free to weigh in or nitpick on what I wrote here: https://github.com/DeepSpec/InteractionTrees/issues/230
@Karl Palmskog I think you can also observe that wihtout TCs
if you have terms that unfold
the whole point of the locking pattern in math-comp was to avoid thi
this, for example, by forbidding unif / conversion from unfolding methods such as enum
etc...
my naive impression was that those problems mainly arose during complex unification triggered by canonical structures?
but in any case, using MathComp style locking is kind of the S [top] tier of library design
is there even any paper written about it?
Not sure about where, but I've seen it covered
You don't need canonicals to have this problem, it is more like imagine some conversion problem foo ~ @enum T
, that is bound to fail, but both expansions can happen, if you are unlucky the expansions can trigger exponential behavior, and then, boom
What we are missing in Coq is good tools to debug this
So in the case this happens, you can like see where the problem is more easily.
Now it is possible, but requires quite advanced skills
there are apparently a few areas in Coq where you still need to "do a PhD at X" to know about it
I wish there were just a few XD
Like most of them :D
Well, I didn't do a PhD at X and I know
ok, "do a postdoc at X"
So do we think it'd be ok to enforce Typeclasses eauto := 2.
and assume if it needs more than 2 layers it will diverge?
I don't think depth 2 will get you very far...
4 takes just under 20 seconds to fail, 3 takes about 2 seconds
OK, so then at least we know with reasonable certainty that typeclass resolution is the culprit, let me adjust the topic name
oh yeha, seeing a ton of useful information now. so the thread has been helpful.
I'm curious if @Li-yao has any go-to Typeclasses
settings declarations for working with itrees
I think the previous advice from Li-yao is probably the way to go for now:
specialize the functions you need up front, in isolated definitions that don't expose type classes in their type.
Yes my best advice is to not use type classes :)
My goal is to modify tutorial/Asm.v
to emulate a particular architecture. I don't really know how to use itrees without blindly copying the tutorials haha
Typeclasses eauto :=
is meh because it can hide errors (resolutions that should diverge end up succeeding with possibly brittle solutions).
My understanding is that the default is some Typeclasses eauto := n
for some high n
, and I was just lowering it but not qualitatively changing the search.
I'm not sure I understanding this advice. Does it mean to get rid of Context
declarations, and bind typeclasses in function signature directly? or does it mean, if a typeclass exposes a method A -> B -> C
put a general A -> B -> C
in the signature of each definition _and_ delete the Context
declarations?
Karl Palmskog said:
there are apparently a few areas in Coq where you still need to "do a PhD at X" to know about it
Now seriously this is bad so any ideas on how to improve it are much welcome
@Quinn IIRC the main functions using typeclasses are trigger
and interp
. They are usually only used with the same couple of type parameters, so for example you can make something like Definition trigger_Reg : Reg ~> itree E := trigger
, and so on for every relevant effect, and then only use those monomorphic functions.
I think we all want good practices/heuristics/techniques about type class resolution and unification to become more known and reach more users, get better docs, etc., but to be produced in the research community it has be novel and/or have a nice evaluation. So if anyone can think of a nice mitigation for TC resolution divergence that is somehow measurable in lots of Coq code, I'd be happy to wrap it up nicely for a conference like ASE...
but as with many issues in Coq, people typically don't even commit code to repos with TC resolution problems, so there is not a lot to go on empirically...
Ok thanks, sounds good!
I agree with @Karl Palmskog 's initial suggestion: nobody ever will tell you, but to a first approximation every typeclass without a mode is a bug.
_in addition_, typeclass search is not going to be efficient unless you (a) pay attention so that your logic program is well-moded and as “syntax-directed” as possible (b) ideally, mark your predicates as “typeclass opaque” instead of letting unification try using every instance against every goal
argh, one might say: have a "typeclass czar" in every Coq project that uses typeclasses, and make sure that person has experience equivalent to a PhD in logic programming & type theory
@Paolo Giarrusso do you agree with the following?
using type classes is, in Greg Morrisett’s term, like steering the Queen Mary: you have to get this hulking mass pointed in the right direction so that the inference mechanism resolves things the way you want it to.
(from here: https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/)
maybe in Coq it's rather more like doing maintenance in a nuclear submarine?
no, but I’m somewhat biased
it took me a while to climb the stdpp/iris learning curve, but I never did that with the math-comp one :-).
It _is_ nice that math-comp is getting a proper high-level language in hierarchy builder, but I _am_ concerned if you need so much code generation, since it cannot possibly be transparent to the user.
just using MathComp has always been a nice experience for me, it's when one wants to generate hierarchies it gets hard. I find just using TCs hard, not least due to stuff similar to this topic
a typeclass czar might be necessary today, but that’s a symptom of a problem.
since that’s a documentation/awareness problem, let me just say _why_ you need modes.
say Coq must find an instance of YourTypeclass ?evar1 ?evar2
. The problem is clearly underspecified, so you might hope for an error.
Indeed, if you mark one or both arguments to YourTypeclass
as inputs (or at least, their _heads_ as inputs), an error is what you’ll get until you constrain the problem.
by default, Coq will just let TC search instantiate those variables… that’s reasonable for outputs, but if you treat everything as output, Coq might just pick the last declared instance and call it a day :-(.
Indeed, in Lean TC arguments default to being inputs.
so should we start adding manual hint modes to that effect [inputs everywhere] in the absence of dev resources?
I think one issue is that between a project like Itrees (no hint modes) and Stdpp (expert-level hint modes), the gulf is vast, and we don't seem to have consensus or awareness what to do
adding mores is one good thing, but you also need to design your instances accordingly, and that part can be trickier and I don’t have a summary ready…
however, [hopefully] this isn’t a big problem in stdpp (or presumably itrees)
those are using typeclasses for overloading, not full programming
Paolo Giarrusso said:
It _is_ nice that math-comp is getting a proper high-level language in hierarchy builder, but I _am_ concerned if you need so much code generation, since it cannot possibly be transparent to the user.
I think HB does a good job at keeping things transparent. I mean there is always plan B.
so in your opinion, if all you want is overloading, you don't need to spend inordinate amounts of time thinking about instance declarations?
see the “hopefully” :-)
but I think basically yes: #[global] Hint Mode YourTypeclass ! ! : typeclass_instances.
(with the right number of !
) is likely to be an overall improvement.
and you can often see which arguments should plausibly be an output…
hmm, but we are talking unbundled classes here, right? For jumbled stuff that mixes stuff in Prop
and Set
/Type
, I often can't make any sense of inputs or outputs
for instance:
(** The function look up [m !! k] should yield the element at key [k] in [m]. *)
Class Lookup (K A M : Type) := lookup: K → M → option A.
Global Hint Mode Lookup - - ! : typeclass_instances.
(this one seems more liberal than I’d have expected: it can deduce the key and element type from the collection, but not viceversa, probably because of some non polymorphic collections in stdpp)
@Karl Palmskog I’m not sure I follow, do you have an example? stdpp does use unbundled classes, indeed
I mean that you have some monstrosity of a class that takes 4+ parameters that are not just of the varietyA : Type
, and then the thing doesn't live in Prop
and isn't an operational typeclass, but has 5+ fields living in various sorts.
basically, I can definitely see that the inputs/outputs are "obvious" for properly defined operational typeclasses and so on, but I'm not sure that's what people are writing in the wild
I can't recall those, but marking arguments as input might still be a good idea...
I do recall cases where modes are not obvious; my old project adds its own modes to Autosubst :-)
ah, here you have the future, packages that only add/replace modes in existing projects
It did quite a bit more, but certain modes made more sense for my language than for other ones
for the operation of “substite variables for sort A in sort B”, what’s the most appropriate mode?
OK, the "jumbled typeclass" is what one might call a "module type substitute" (basically just a way to carry around a bunch of types and facts without doing fancy proof search). And for that particular pattern, probably all inputs are the way to go.
Would you guys have any thoughts about Set Typeclasses Strict Resolution
https://coq.inria.fr/distrib/current/refman/addendum/type-classes.html#coq:flag.Typeclasses-Strict-Resolution Isn't that a "input mode by default"?
@Li-yao I know that @Janno tried it, and it didn't work correctly.
I don't think we ever dug too deeply, so it might be worth trying. It also might work well enough for most uses.
And remember to set it before defining the typeclass in question, this flag affects definitions not uses.
Any rule of thumb on using mode !
vs mode +
?
Good question... Mode ! seems most common, and often is enough to disambiguate between instances.
I guess a key criterion is the amount of backtracking that you're going to get; a search on an evar isn't just possibly incorrect, but it can be exponentially slow
but isn't that unlikely [having pure unification problems] when using a mostly typeclass-based library?
I forget the exact conditions, but if you have Definition foo A := list A.
then you define Instance fmap_foo : FMap foo.
, that instance will be potentially applicable to any search for FMap bar
at each level of the search.
TC search uses typeclasses eauto
and unlike eauto
, hints are not matched syntactically against goals.
however, if TC search sees head symbols that are (a) distinct (b) considered opaque for typeclass search (e.g. because of Typeclasses Opaque
), then it can filter out the instance eagerly and skip it altogether
I might have an itree-dependent code base I'm passing off to a teammember soon. I can live with "tinker with settings if you think typeclass resolution is diverging" to an extent (not ideal), but about Hint Mode
what do I say that isn't "it's an oral tradition so you're required to be in the zulip"
haha, after a Section A.
containing eight Context
declarations of typeclass instances, then a Definition foo : ...
. after End A.
when I run a Check foo
, it takes forever, but when I run Check @foo
it returns immediately (with the contents of all the Context
declarations as inputs).
I'm seeing why I have to internalize some of the advice in the thread.
some docs for Hint Mode
do exist
even if they're not ideal they might be enough for the explanation...
in your example, what I would do is look at the output of Set Typeclasses Debug
with the loop, and try to add hint modes that fix that.
(docs at https://coq.inria.fr/refman/proofs/automatic-tactics/auto.html#coq:cmd.Hint-Mode)
you can read the docs, or you can stare very long at Global Hint Mode ...
in: https://gitlab.mpi-sws.org/iris/stdpp/-/blob/master/theories/base.v
which may actually work better in some cases
The other trick we don't teach is how to informally "termination-check" logic programs.
For instance, without modes and with an instance foo : TC (list A) -> TC A
, you'll get an infinite loop when searching for TC ?A
: Coq will just keep applying foo
and you'll get goals like TC ?A -> TC (list ?A) -> TC (list (list ?A)) -> ...
, hence the loop.
ok cool. thanks everyone!
@Li-yao would you say there's an opportunity for someone to refactor ITree
into Module
and Section
_instead_ of typeclasses?
What do you have in mind? I'm not sure modules give you the same amount of flexibility here. Instead, setting some Mode
s should alleviate most of the nontermination issues.
there are arguably two state-of-the-art approaches to hierarchies of structures in Coq: type classes, and packed classes w/ canonical structures. While it's in principle possible to use the module system and module types for hierarchies, they are not "first class", which means more work and less reasoning support...
Ok. I read the bob harper post linked up thread and sort of got the impression in ML-like module systems you can get many of the benefits of typeclasses. Where fulfilling the spec of a signature replaces the providing of an instance.
the key thing with type classes in Coq is that they are not a separate language feature like modules/module types are
this in my mind means that Bob's argument doesn't really cover Coq type classes. The basis for type classes is (essentially) dependent records and implicit arguments, which I don't think Bob has anything against in themselves
there isn't really any problem "instantiating" a type class several times for the same parameters in Coq, unlike in Haskell, where one might have to use compile-time configuration to get the "right" instance
this is the relevant Coq type class paper: https://hal.inria.fr/inria-00628864
@Karl Palmskog Harper is strongly opposed to first-class modules as well. But even if Harper is an excellent mathematicians, I’m not sure Harper wants to summarize the consensus of the community
but I have no idea how Harper’s view would apply to modules for dependent types. His argument against first-class modules is really for the importance of the phase distinction, but I’m not sure that’d apply to dependent types…
Quinn said:
Ok. I read the bob harper post linked up thread and sort of got the impression in ML-like module systems you can get many of the benefits of typeclasses. Where fulfilling the spec of a signature replaces the providing of an instance.
Modules are pretty much alike base instances (facts in logic programming) while functors are pretty much parametric instances (rules with premises in LP). But then type classes compose these instances automatically (using some sort of logic programming engine). I'd say that "automatic resolution of instances" is the key ingredient of "type classes".
Last updated: Sep 23 2023 at 07:01 UTC