Re: [Haskell] What is the best way to write adapters?
Well. The instance declaration is like this: instance FwdSig Native instance FwdSig Def instance FwdSig d => Sig d instance Sig XXX Neither Native nor Def are direct instances of Sig. And this XXX, it is an instance of Sig, not a FwdSig. As you can see, there's no real overlapping at all if we take the "FwdSig d =>" as a precondition for "Sig d". However, ghc seems disregard the "FwdSig d =>" piece and insists that the "Sig d" and "Sig XXX" forms an overlapping. This confuses me and the only reason I can figure out is that ghc does not attempt to resolve ambiguity based on "=>", which, I guess, could make the implementation simpler. Brandon Michael Moore <brandon@its.caltech.edu>@haskell.org on 03/11/2004 06:56:41 PM Sent by: haskell-bounces@haskell.org To: Ben_Yu@asc.aon.com cc: haskell@haskell.org, oleg@pobox.com Subject: Re: [Haskell] What is the best way to write adapters? On Thu, 11 Mar 2004 Ben_Yu@asc.aon.com wrote:
Thanks! Oleg.
This works and it looks nice!
And now, my code can be like:
class FwdSig d where (forall a. Sig a => a -> w) -> d -> w
All the types that supports such forwarding are instances of FwdSig.
My Def type is:
instance FwdSig Def where fwd f (ClassDef c) = f c fwd f (ProtDef p) = f p
instance Sig Def where getName = fwd getName getMethods = fwd getMethods ...
My Native type is:
instance FwdSig Native where fwd f (NativeSignature s) = f s fwd f (NativeProtocol p) = f p
instance Sig Native where getName = fwd getName getMethods = fwd getMethods ...
Many annoying forwarding functions are gone.
The only thing that I hope to be better is this "getXXX = fwd getXXX"
piece
of code. Is it possible to reuse the same piece of code for both Native and Def and any other possible types?
I'm not as handy with the type system as Oleg, but I can help out here. The problem with your new instance is that if the compiler is trying to see if Native is an instance of Sig, it can start with the declaration Sig Native, or the declarations FwdSig a => Sig a, both of which could potentially derive an instance of Sig Native. Additionally passing the -fallow-overlapping-instances flag will permit you to compile a program where instances overlap like this, and will select the most specific matching instance (looking only at the head). Your code is fine. Brandon
Inspired by your generic code, I wrote such thing:
instance FwdSig d => Sig d where getName = fwd getName getMethods = fwd getMethods ...
However, my ghc complains about the use of "Sig d".
I followed its recommendation and put -fallow-undecidable-instances flag with the surprise that the "FwdSig d=>Sig d" instance declaration conflicts with my other "instance Sig XXX" declarations.
Surely this is not a serious problem, I can live with repeating the "getXXX=fwd getXXX" several times. Just curious about how further this can go.
Ben.
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell This message is intended only for the addressee and may contain information that is confidential or privileged. Unauthorized use is strictly prohibited and may be unlawful. If you are not the intended recipient, or the person responsible for delivering to the intended recipient, you should not read, copy, disclose or otherwise use this message, except for the purpose of delivery to the addressee. If you have received this email in error, please delete and advise us immediately.
Ben Yu wrote:
The instance declaration is like this: instance FwdSig Native instance FwdSig Def instance FwdSig d => Sig d instance Sig XXX
Neither Native nor Def are direct instances of Sig. And this XXX, it is an instance of Sig, not a FwdSig.
As you can see, there's no real overlapping at all if we take the "FwdSig d =>" as a precondition for "Sig d".
You have pointed out one of the most confusing issues when dealing with typeclasses. One has the temptation to view
class Foo a instance Foo Int instance (Real a) => Foo a instance Foo a
something like
foo 5 = <body1> foo a | is_real a = <body2> foo a = <body3>
But there is a great difference. The typechecker chooses which instance to use based entirely on the shape of the types described in the instance, disregarding the constraints. In the Foo example, if we drop the constraints, we get
instance Foo Int instance Foo a instance Foo a
It's immediately obvious that there is no way the compiler could choose between the second and the third instance. And so we get a duplicate instances error. If we remove the third instance for example, we still get the problem: if the type in question is Int, which instance, the first or the second, should the compiler choose. The flag -fallow-overlapping-instances increases the compilers's IQ and so the compiler notices that the "Foo Int" is a specialization of "Foo a" (that is, there exists a substitution a->Int which makes "Foo a" identical to "Foo Int"). Therefore, if the instances can be so ordered, the compiler chooses the most specific applicable instance. After the compiler chose the instance, the compiler _becomes committed_ to it. Then the compiler notices constraints (e.g., Real a) and checks if they are satisfied. If they are not -- the typechecking fails. The compiler makes no attempt to choose another instance (more general one, if any) and see if that would work out. This is in the marked contrast with functional clauses (and with Prolog), where the failure of a guard on one clause causes another clause to be tried. If the compiler had taken instance constraints into account when choosing class instances, great many things would be possible. For example, we could easily dispatch on a class of a type of a value. That is, we could write class Foo a where what_is_it:: a -> String instance (Num a) => Foo a where what_is_it _ = "a number" instance (Monad a) => Foo a where what_is_it = "an action" In generally, we could implement `instanceOf' in a robust and succinct way. I guess history plays some part: in Haskell98, which does not permit overlapping instances, there can be no more second chances. If the instance constraints failed, that is it. I do want to point out that adding 'backtracking' to the instance choosing algorithm isn't that trivial. The first question is: should we permit instances that are truly duplicates, like "instance (Real a) => Foo a" and "instance Foo a" above. Many people would say yes. However, we cannot tell which of these instances is more general (syntactically, they are both "Foo a". There is generally no ordering on constraints). So, absent of a substitution-induced ordering, we are forced to resort to textual ordering. If two instances are otherwise the same, the one that appears first should be tried first. However, what does it mean to "textually appear first"? If one instance is declared in one module and the other is in another module (imported via a third module), which of them appears first? We are confronted with the eternal open/closed world issue. To see where it can lead us, one may wish to look at the XSLT Recommendation. http://www.w3.org/TR/1999/REC-xslt-19991116#conflict The XSLT processor likewise has the task of choosing the most appropriate template. The processor has to mind 'modes', import precedence, and even compute priorities of patterns. Curiously, the wildcard '*' gets the priority of -0.5. Integers are not enough for them. I wonder if this stuff is already in HaWiki....
participants (2)
-
Ben_Yu@asc.aon.com -
oleg@pobox.com