
Hi all, [Context: I'm working on a CQRS/ES library and in the following, "i" is an identifier for an aggregate root, "e" is an event type, and the "Int" is a sequence number.] So, I have a data type data Foo i e = Foo i e Int where I want to have a corresponding data Batch i e = Batch i [Foo x e Int] (I'll come back to "x". It's just a metasyntactic variable for now. The point of the "Int" is that it's extra information that I really need to keep, but it's not essential to the Foo data structure itself.) with an operation batchFoos :: [Foo i e] -> [Batch i e] batchFoos = ... (basically a standard Data.List.groupBy where I compare for equality on the i's.) I also have an extraction function toList :: Batch i e -> (i, [Foo x e Int]) My question is then: What's the best thing to use for "x", Void or ()? The idea behind "collapsing" the "i" parameter away to an "x" is to signal to users to the API that they can be absolutely certain that when they call "toList", they can be sure that all the Foo's that they get back have the same i value, namely the one which is the first component of the tuple. An alernative I've been considering is actually to create a Foo' type which actually does eliminate the i parameter, but it seems a bit boilerplatey. (It's not a *lot* of code to duplicate, so if it looks like the best option, I'll do that... but it feels odd.) Anyone have any suggestions on what the best way to approach this would be? (Aside: AFAICT there's no reasonable way to implement anything like Foldable or Traversable to avoid the explicit "toList" function, right? AFAICT Foldable/Traversable constrain you to using the *actual* last type parameter, "e", for folding over. Are there any generic alternative type classes that let you "pick your own element type"? I'm guessing Traversal could possibly be that, but if I have to incur a Lens dependency, I'll just live with a "toList" function. This is just about whether it can be done elegantly -- it's not a big deal.) Regards,

My question is then: What's the best thing to use for "x", Void or ()? […] An alernative I've been considering is actually to create a Foo' type which actually does eliminate the i parameter
This is only a partial answer, but why not role your own i? And why not define Foo' in terms of Foo and whatever you end up using? data ThereIsNoIOnlyZuul -- intentionally left blank type Foo' e n = Foo ThereIsNoIOnlyZuul e n -- extra types included for clarity The reason I suggest this is that a) your own types are also documentation and b) you can change the behavior of your new type at will. Speaking of new types…
(Aside: AFAICT there's no reasonable way to implement anything like Foldable or Traversable to avoid the explicit "toList" function, right? […]) I suspect the canonical answer would be to use copious amounts of newtype. Downside: lots of wrapping and unwrapping. Upside: it's like an early Christmas!
Cheers, MarLinn

On 2017-03-24 22:50, MarLinn wrote:
My question is then: What's the best thing to use for "x", Void or ()? […] An alernative I've been considering is actually to create a Foo' type which actually does eliminate the i parameter
This is only a partial answer, but why not role your own i? And why not define Foo' in terms of Foo and whatever you end up using?
Not sure what you mean by "role your own i" ... but Foo' as a newtype of Foo might actually make a lot of sense. I think I'm *mostly* using type-class methods on the Foo, so I can just "propagate" those trivially. I hadn't considered that idea, thanks! (I'm sure it'll work out, but I'll at least try it to see how it works with the rest of the code.)
data ThereIsNoIOnlyZuul -- intentionally left blank
type Foo' e n = Foo ThereIsNoIOnlyZuul e n -- extra types included for clarity
Well, Void *is* ThereIsNoIOnlyZuul, at least AFAICT? :) Regards,

Not sure what you mean by "role your own i"
Oups. *roll Basically what I did with ThereIsNoIOnlyZuul. A custom Void/().
Well, Void *is* ThereIsNoIOnlyZuul, at least AFAICT? :)
Apart from details, I think so too, yes. Void has instances and some primitive functions while Zuul has a more meaningful name (well… theoretically) and the ability to become more than itself later on. Say you're doing it like I suggested, but then you decide that () would have been better than something Void-like – just add a single constructor to Zuul, done. No global search-replace necessary. But it also comes down to preferences. I personally have started to define my own types even for things that could be trivially expressed via Bool, Maybe, or Either just so that my types are more mnemonic. But then some of my hobby projects stall because I can spend hours on adjusting types before I even start to really implement something… Painting the yak shed, if you will. Cheers, MarLinn

Using Void states that the list will always be empty, because the only way
to put elements in it would be to fill in the Void fields with bottoms. ()
is the canonical choice for "doesn't matter", but a custom
data Boring = DoNotBother
would work as well.
On Mar 24, 2017 5:26 PM, "Bardur Arantsson"

On 2017-03-25 00:45, David Feuer wrote:
Using Void states that the list will always be empty, because the only way to put elements in it would be to fill in the Void fields with bottoms.
Right, so I kind of took it to mean "don't even bother trying to look at this -- there's nothing here", but I guess it could get kind of awkward if you were to, say, try to sort the list further. You'd have to purposefully avoid the field explicitly... Haven't go to it, but I'm probably going to go with a newtype and punt the naming by just doing Foo' vs Foo. After all, naming is really hard. :). Regards,

I'm afraid the answers so far haven't been as clear as they should. You
want (), and no one at all will be surprised by this use of ().
() is the type that says the value should contain no information. There is
one possible value; and since all values of () are the same, there's no
information contained in the choice of value. It is completely standard to
use () for situations exactly like this, where a type was designed with the
possibility of some information being communicated, but in this case
there's really nothing to say. Your situation is exactly analogous to
common uses, like IO () as the type for IO actions with no result.
So where does Void fit in? Void is a type that cannot occur at all! There
are NO possible values for Void. The result is that there are no possible
values for Foo Void e. And therefore, if you define Batch i e using Void,
the only values of Batch i e are those that have an empty list, so Batch i
e is isomorphic to Int. This is NOT what you want.
(The above is ignoring bottoms. It's true that you can create other
possible values of Foo Void e by using undefined as a value for Void. This
isn't a road you want to go down.)
On Sat, Mar 25, 2017 at 2:21 AM, Bardur Arantsson
On 2017-03-25 00:45, David Feuer wrote:
Using Void states that the list will always be empty, because the only way to put elements in it would be to fill in the Void fields with bottoms.
Right, so I kind of took it to mean "don't even bother trying to look at this -- there's nothing here", but I guess it could get kind of awkward if you were to, say, try to sort the list further. You'd have to purposefully avoid the field explicitly...
Haven't go to it, but I'm probably going to go with a newtype and punt the naming by just doing Foo' vs Foo. After all, naming is really hard. :).
Regards,
_______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.
participants (4)
-
Bardur Arantsson
-
Chris Smith
-
David Feuer
-
MarLinn