classes for data structures

Hi, I've been working a lot with maps, sets and lists. While the process of getting things out of them is abstracted by foldable, traversable and friends, the process of building one up is not. Would it be possible to have something like: class Buildable b where empty :: b a --makes an empty b with elements of type a insert :: a -> b a -> b a --inserts the new element into the buildable I'm not particularly wedded to the names. It's just that it would be very convenient sometimes to collect data items into e.g. a list or a set, without pushing in some klunky foralled insert function. I see that this is realted to MonadPlus, but the space of possible container classes is wider than that of monads e.g. set. Matthew

On Wed, 6 Feb 2008, Matthew Pocock wrote:
Hi,
I've been working a lot with maps, sets and lists. While the process of getting things out of them is abstracted by foldable, traversable and friends, the process of building one up is not. Would it be possible to have something like:
class Buildable b where empty :: b a --makes an empty b with elements of type a insert :: a -> b a -> b a --inserts the new element into the buildable
How can this interface be used both for lists and maps? I'm afraid it is difficult to find an interface that fits the needs of many different data structures. Maybe at least a generalized 'unfoldr' is possible.

On Feb 6, 2008 9:00 AM, Henning Thielemann
On Wed, 6 Feb 2008, Matthew Pocock wrote:
class Buildable b where empty :: b a --makes an empty b with elements of type a insert :: a -> b a -> b a --inserts the new element into the buildable
How can this interface be used both for lists and maps? [...]
One solution is to change the class slightly: class Buildable t x where empty :: t insert :: x -> t -> t instance Buildable (Map k a) (k, a) where empty = Map.empty insert = uncurry Map.insert -- Denis

On Wed, 6 Feb 2008, Denis Bueno wrote:
On Feb 6, 2008 9:00 AM, Henning Thielemann
wrote: On Wed, 6 Feb 2008, Matthew Pocock wrote:
class Buildable b where empty :: b a --makes an empty b with elements of type a insert :: a -> b a -> b a --inserts the new element into the buildable
How can this interface be used both for lists and maps? [...]
One solution is to change the class slightly:
class Buildable t x where empty :: t insert :: x -> t -> t
instance Buildable (Map k a) (k, a) where empty = Map.empty insert = uncurry Map.insert
ok, maybe with functional dependency t -> x

On Feb 6, 2008 9:40 AM, Henning Thielemann
On Wed, 6 Feb 2008, Denis Bueno wrote:
On Feb 6, 2008 9:00 AM, Henning Thielemann
wrote: On Wed, 6 Feb 2008, Matthew Pocock wrote:
class Buildable b where empty :: b a --makes an empty b with elements of type a insert :: a -> b a -> b a --inserts the new element into the buildable
How can this interface be used both for lists and maps? [...]
One solution is to change the class slightly:
class Buildable t x where empty :: t insert :: x -> t -> t
instance Buildable (Map k a) (k, a) where empty = Map.empty insert = uncurry Map.insert
ok, maybe with functional dependency t -> x
I'm not sure about that. It's often convenient to have two instances, one like the one I gave above, and others involving something that embeds a key-value pair: type SomethingWithKV k a = KV {getKV :: (k, a)} instance Buildable (Map k a) (SomethingWithKV k a) where empty = Map.empty insert s m = uncurry Map.insert (getKV s) m I have done this before -- it's very convenient, and I think makes the code that uses empty and insert more robust, and easier to read. -- Denis

On Wed, 6 Feb 2008, Denis Bueno wrote:
On Feb 6, 2008 9:40 AM, Henning Thielemann
ok, maybe with functional dependency t -> x
I'm not sure about that. It's often convenient to have two instances, one like the one I gave above, and others involving something that embeds a key-value pair:
type SomethingWithKV k a = KV {getKV :: (k, a)} instance Buildable (Map k a) (SomethingWithKV k a) where empty = Map.empty insert s m = uncurry Map.insert (getKV s) m
I have done this before -- it's very convenient, and I think makes the code that uses empty and insert more robust, and easier to read.
But why do you want to have the special type SomethingWithKV, but not MapSomething ?

On Feb 6, 2008 10:36 AM, Henning Thielemann
type SomethingWithKV k a = KV {getKV :: (k, a)} instance Buildable (Map k a) (SomethingWithKV k a) where empty = Map.empty insert s m = uncurry Map.insert (getKV s) m
I have done this before -- it's very convenient, and I think makes the code that uses empty and insert more robust, and easier to read.
But why do you want to have the special type SomethingWithKV, but not MapSomething ?
Do you mean a map that maps SomethingWithKV to whatever, instead of k to v? My point is that if you add the functional dependency, you can't write instances for types which wrap what you're really interested in. You have to manually unwrap those types everywhere they interact with a map. This is tedious and error-prone. Being able to write such instances is useful, I think, though not always clearer in all cases. -- Denis

On Wed, 6 Feb 2008, Denis Bueno wrote:
On Feb 6, 2008 10:36 AM, Henning Thielemann
wrote: type SomethingWithKV k a = KV {getKV :: (k, a)} instance Buildable (Map k a) (SomethingWithKV k a) where empty = Map.empty insert s m = uncurry Map.insert (getKV s) m
I have done this before -- it's very convenient, and I think makes the code that uses empty and insert more robust, and easier to read.
But why do you want to have the special type SomethingWithKV, but not MapSomething ?
Do you mean a map that maps SomethingWithKV to whatever, instead of k to v?
I mean a Map that is specialised for storing SomethingWithKV instead of plain pairs. (If I imagine a Map as a list of pairs that is optimized for efficiency.)

Matthew Pocock wrote:
Hi,
I've been working a lot with maps, sets and lists. While the process of getting things out of them is abstracted by foldable, traversable and friends, the process of building one up is not. Would it be possible to have something like:
class Buildable b where empty :: b a --makes an empty b with elements of type a insert :: a -> b a -> b a --inserts the new element into the buildable
Another approach uses : singleton :: a -> b a and then just mappend :: b a -> b a -> b a i.e. make b a into a Monoid. singleton = pure = return, if there happens to be a Monad/Applicative instance around. And indeed there *will* be a Monad, if there is a sensible way of defining concat :: b (b a) -> b a, which there probably is. Jules

Am Mittwoch, 6. Februar 2008 16:12 schrieb Jules Bean:
And indeed there *will* be a Monad, if there is a sensible way of defining concat :: b (b a) -> b a, which there probably is.
Not with sets. “concat” on Set would have type Ord a => Set (Set a) -> Set a instead of Set (Set a) -> Set a. Best wishes, Wolfgang
participants (5)
-
Denis Bueno
-
Henning Thielemann
-
Jules Bean
-
Matthew Pocock
-
Wolfgang Jeltsch