The following observations are not new, insightful, or gracious, but I was lusting after the innocent +,-,* operators for my own evil ends and was mildly curious why... Num is such a fat and greedy class. If you want to marry Cinderella, you have to take her ugly stepsisters too. 1) Groups may only want to define addition. Why can't they use + (instead of <+>, >++++?&**+>, or other such perversion)? 2) Affine spaces have a (-) but no (+). Worse, the signature might be (-) :: Point -> Point -> Vector, which doesn't unify with (a -> a -> a). Wouldn't the following be more useful/general? class Subtraction a b | a -> b where (-) :: a -> a -> b Or would this require needless type annotation for the common subset of (a -> a -> a) instances? 3) Quaternions have no signum, unit quaternions have (*), (/) but no (+) or (-), abs would have a different signatures (Quaternion -> Double) which doesn't unify with (a -> a), and fields cannot be scaled with (*) as in (*) :: (Field f) => Double -> f -> f Would it not make sense to put each of these operators (division too) into their own individual superclasses that Num inherits? My (obviously naive) philosophy about type classes is that operations should be bundled only when they are mutually recursive (i.e. there is more than one useful minimal definition). If there is just one minimal set of operations, they can be in their own parent class too. Then again, I should get over my lust and stick with my own operators <+++++++++>, <---------------------->, and <*******>. Not too pretty, but they have a wonderful personality all their own! Dan
Oh, come on. It's not that bad. Just start every module with import Prelude() import MyPrelude that's what I do. There's nothing sacred about the Prelude (except a few things used for special syntax). It just happens to be in scope. -- Lennart On Dec 8, 2006, at 15:04 , Dan Weston wrote:
The following observations are not new, insightful, or gracious, but I was lusting after the innocent +,-,* operators for my own evil ends and was mildly curious why...
Num is such a fat and greedy class. If you want to marry Cinderella, you have to take her ugly stepsisters too.
1) Groups may only want to define addition. Why can't they use + (instead of <+>, >++++?&**+>, or other such perversion)?
2) Affine spaces have a (-) but no (+). Worse, the signature might be (-) :: Point -> Point -> Vector, which doesn't unify with (a -> a -
a).
Wouldn't the following be more useful/general?
class Subtraction a b | a -> b where (-) :: a -> a -> b
Or would this require needless type annotation for the common subset of (a -> a -> a) instances?
3) Quaternions have no signum, unit quaternions have (*), (/) but no (+) or (-), abs would have a different signatures (Quaternion -
Double) which doesn't unify with (a -> a), and fields cannot be scaled with (*) as in (*) :: (Field f) => Double -> f -> f
Would it not make sense to put each of these operators (division too) into their own individual superclasses that Num inherits? My (obviously naive) philosophy about type classes is that operations should be bundled only when they are mutually recursive (i.e. there is more than one useful minimal definition). If there is just one minimal set of operations, they can be in their own parent class too.
Then again, I should get over my lust and stick with my own operators <+++++++++>, <---------------------->, and <*******>. Not too pretty, but they have a wonderful personality all their own!
Dan
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On 08/12/06, Dan Weston <westondan@imageworks.com> wrote:
The following observations are not new, insightful, or gracious, but I Wouldn't the following be more useful/general?
class Subtraction a b | a -> b where (-) :: a -> a -> b
If you split them up like this, things like quadRoots take an unwieldly type signature: quadRoots a b c | a /= 0 = (x1 / (2*a), x2 / (2*a)) where x1 = -b - sqrt det x2 = -b + sqrt det det = b ^ 2 - 4*a*c As these use many of the operations. However, this might become manageable with class aliases [1]. [1]: http://repetae.net/john/recent/out/classalias.html -- -David House, dmhouse@gmail.com
class Subtraction a b | a -> b where (-) :: a -> a -> b
If you split them up like this, things like quadRoots take an unwieldly type signature: [...]
ultimately this leads to a style of programming where you have a class Has_f for each function symbol f. then the signature of a function contains all the (wrapper class) names of all the functions it intends to call. This shows that type classes are "just" a means for expressing implicit parameters to functions (the parameters are the class methods, and the (invisible) dictionary transports them). Their usability still is limited because all (data and) class and instance declarations are global. Is there a fundamental (design or implementation) reason for this? I really sometimes would want to write let { instance Show Foo where ... } in show ... or let data Bar = ... in ... Compare Java where you can have nested classes and interfaces. Where of course part of their motivation is that Java lacks proper (and anonymous) functions. Best regards, -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------
On Dec 11, 2006, at 03:50 , Johannes Waldmann wrote:
let data Bar = ... in ...
If you allow this you need to be very careful about type equality. When is Bar equal to Bar? If it's inside a recursive function, does each invocation get its own Bar? (In SML the answer is yes.) If you decide the answer is no, then is the beta rule still valid? E.g., let x = (let Bar = ... in ...) in ... x ... x ... expand x (which has always been semantically valid in Haskell) ... (let Bar = ... in ...) ... (let Bar = ... in ...) ... Are those two Bar types equal? In Cayenne I allowed all these things, but the price is that Cayenne has structural equality on types rather than nominal. Switching to nominal to structural would be a major change in Haskell. So this change may look innocuous, but it has big ramifications. -- Lennart
Lennart Augustsson wrote:
let data Bar = ... in ...
If you allow this you need to be very careful about type equality. When is Bar equal to Bar? If it's inside a recursive function, does each invocation get its own Bar? (In SML the answer is yes.)
Can you give an example of how this would be observable in SML? AFAICS, there is no way to tell the difference, because generative type names are not allowed to escape their scope. (You can observe dynamic generativity of exception constructors, though.)
If you decide the answer is no, then is the beta rule still valid?
I think with the scoping restrictions in place the beta rule would not be affected. - Andreas -- Andreas Rossberg, rossberg@ps.uni-sb.de
let data Bar = ... in ...
If you allow this you need to be very careful about type equality. When is Bar equal to Bar? If it's inside a recursive function, does each invocation get its own Bar? (In SML the answer is yes.)
Not really. In SML the answer used to be a clear "no", that is: in the 1990 definition. However, that proved to be a matter of type-unsoundness, and Claudio Russo came up with an example that used this feature to break the type system. Having said this, this was based on the way the type system was defined in the language definition, the problem did not show up in implementations (which therefore failed to implement the language standard :-). The problem was fixed in the 1997 language standard. But there the answer isn't "yes" either, it is more like: "whatever it is, you cannot tell", though technically it is still "no". In the static semantics, a local datatype in SML is fresh. However, this freshness is a static freshness, at compile time, and every time the code is run the same "type name" (a uniqueness tag for types) will be used: there is no run time type-checking, the type name is generated once, at compile time. However, that type name is not allowed to leak to the outside, i.e. not only is the identifier Bar not visible outside, the type of the value returned by the let-expression must not contain the type name associated with Bar. Thus, if a let-expression with a local datatype is evaluated twice, it does not really matter whether it uses the same or a different type name because encapsulation ensures that these type names do not interfere with each other in any way. In a nutshell: local types are not worth the trouble they cause.
S.M.Kahrs wrote:
let data Bar = ... in ...
If you allow this you need to be very careful about type equality. When is Bar equal to Bar? If it's inside a recursive function, does each invocation get its own Bar? (In SML the answer is yes.)
Not really. In SML the answer used to be a clear "no", that is: in the 1990 definition. However, that proved to be a matter of type-unsoundness, and Claudio Russo came up with an example that used this feature to break the type system. Having said this, this was based on the way the type system was defined in the language definition, the problem did not show up in implementations (which therefore failed to implement the language standard :-).
The problem was fixed in the 1997 language standard. But there the answer isn't "yes" either, it is more like: "whatever it is, you cannot tell", though technically it is still "no".
Mh, technically, isn't it likewise "neither"? As you say, types are only generated statically, and are erased in the dynamic semantics, so how is it a "no" more than a "yes"? As an aside (sorry if this is getting far too OT), in Alice ML we extend SML with dynamic types, and local types in fact *are* dynamically generative. This seemed to be the most natural semantics (and the only correct one in the presence of dynamic linking).
In the static semantics, a local datatype in SML is fresh. However, this freshness is a static freshness, at compile time, and every time the code is run the same "type name" (a uniqueness tag for types) will be used: there is no run time type-checking, the type name is generated once, at compile time.
Well, this is sort of true for types defined in functor bodies as well, still they are generative. The only semantical difference I see is that its local types are allowed to escape scope, and are (statically) renamed upon each application of the functor. No difference with respect to the dynamic semantics, however.
However, that type name is not allowed to leak to the outside, i.e. not only is the identifier Bar not visible outside, the type of the value returned by the let-expression must not contain the type name associated with Bar. Thus, if a let-expression with a local datatype is evaluated twice, it does not really matter whether it uses the same or a different type name because encapsulation ensures that these type names do not interfere with each other in any way.
In a nutshell: local types are not worth the trouble they cause.
I'm not quite sure how this follows from your explanation. :-) Don't you just need the same standard scoping restriction as for existential types? (Which they basically are, as we know.) Why do you consider it troublesome? - Andreas -- Andreas Rossberg, rossberg@ps.uni-sb.de
participants (6)
-
Andreas Rossberg -
Dan Weston -
David House -
Johannes Waldmann -
Lennart Augustsson -
S.M.Kahrs