Re: [Haskell-cafe] Restricted type classes

Message: 20 Date: Sat, 04 Sep 2010 03:40:49 -0400 From: wren ng thornton
Subject: Re: [Haskell-cafe] Restricted type classes To: Haskell Cafe Message-ID: <4C81F801.606@freegeek.org> Content-Type: text/plain; charset=UTF-8; format=flowed On 9/3/10 12:16 AM, Ivan Lazar Miljenovic wrote:
2) How far should I go? Should I restrict myself to the "data-oriented" classes such as Functor, Traversable, etc. or should I try to make restricted versions of Applicative and Monad? Assuming I should:
I'd say you should do as much as seems reasonable. I tend to take things as far as I can, but that'd end up doing a lot of the same work as category-extras. For a general collections library, I think it'd make sense to try to keep things simpler than that if possible. The simpler it is, the better the uptake will be, so long as it's still complex enough to capture what it needs to.
I'd say you should include: Functor, Foldable, Traversable, Pointed, Applicative, Monad, and Monoid (both additive and multiplicative in separate classes, as in the monoids package). Those eight make for a really comprehensive toolkit that does most of the things people frequently need. Of course, not every collection will have instances for all of them.
Although I agree these do most things that people need, it's very rare that I need a data structure that guarantees *only* these features. Most often I need a map, a queue, etc. because I need either lookup by key, queue properties, etc. I think it's necessary to include classes like Indexable and Queue. Indexable may need to be split into two, one for ordered-Int indexes (i.e. lists) and one for Maps. Just a few days ago I wanted to change the priority queue implementation in some code. This was painful because the different available implementations all have varying APIs, so I couldn't just change the imports. I've wanted to do this in other contexts as well (e.g. maps and tries). The perhaps more important use is for coding algorithms that depend upon map properties, queue properties, etc., but leaving the actual implementation polymorphic so it can be chosen by a higher level. If the purpose of this project is to present a common interface for containers, then I think you should start by seeing what are the most common containers (I would guess lists, sets, maps, and queues) with a goal of providing their specific functionality through classes. Then all the common stuff can be stripped out and provided by superclasses. Of course we already know a great deal of the common operations, e.g. Traversable and Foldable, and we make use of those abstractions. It's this last step of a common map interface, queue interface, etc. that's missing. If you're just going to provide stuff we already have, I don't really see the point.
2c) Should I keep the classes as-is, or should I explicitly put in the constraints mentioned in the Typeclassopedia (e.g. make Applicative an explicit superclass of Monad, and define return = pure for compatability reasons)? If so, should I bring over Pointed, etc. from category-extras to round out the set or just stick with classes that are already in base?
If you're defining a new hierarchy, I'd say you should do it correctly, i.e.
class Functor where fmap class Functor => Pointed where unit -- or point class Pointed => Applicative where (<*>) ; (<*) ; (*>) class Applicative => Monad where (>>=) ; join
Shouldn't it be: class Functor where fmap class Pointed where point class (Functor f, Pointed f) => PointedFunctor f where class PointedFunctor f => Applicative f where (<*>); --etc. class Applicative f => Monad f where (>>=); --etc. even I might omit PointedFunctor, though, because it doesn't add anything. If it is omitted, that just means your Applicative contexts are slightly longer. But please don't make Pointed depend on Functor - we've already seen that it won't work for Bloom filters. Cheers, John

On Mon, Sep 6, 2010 at 5:11 PM, John Lato
Message: 20 Date: Sat, 04 Sep 2010 03:40:49 -0400 From: wren ng thornton
Subject: Re: [Haskell-cafe] Restricted type classes To: Haskell Cafe Message-ID: <4C81F801.606@freegeek.org> Content-Type: text/plain; charset=UTF-8; format=flowed On 9/3/10 12:16 AM, Ivan Lazar Miljenovic wrote:
2) How far should I go? Should I restrict myself to the "data-oriented" classes such as Functor, Traversable, etc. or should I try to make restricted versions of Applicative and Monad? Assuming I should:
I'd say you should do as much as seems reasonable. I tend to take things as far as I can, but that'd end up doing a lot of the same work as category-extras. For a general collections library, I think it'd make sense to try to keep things simpler than that if possible. The simpler it is, the better the uptake will be, so long as it's still complex enough to capture what it needs to.
I'd say you should include: Functor, Foldable, Traversable, Pointed, Applicative, Monad, and Monoid (both additive and multiplicative in separate classes, as in the monoids package). Those eight make for a really comprehensive toolkit that does most of the things people frequently need. Of course, not every collection will have instances for all of them.
Although I agree these do most things that people need, it's very rare that I need a data structure that guarantees *only* these features. Most often I need a map, a queue, etc. because I need either lookup by key, queue properties, etc. I think it's necessary to include classes like Indexable and Queue. Indexable may need to be split into two, one for ordered-Int indexes (i.e. lists) and one for Maps. Just a few days ago I wanted to change the priority queue implementation in some code. This was painful because the different available implementations all have varying APIs, so I couldn't just change the imports. I've wanted to do this in other contexts as well (e.g. maps and tries). The perhaps more important use is for coding algorithms that depend upon map properties, queue properties, etc., but leaving the actual implementation polymorphic so it can be chosen by a higher level. If the purpose of this project is to present a common interface for containers, then I think you should start by seeing what are the most common containers (I would guess lists, sets, maps, and queues) with a goal of providing their specific functionality through classes. Then all the common stuff can be stripped out and provided by superclasses. Of course we already know a great deal of the common operations, e.g. Traversable and Foldable, and we make use of those abstractions. It's this last step of a common map interface, queue interface, etc. that's missing. If you're just going to provide stuff we already have, I don't really see the point.
2c) Should I keep the classes as-is, or should I explicitly put in the constraints mentioned in the Typeclassopedia (e.g. make Applicative an explicit superclass of Monad, and define return = pure for compatability reasons)? If so, should I bring over Pointed, etc. from category-extras to round out the set or just stick with classes that are already in base?
If you're defining a new hierarchy, I'd say you should do it correctly, i.e.
class Functor where fmap class Functor => Pointed where unit -- or point class Pointed => Applicative where (<*>) ; (<*) ; (*>) class Applicative => Monad where (>>=) ; join
Shouldn't it be: class Functor where fmap class Pointed where point class (Functor f, Pointed f) => PointedFunctor f where class PointedFunctor f => Applicative f where (<*>); --etc. class Applicative f => Monad f where (>>=); --etc. even I might omit PointedFunctor, though, because it doesn't add anything. If it is omitted, that just means your Applicative contexts are slightly longer. But please don't make Pointed depend on Functor - we've already seen that it won't work for Bloom filters. Cheers, John
I think most people have been using "Pointed" merely as shorthand for "Pointed Functor" -- in the same way that Applicative isn't called ApplicativeFunctor, even though that's what it is. So if it doesn't work for Bloom filters, the reason is that Bloom filters aren't pointed functors. *That said*, I actually have nothing at all against splitting the 'a -> f a' method out into a separate class if you think it's useful, whether you call it Pointed or something else. (And `class (Pointed f, Functor f) => PointedFunctor f` is sort of cute.) I'm just trying to clarify where people are probably coming from -- those people can hopefully correct me if I'm wrong about this.
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
-- Work is punishment for failing to procrastinate effectively.

On 9/6/10 11:50 AM, Gábor Lehel wrote:
On Mon, Sep 6, 2010 at 5:11 PM, John Lato
wrote: But please don't make Pointed depend on Functor - we've already seen that it won't work for Bloom filters.
I think most people have been using "Pointed" merely as shorthand for "Pointed Functor" -- in the same way that Applicative isn't called ApplicativeFunctor, even though that's what it is. So if it doesn't work for Bloom filters, the reason is that Bloom filters aren't pointed functors.
Exactly. For my part I don't particularly care whether the class defining unit requires fmap or not. Though, as I've mentioned earlier, I don't see any particular reason for omitting the dependency. In particular, one of the primary complaints against the Monad class is precisely the fact that it *fails* to mention the Functor class as a (transitive) dependency. Why should we believe that making unit independent from fmap will fare any better? The unit natural transformation of pointed functors is not the same as any old inclusion function--- even if they are forced to agree when both are defined. Bloomfilters are not pointed functors. This is required, because bloomfilters are not structure preserving! But bloomfilters aren't terribly special in allowing a scalar type to be lifted into them. Vector spaces do the same thing; so do modules; so does path completion; so do free monoids; so does inclusion of real numbers into complex;... But one thing all of these examples has in common is that there is some particular structure which is preserved by the lifting process. The "associativity" between scalar multiplication and vector scaling, being one example. It's not clear to me that the singleton function for bloomfilters has any analogous structure it's preserving. Bloomfilters can be thought of as a sort of completion of the set of elements inserted into them, but there isn't much we can do to work with that. One thing I am opposed to, however, is introducing a new class without being explicit about the laws and properties required of instances. If the class does not have a set of laws that it obeys, then it will only lead to confusion and poor design. Why bother giving a name to something that doesn't have a special and interesting structure? This failure of specification has led to problems in the MonadPlus class as well as the Alternative class, where people weren't sure what sort of structure those classes were supposed to be representing, and therefore came to conflicting conclusions. In what sense can we define a unique parametric inclusion function that fails to meet the requirements of a pointed functor? What properties will this parametric inclusion function have, what laws will it require? Pointed functors do have laws; and explicitly mentioning these laws allows simplifying the definition of other classes (e.g., Applicative). But what laws do pointed non-functors have which make them a proper subclass of pointed functors? If we cannot come up with anything other than the parametricity of inclusion, then that would make me opposed to introducing this separate class. The only laws I can come up with for pointed types all make reference to some additional structure like the pointed type being a semiring, a functor, etc. I'm not sure that these additional structures all mean the same thing with the inclusion function they require, so I am uneasy with conflating them into a single class which satisfies no laws on its own. -- Live well, ~wren

On 7 September 2010 13:23, wren ng thornton
On 9/6/10 11:50 AM, Gábor Lehel wrote:
On Mon, Sep 6, 2010 at 5:11 PM, John Lato
wrote: But please don't make Pointed depend on Functor - we've already seen that it won't work for Bloom filters.
I think most people have been using "Pointed" merely as shorthand for "Pointed Functor" -- in the same way that Applicative isn't called ApplicativeFunctor, even though that's what it is. So if it doesn't work for Bloom filters, the reason is that Bloom filters aren't pointed functors.
Exactly. For my part I don't particularly care whether the class defining unit requires fmap or not. Though, as I've mentioned earlier, I don't see any particular reason for omitting the dependency. In particular, one of the primary complaints against the Monad class is precisely the fact that it *fails* to mention the Functor class as a (transitive) dependency. Why should we believe that making unit independent from fmap will fare any better?
Because there are some types for which pure/unit/singleton/etc. make sense but fmap doesn't?
The unit natural transformation of pointed functors is not the same as any old inclusion function--- even if they are forced to agree when both are defined. Bloomfilters are not pointed functors. This is required, because bloomfilters are not structure preserving! But bloomfilters aren't terribly special in allowing a scalar type to be lifted into them. Vector spaces do the same thing; so do modules; so does path completion; so do free monoids; so does inclusion of real numbers into complex;... But one thing all of these examples has in common is that there is some particular structure which is preserved by the lifting process. The "associativity" between scalar multiplication and vector scaling, being one example. It's not clear to me that the singleton function for bloomfilters has any analogous structure it's preserving. Bloomfilters can be thought of as a sort of completion of the set of elements inserted into them, but there isn't much we can do to work with that.
Ignoring that "Pointed" is used as a short-hand for "Pointed Functor": if we define a class with a method of type a -> c a, do we really need the Functor constraint? Is there anything stopping this class as well as Functor being super-classes of Applicative rather than Functor => Pointed => Applicative?
One thing I am opposed to, however, is introducing a new class without being explicit about the laws and properties required of instances. If the class does not have a set of laws that it obeys, then it will only lead to confusion and poor design. Why bother giving a name to something that doesn't have a special and interesting structure? This failure of specification has led to problems in the MonadPlus class as well as the Alternative class, where people weren't sure what sort of structure those classes were supposed to be representing, and therefore came to conflicting conclusions.
If we have Foldable as well, we can define length/size. We can then define a law such that length (singleton x) == 1, which to me is the whole point of the singleton method/class. -- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com

On Sep 7, 2010, at 5:23 AM, wren ng thornton wrote:
In particular, one of the primary complaints against the Monad class is precisely the fact that it *fails* to mention the Functor class as a (transitive) dependency. Why should we believe that making unit independent from fmap will fare any better?
A noteworthy difference is that fmap can be defined in terms of return and >>= but not in terms of point. IMHO, this is a good reason why fmap should be defined for every Monad and may be missing for some Pointed. Sebastian -- Underestimating the novelty of the future is a time-honored tradition. (D.G.)

2010/9/7 Gábor Lehel
*That said*, I actually have nothing at all against splitting the 'a -> f a' method out into a separate class if you think it's useful, whether you call it Pointed or something else. (And `class (Pointed f, Functor f) => PointedFunctor f` is sort of cute.)
It might be cute, but until we get class aliases [1] this results in yet another class to make your data type an instance of, and what's more it's one that doesnt' even give you anything. I think it makes much more sense to have Functor, Pointed and "(Functor f, Pointed f) => Applicative f" rather than a useless intermediary class. If, however, we could get class aliases _for free_ (i.e. something like "class alias PointedFunctor f = (Functor f, Pointed f)" for which all instances of Functor and Pointed are automatically instanced of PointedFunctor), then I can see that as being something nice to have. [1]: http://www.haskell.org/haskellwiki/Context_alias -- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com

2010/9/7 Ivan Lazar Miljenovic
2010/9/7 Gábor Lehel
: *That said*, I actually have nothing at all against splitting the 'a -> f a' method out into a separate class if you think it's useful, whether you call it Pointed or something else. (And `class (Pointed f, Functor f) => PointedFunctor f` is sort of cute.)
It might be cute, but until we get class aliases [1] this results in yet another class to make your data type an instance of, and what's more it's one that doesnt' even give you anything.
I think it makes much more sense to have Functor, Pointed and "(Functor f, Pointed f) => Applicative f" rather than a useless intermediary class. If, however, we could get class aliases _for free_ (i.e. something like "class alias PointedFunctor f = (Functor f, Pointed f)" for which all instances of Functor and Pointed are automatically instanced of PointedFunctor), then I can see that as being something nice to have.
I agree completely. John
participants (5)
-
Gábor Lehel
-
Ivan Lazar Miljenovic
-
John Lato
-
Sebastian Fischer
-
wren ng thornton