G'day all. I've been toying with an idea for a small language extension, and I was curious what you all think of this. As a motivating example, consider the following standard typeclass: class Bounded a where min :: a max :: a This is an example of what I refer to as a "traits typeclass" (by analogy to traits classes, well-known to C++ programmers). The idea is that you want to specify certain properties/operations which depend on a _type_, rather than any particular value of that type. Another example is floating point format information, like the information in C's <float.h>. One might implement this as: class (Bounded a) => FloatTraits a where epsilon :: a -- OK mantissaDigits :: Int -- Not OK! For obvious reasons, the declaration of mantissaDigits is not valid Haskell. Instead, you might do something like this: class FloatTraits a where mantissaDigits :: a -> Int instance FloatTraits Float where mantissaDigits _ = 24 instance FloatTraits Double where mantissaDigits _ = 53 and invoke it as, say: mantissaDigits (undefined :: Float) A decent strictness analyser can tell if an argument to some function is always unused (in GHC core terminology, this means its demand is "absent") and thus avoid passing the argument at all. However, it is currently impossible to tell if this is true all instances of a typeclass. What I would like is some annotation that I can use to tell my compiler that this argument of this method is always unused, no matter what instance it resolves to. My proposal is to grab a new punctuation symbol (I will use '~' as an example) to sit alongside the GHC extension use of '!'. Where '!' means that an argument may be evaluated before calling a function, '~' means than an argument may be ignored by the callee. So in the above example, I can write: class FloatTraits a where mantissaDigits :: ~a -> Int Now this marker is not as simple to implement as '!'. The strict marker is advisory; if you stripped all '!'s from your program, it would still have the same static semantics. Not so with '~', which must be statically checked. However, the static analysis is pretty trivial, except for the problem of inference. Basically, if a variable has absent demand, it a) can't be pattern matched on (unless the pattern is a variable, of course), and b) can't be passed as an argument to a function (including a data constructor) which doesn't also treat that argument as having absent demand. I therefore propose that the marker not be inferred for top-level definitions, or at least for exported definitions. If you declare it, it's checked, but otherwise it's not. In addition, any calls that you pass a variable with absent demand to must themselves have the marker declared explicitly in their type signatures. There may also be an argument for extending '~' to data constructors, in the same way that H98 uses '!'. This would allow parameters with absent demand to be stored in data structures. I can't think of a use for this right now, but it shouldn't be too much harder to add. What do you all think? Useful? Not worth the effort? Cheers, Andrew Bromage
Some thoughts on Enum, Bounded, and [ x .. y ] notation. Sometimes I want to write
x :: Foo <- [ .. ]
that is, an enumeration with both implicit lower and upper bound. This is useful if the type is both an instance of Enum and Bounded. Example: in module Foo, you have
data Foo = A | B | C deriving ( Enum, Bounded )
and in another module Bar, I want to loop over all possible Foo values, but - I don't want to change the text of Bar after I added one new Foo value (anywhere in the enumeration). A related point: the Haskell definition states that
enumFrom and enumFromThen should be defined with an implicit bound, thus: enumFrom x = enumFromTo x maxBound ..
This suggests that this `maxBound' is the method of class Bounded. I think this should be enforced - the *only* method in Enum should be `enumFromTo'. If one writes [ x .. ], then the compiler expands this to `enumFromTo x maxBound', and therefore adds a `Bounded' constraint. Likewise, [ .. y ] expands to `enumFromTo minBound y', and [ .. ] to `enumFromTo minBound maxBound' . I think this would make matters more simple and orthogonal. PS: and enumFromThenTo should just be removed, alongside n+k patterns :-) -- -- Johannes Waldmann ---- http://www.informatik.uni-leipzig.de/~joe/ -- -- joe@informatik.uni-leipzig.de -- phone/fax (+49) 341 9732 204/209 --
PS: and enumFromThenTo should just be removed, alongside n+k patterns :-)
Even though enumFromThenTo is useful for Integral types and Rational? [1,3 ..] [0,1/2 .. 10] :: [Rational] The only problems with enumFromThenTo come from providing Float and Double instances (and are the same problems encountered with providing Float and Double instances for enumFrom). -- Alastair Reid
Johannes Waldmann <joe@informatik.uni-leipzig.de> writes:
x :: Foo <- [ .. ]
A related point: the Haskell definition states that For any type that is an instance of class Bounded as well as Enum, the following should hold:
enumFrom and enumFromThen should be defined with an implicit bound, thus: enumFrom x = enumFromTo x maxBound
I think this should be enforced - the *only* method in Enum should be `enumFromTo'.
If one writes [ x .. ], then the compiler expands this to `enumFromTo x maxBound', and therefore adds a `Bounded' constraint. Likewise, [ .. y ] expands to `enumFromTo minBound y',
No, "half" infinite list like [1..] are useful.
and [ .. ] to `enumFromTo minBound maxBound' .
This I would not mind, I can not think of another reasonable translation. But why not define elements :: (Enum a, Bounded a) => [a] elements = [minBound .. maxBound] instead?
PS: and enumFromThenTo should just be removed, alongside n+k patterns :-)
Or preferably the Floating and Double Enum instances... Feri.
In article <20030625041751.GA29774@smtp.alicorna.com>, Andrew J Bromage <ajb@spamcop.net> wrote:
Another example is floating point format information, like the information in C's <float.h>. One might implement this as:
class (Bounded a) => FloatTraits a where epsilon :: a -- OK mantissaDigits :: Int -- Not OK!
Oh I do this all the time in HBase. I simply do this: data Type a = MkType getType :: a -> Type a getType _ = MkType class (Bounded a) => FloatTraits a where epsilon :: a mantissaDigits :: Type a -> Int The only annoyance is that you frequently have to write (MkType :: Type MyFloat). Syntactic sugar for _that_ would be useful. -- Ashley Yakeley, Seattle WA
Ashley Yakeley wrote:
Oh I do this all the time in HBase. I simply do this:
data Type a = MkType
getType :: a -> Type a getType _ = MkType
class (Bounded a) => FloatTraits a where epsilon :: a mantissaDigits :: Type a -> Int
Suffering from persecution mania, I prefer to know for sure that nobody never ever will pattern match on those a's. So I prefer to write: -- Values serving as type arguments type TypeArg a = a -> () -- Constant function as type argument typeArg :: TypeArg a typeArg = const () -- Extract type argument from value getTypeArg :: a -> TypeArg a getTypeArg _ = typeArg I start to use this style more agressively in Strafunski and the boilerplate gmaps. A side remark: Data.Dynamic does NOT use this style but rather Ashley's: ... typeOf :: a -> TypeRep ... which is a pitty because there is nothing in the type which prevents you from looking at a; its only in the comments. Every now and then I get this wrong.
The only annoyance is that you frequently have to write (MkType :: Type MyFloat). Syntactic sugar for _that_ would be useful.
On the other hand, supporting the above style with TypeArg's by an ADT, would make the whole thing entirely safe and transparent. -- Ralf Laemmel VU & CWI, Amsterdam, The Netherlands http://www.cs.vu.nl/~ralf/ http://www.cwi.nl/~ralf/
participants (6)
-
Alastair Reid -
Andrew J Bromage -
Ashley Yakeley -
Ferenc Wagner -
Johannes Waldmann -
Ralf Laemmel