> Let's consider the general case (which I didn't describe in my earlier > email). > > In case we have an n-ary type function T (or (n+1)-ary type class > constraint T) the conditions says for each > > type T t1 ... tn = t > > (or rule T t1 ... tn x ==> t) > > then rank(ti) > rank(t) for each i=1,..,n I didn't know what condition you meant for the general form. But the condition above is not sufficient either, as a trivial modification of the example shows. The only modification is instance E ((->) (m ())) (() -> ()) (m ()) where and test = foo (undefined::((() -> ()) -> ()) -> ()) (\f -> (f ()) :: ()) Now we have t1 = ((->) (m ())) : two constructors, one variable t2 = () -> () : three constructors t = m () : one constructor, one variable and yet GHC 6.4.1 loops in the typechecking phase as before. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
[hidden email] writes:
> > > Let's consider the general case (which I didn't describe in my earlier > > email). > > > > In case we have an n-ary type function T (or (n+1)-ary type class > > constraint T) the conditions says for each > > > > type T t1 ... tn = t > > > > (or rule T t1 ... tn x ==> t) > > > > then rank(ti) > rank(t) for each i=1,..,n > > I didn't know what condition you meant for the general form. But the > condition above is not sufficient either, as a trivial modification of the > example shows. The only modification is > > instance E ((->) (m ())) (() -> ()) (m ()) where > > and > test = foo (undefined::((() -> ()) -> ()) -> ()) (\f -> (f ()) :: ()) > > Now we have t1 = ((->) (m ())) : two constructors, one variable > t2 = () -> () : three constructors > t = m () : one constructor, one variable > > and yet GHC 6.4.1 loops in the typechecking phase as before. rank (() ->()) > rank (m ()) does NOT hold. Sorry, I left out the precise definition of the rank function in my previous email. Here's the formal definition. rank(x) is some positive number for variable x rank(F t1 ... tn) = 1 + rank t1 + ... + rank tn where F is an n-ary type constructor. rank (f t) = rank f + rank t f is a functor variable Hence, rank (()->()) = 3 rank (m ()) = rank m + 1 We cannot verify that 3 > rank m + 1. So, I still claim my conjecture is correct. Martin P. S. Oleg, can you next time please provide more details why type inference does not terminate. This will help others to follow our discussion. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
In reply to this post by oleg-7
On Tue, Feb 21, 2006 at 07:13:17PM -0800, [hidden email] wrote:
> I'm afraid that may still be insufficient, as the following > counter-example shows. It causes GHC 6.4.1 to loop in the typechecking > phase. I haven't checked the latest GHC. The HEAD is more cautious: F.hs:12:0: Variable occurs more often in a constraint than in the instance head in the constraint: E m a b (Use -fallow-undecidable-instances to permit this) In the instance declaration for `Foo m (a -> ())' This is required for all instances. GHC 6.4 relaxed it in the presence of FDs on the classes in the context, but as you've shown that is dangerous. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
In reply to this post by Martin Sulzmann
On Wed, 2006-02-22 at 12:33 +0800, Martin Sulzmann wrote:
> In case we have an n-ary type function T > (or (n+1)-ary type class constraint T) > the conditions says > for each > > type T t1 ... tn = t > > (or rule T t1 ... tn x ==> t) > > then rank(ti) > rank(t) for each i=1,..,n I'm probably misunderstanding something but doesn't this imply that we cannot declare any instances for class C a b | a -> b, b -> a which do not break the bound variable condition? This would remove one of the main advantages fundeps have over associated types. In general, wouldn't it be better to look at *all* visible instance declarations when they are used instead of looking at each one individually when it is defined? If the goal is to allow only primitive recursion, then that would lead to much more permissive rules. As to the non-termination of GHC's type checker, below is an example which encodes a stripped-down version of the lambda calculus (with only one variable) and then evaluates (\x. x x) (\x. x x). Loops nicely with GHC 6.4.1, but the second Subst instance is invalid under your rule if I understand correctly. Roman ---- {-# OPTIONS -fglasgow-exts #-} data X data App t u data Lam t class Subst s t u | s t -> u instance Subst X u u instance (Subst s u s', Subst t u t') => Subst (App s t) u (App s' t') instance Subst (Lam t) u (Lam t) class Apply s t u | s t -> u instance (Subst s t u, Eval u u') => Apply (Lam s) t u' class Eval t u | t -> u instance Eval X X instance Eval (Lam t) (Lam t) instance (Eval s s', Apply s' t u) => Eval (App s t) u x = undefined :: Eval (App (Lam (App X X)) (Lam (App X X))) u => u _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
The following not only answers Roman's question but also includes a short summary (at the end) of the discussion we had so far. Roman Leshchinskiy writes: > On Wed, 2006-02-22 at 12:33 +0800, Martin Sulzmann wrote: > > In case we have an n-ary type function T > > (or (n+1)-ary type class constraint T) > > the conditions says > > for each > > > > type T t1 ... tn = t > > > > (or rule T t1 ... tn x ==> t) > > > > then rank(ti) > rank(t) for each i=1,..,n > > I'm probably misunderstanding something but doesn't this imply that we > cannot declare any instances for > > class C a b | a -> b, b -> a > > which do not break the bound variable condition? This would remove one > of the main advantages fundeps have over associated types. > Sure you can. For example, class C a b | a->b, b->a instance C [a] [a] The above class/instance declarations satisfy the Consistency, Coverage, Basic Bound Variable Conditions. See "Understanding FDs via CHRs" paper (see Sect 4, 4.1). Under these conditions, we know that type inference is sound, complete and decidable. In your example below, you are not only breaking the Bound Variable Condition, but you are also breaking the Coverage Condition. > {-# OPTIONS -fglasgow-exts #-} > data X > data App t u > data Lam t > > class Subst s t u | s t -> u > instance Subst X u u > instance (Subst s u s', Subst t u t') => Subst (App s t) u (App s' t') breaks Coverage > instance Subst (Lam t) u (Lam t) > > class Apply s t u | s t -> u > instance (Subst s t u, Eval u u') => Apply (Lam s) t u' > > class Eval t u | t -> u > instance Eval X X > instance Eval (Lam t) (Lam t) > instance (Eval s s', Apply s' t u) => Eval (App s t) u breaks Coverge and Bound Variable > > x = undefined :: Eval (App (Lam (App X X)) (Lam (App X X))) u => u > It's no surprise that the ghc inferencer does not terminate. We know that breaking the Coverage Condition alone (not breaking any of the other conditions) will break termination. Well, not always but for certain examples.See Example 15, Sect 5.2 in the paper. We also know that breaking the Bound Variable Condition will break termination. See Example 16, Sect 5.2. All this may be surprising, cause the instances are terminating. It's the devious interaction between instance reduction and improvement. In case we break the Coverage Condition we need to find some "weaker" conditons which guarantee confluence (i.e. complete inference) *if* we know that inference is decidable. See Sect 6. So, it's really termination that's the trouble maker and there's really not hope to maintain termination in general. Though, if we can verify termination, e.g. for a particular inference goal, we obtain completeness. Hence, inference is sound, complete but semi-decidable. How/why did we need up talking about decidable FD inference? Well, somebody (can't remember who) asked how to translate the following FD program to ATs. class D b class D b => C a b | a->b In the AT system, FDs are expressed via type relations. class D b class D (T a) => C a where type T a The point here is that to encode the FD program the AT system (as described in ICFP'05) needs to be extended. Specifically, associated type synonyms may now constrain the types of type classes in the instance/superclass context. I've pointed out that this easily leads to undecidable type inference. I've appended my *old email* below. My main point were: - The type functions are obviously terminating, e.g. type T [a] = [[a]] clearly terminates. - It's the devious interaction between instances/superclasss and type function which causes the type class program not to terminate. The problem with decidable AT inference is very similar to the FD case where we break the Bound Variable Condition. Here's a "rough" FD encoding of the above AT program. class T a b | a->b class D b class (D b, T a b) => C a ^^^^^^^^^^^ Notice that the superclass context contains the unbound variable b. This observation allowed me to lift the "critical" FD examples to the AT world. As a remedy to the undecidability issue, I proposed to impose stronger conditions on AT type relations (we know the AT type relations are terminating but that's still not enough). I'm not reproducing here the details, see my earlier emails. Also note that similar conditions can be applied to FD programs (to recover decidability). Here's now the summary: - Decidable type inference for ATs and FDs is an equally hard problem. - There's a huge design space which additional conditions will recover decidability, e.g. impose ranking conditions on type relations, dynamic termination checks etc. It's very likely that similar conditions apply to FDs and ATs. - To encode certain FD programs the AT system needs extensions which may threaten decidable type inference. Martin *old email* > > Stefan Wehr writes: > > > [...] > > > Manuel (Chakravarty) and I agree that it should be possible to > > > constrain associated type synonyms in the context of class > > > definitions. Your example shows that this feature is actually > > > needed. I will integrate it into phrac within the next few days. > > > > > > > By possible you mean this extension won't break any > > of the existing ATS inference results? > > Yes, although we didn't go through all the proofs. > > > You have to be very careful otherwise you'll loose decidability. > > Do you have something concrete in mind or is this a more general > advice? > I'm afraid, I think there's a real issue. Here's the AT version of Example 15 from "Understanding FDs via CHRs" class D a class F a where type T a instance F [a] where type T [a] = [[a]] instance (D (T a), F a) => D [a] ^^^^^^^ type function appears in type class Type inference (i.e. constraint solving) for D [a] will not terminate. Roughly, D [[a]] -->_instance D (T [a]), F [a]) -->_type function D [[a]], F [a] and so on Will this also happen if type functions appear in superclasses? Let's see. Consider class C a class F a where type T a instance F [a] where type T [a] = [[[a]]] class C (T a) => D a ^^^^^ type function appears in superclass context instance D [a] => C [[a]] -- satisfies Ross Paterson's Termination Conditions Consider D [a] -->_superclass C (T [a]), D [a] -->_type function C [[[a]]], D [a] -->_instance D [[a]], D [a] and so on _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
On Mon, 27 Feb 2006, Martin Sulzmann wrote:
> > > In case we have an n-ary type function T > > > (or (n+1)-ary type class constraint T) > > > the conditions says > > > for each > > > > > > type T t1 ... tn = t > > > > > > (or rule T t1 ... tn x ==> t) > > > > > > then rank(ti) > rank(t) for each i=1,..,n > > > > I'm probably misunderstanding something but doesn't this imply that we > > cannot declare any instances for > > > > class C a b | a -> b, b -> a > > > > which do not break the bound variable condition? This would remove one > > of the main advantages fundeps have over associated types. > > > > Sure you can. For example, > > class C a b | a->b, b->a > instance C [a] [a] Ah, sorry, my question was very poorly worded. What I meant to say was that there are no instances declarations for C which satisfy your rule above and, hence, all instances of C (or of any other class with bidirectional dependencies) must satisfy the other, more restrictive conditions. Is that correct? > In your example below, you are not only breaking the Bound Variable > Condition, but you are also breaking the Coverage Condition. Yes, but I'm breaking the rule you suggested only once :-) It was only intended as a cute example. My worry, however, is that there are many useful type-level programs similar to my example which are guaranteed to terminate but which nevertheless do not satisfy the rules in your paper or the one you suggested here. I think ruling those out is unavoidable if you want to specify termination rules which every instance must satisfy individually. But why not specify rules for sets of instances instead? This is, for instance, what some theorem provers do for recursive functions and it allows them to handle a wide range of those without giving up decidability. Roman _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
I was talking about *static* termination. Hence, the conditions in the paper and the new one I proposed are of course incomplete. I think that's your worry, isn't it? There are reasonable type-level programs which are rejected but will terminate for certain goals. I think what you'd like is that each instance specifies its own termination condition which can then be checked dynamically. Possible but I haven't thought much about it. The simplest and most efficient strategy seems to stop after n number of steps. Martin Roman Leshchinskiy writes: > On Mon, 27 Feb 2006, Martin Sulzmann wrote: > > > > In case we have an n-ary type function T > > > > (or (n+1)-ary type class constraint T) > > > > the conditions says > > > > for each > > > > > > > > type T t1 ... tn = t > > > > > > > > (or rule T t1 ... tn x ==> t) > > > > > > > > then rank(ti) > rank(t) for each i=1,..,n > > > > > > I'm probably misunderstanding something but doesn't this imply that we > > > cannot declare any instances for > > > > > > class C a b | a -> b, b -> a > > > > > > which do not break the bound variable condition? This would remove one > > > of the main advantages fundeps have over associated types. > > > > > > > Sure you can. For example, > > > > class C a b | a->b, b->a > > instance C [a] [a] > > Ah, sorry, my question was very poorly worded. What I meant to say was > that there are no instances declarations for C which satisfy your rule > above and, hence, all instances of C (or of any other class with > bidirectional dependencies) must satisfy the other, more restrictive > conditions. Is that correct? > > > In your example below, you are not only breaking the Bound Variable > > Condition, but you are also breaking the Coverage Condition. > > Yes, but I'm breaking the rule you suggested only once :-) It was only > intended as a cute example. My worry, however, is that there are many > useful type-level programs similar to my example which are guaranteed to > terminate but which nevertheless do not satisfy the rules in your paper or > the one you suggested here. I think ruling those out is unavoidable if you > want to specify termination rules which every instance must satisfy > individually. But why not specify rules for sets of instances instead? > This is, for instance, what some theorem provers do for recursive > functions and it allows them to handle a wide range of those without > giving up decidability. > > Roman _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
On Mon, 2006-02-27 at 16:43 +0800, Martin Sulzmann wrote:
> I was talking about *static* termination. Hence, the conditions > in the paper and the new one I proposed are of course incomplete. Just to clarify: by static you mean verifiable at instance definition time (i.e. under the open world assumption) whereas dynamic is when the instance is used (i.e. in a closed world)? Note that both are "static" from a programmer's point of view, but this terminology definitely makes sense here. > I think that's your worry, isn't it? There are reasonable > type-level programs which are rejected but will terminate for certain > goals. My worry are type-level programs which are rejected but will provably terminate for *all* goals. > I think what you'd like is that each instance specifies its own > termination condition which can then be checked dynamically. That depends on what you mean by specifying a termination condition. Suppose we want to solve C t1 ... tn = t. A possible rule might be: if while solving this we ever come up with the goal C u1 ... un = u, then the number of constructors in u1 ... un must be strictly less than the number of constructors in t1 ... tn. Something similar to this should guarantee termination but would still allow structural recursion on types. Note that this doesn't even have to be fully dynamic - it can be checked once per module by using instance declarations as generators, I think. > Possible but I haven't thought much about it. The simplest and most > efficient strategy seems to stop after n number of steps. Yes, but I don't really like that. Any n will be completely arbitrary and rule out perfectly good type-level programs for no good reason. For what it's worth, this is essentially what C++ does and people don't like it and seem to largely ignore the limit specified in the standard. Roman _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
Free forum by Nabble | Edit this page |