Void vs ()

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Void vs ()

Bardur Arantsson-2
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,

_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Void vs ()

MarLinn

> 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
_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Void vs ()

Bardur Arantsson-2
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,

_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Void vs ()

Bardur Arantsson-2
On 2017-03-24 23:11, Bardur Arantsson wrote:
> (I'm sure it'll work out, but I'll at least try it to see how it works
       ^^^
       not sure

(I'm guessing that omission was pretty obvious from context, but...)

_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Void vs ()

MarLinn
In reply to this post by Bardur Arantsson-2
> 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
_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Void vs ()

David Feuer
In reply to this post by Bardur Arantsson-2
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" <[hidden email]> wrote:
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,

_______________________________________________
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.


_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Void vs ()

Bardur Arantsson-2
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.
Reply | Threaded
Open this post in threaded view
|

Re: Void vs ()

Chris Smith-31
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 <[hidden email]> wrote:
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.


_______________________________________________
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.