Strictness of Semigroup instance for Maybe

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

Strictness of Semigroup instance for Maybe

Donnacha Oisín Kidney-2
The current semigroup instance  for Maybe looks like  this:

    instance Semigroup a => Semigroup (Maybe a) where
        Nothing <> b       = b
        a       <> Nothing = a
        Just a  <> Just b  = Just (a <> b)


However, it could be lazier:

    instance Semigroup a => Semigroup (Maybe a) where
        Nothing <> b = b
        Just a  <> b = Just (maybe a (a<>) b)

This causes different behaviour for Data.Semigroup.First and Data.Monoid.First:

    >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
    Just 1
    >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap (pure.pure) $ [1..]
    _|_


A different definition for `Option` gets back the old behaviour:

    newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
        
    instance Semigroup a => Semigroup (LeftOption a) where
      LeftOption Nothing <> ys = ys
      LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>) ys))
        
    instance Semigroup a => Monoid (LeftOption a) where
      mempty = LeftOption Nothing
      mappend = (<>)
    
    >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption . Just . Data.Semigroup.First) $ [1..]
    Just 1


Is there any benefit to the extra strictness? Should this be changed?

Another consideration is that the definition could equivalently be right-strict, to get the desired behaviour for Last, but I think the left-strict definition probably follows the conventions more.

I originally posted this to reddit (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/) and was encouraged to post it here.

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

David Feuer
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:

> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

Tikhon Jelvis
I think the extra laziness makes sense here—it matches the behavior of common functions like &&. My general expectation is that functions are as lazy as they can be and, in the case of operators with two arguments, that evaluation goes left-to-right. (Again like &&.)

On Tue, May 22, 2018 at 4:37 PM, David Feuer <[hidden email]> wrote:
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:
> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

Ryan Reich
I second this expectation. I've recently had a hard time ensuring laziness in my work and it's been comforting in rooting out failures to know that the standard libraries, at least, are concerned about it.

On Tue, May 22, 2018, 16:57 Tikhon Jelvis <[hidden email]> wrote:
I think the extra laziness makes sense here—it matches the behavior of common functions like &&. My general expectation is that functions are as lazy as they can be and, in the case of operators with two arguments, that evaluation goes left-to-right. (Again like &&.)

On Tue, May 22, 2018 at 4:37 PM, David Feuer <[hidden email]> wrote:
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:
> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

Andrew Martin
In reply to this post by Tikhon Jelvis
I feel the the way concerning being lazy as possible and being left-strict where there is a symmetric choice to be made. This seems to be a common theme is base, although I’ve never seen it officially endorsed. I have seen Edward Kmett talk about this on reddit (contrasting it with the Monoid classes in strict-by-default languages), but I cannot find the thread.

Sent from my iPhone

On May 22, 2018, at 7:57 PM, Tikhon Jelvis <[hidden email]> wrote:

I think the extra laziness makes sense here—it matches the behavior of common functions like &&. My general expectation is that functions are as lazy as they can be and, in the case of operators with two arguments, that evaluation goes left-to-right. (Again like &&.)

On Tue, May 22, 2018 at 4:37 PM, David Feuer <[hidden email]> wrote:
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:
> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

Eric Mertens
Hello,

I think changing the strictness of this function could have potentially dramatic performance effects on a wide range of existing code. Exploring existing code to understand the exact impacts would be a huge challenge, and this is a change that would be hard to phase in.

The arbitrariness of decisions like this is part of what makes the Monoid class a mess in the first place. Attaching instances like this to otherwise generic types forces us to make arbitrary choices, which are often not documented on the instances themselves.

While the left-bias behavior might make sense in the case of an instance like we have for First, I don't see why it would be considered more correct in this case.

I'm -1 on this proposal.

Best regards,
Eric Mertens

On Wed, May 23, 2018 at 4:21 AM Andrew Martin <[hidden email]> wrote:
I feel the the way concerning being lazy as possible and being left-strict where there is a symmetric choice to be made. This seems to be a common theme is base, although I’ve never seen it officially endorsed. I have seen Edward Kmett talk about this on reddit (contrasting it with the Monoid classes in strict-by-default languages), but I cannot find the thread.

Sent from my iPhone

On May 22, 2018, at 7:57 PM, Tikhon Jelvis <[hidden email]> wrote:

I think the extra laziness makes sense here—it matches the behavior of common functions like &&. My general expectation is that functions are as lazy as they can be and, in the case of operators with two arguments, that evaluation goes left-to-right. (Again like &&.)

On Tue, May 22, 2018 at 4:37 PM, David Feuer <[hidden email]> wrote:
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:
> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

Carter Schonwald
yeah ... i agreed with Eric,
we almost need Lazy and Strict versions of monoid and each blows up in different ways. I definitely had epic space leaks from the lazy Maybe Monoid 

-1 :)

On Wed, May 23, 2018 at 11:14 AM, Eric Mertens <[hidden email]> wrote:
Hello,

I think changing the strictness of this function could have potentially dramatic performance effects on a wide range of existing code. Exploring existing code to understand the exact impacts would be a huge challenge, and this is a change that would be hard to phase in.

The arbitrariness of decisions like this is part of what makes the Monoid class a mess in the first place. Attaching instances like this to otherwise generic types forces us to make arbitrary choices, which are often not documented on the instances themselves.

While the left-bias behavior might make sense in the case of an instance like we have for First, I don't see why it would be considered more correct in this case.

I'm -1 on this proposal.

Best regards,
Eric Mertens

On Wed, May 23, 2018 at 4:21 AM Andrew Martin <[hidden email]> wrote:
I feel the the way concerning being lazy as possible and being left-strict where there is a symmetric choice to be made. This seems to be a common theme is base, although I’ve never seen it officially endorsed. I have seen Edward Kmett talk about this on reddit (contrasting it with the Monoid classes in strict-by-default languages), but I cannot find the thread.

Sent from my iPhone

On May 22, 2018, at 7:57 PM, Tikhon Jelvis <[hidden email]> wrote:

I think the extra laziness makes sense here—it matches the behavior of common functions like &&. My general expectation is that functions are as lazy as they can be and, in the case of operators with two arguments, that evaluation goes left-to-right. (Again like &&.)

On Tue, May 22, 2018 at 4:37 PM, David Feuer <[hidden email]> wrote:
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:
> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries



_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

Donnacha Oisín Kidney-2
Not sure if I’m correct on this, but wouldn’t you get space leaks in the right-strict version as well? Because the Just constructor itself isn’t strict, you still build up a long chain of <>. In the right-lazy version, you build up a long chain of maybe a (a<>), which is more expensive, yes, but not asymptotically. In other words, if you’ve got a space leak in the right-lazy version, you’ll also have one in the right-strict version.

On 23 May 2018, at 14:54, Carter Schonwald <[hidden email]> wrote:

yeah ... i agreed with Eric,
we almost need Lazy and Strict versions of monoid and each blows up in different ways. I definitely had epic space leaks from the lazy Maybe Monoid 

-1 :)

On Wed, May 23, 2018 at 11:14 AM, Eric Mertens <[hidden email]> wrote:
Hello,

I think changing the strictness of this function could have potentially dramatic performance effects on a wide range of existing code. Exploring existing code to understand the exact impacts would be a huge challenge, and this is a change that would be hard to phase in.

The arbitrariness of decisions like this is part of what makes the Monoid class a mess in the first place. Attaching instances like this to otherwise generic types forces us to make arbitrary choices, which are often not documented on the instances themselves.

While the left-bias behavior might make sense in the case of an instance like we have for First, I don't see why it would be considered more correct in this case.

I'm -1 on this proposal.

Best regards,
Eric Mertens

On Wed, May 23, 2018 at 4:21 AM Andrew Martin <[hidden email]> wrote:
I feel the the way concerning being lazy as possible and being left-strict where there is a symmetric choice to be made. This seems to be a common theme is base, although I’ve never seen it officially endorsed. I have seen Edward Kmett talk about this on reddit (contrasting it with the Monoid classes in strict-by-default languages), but I cannot find the thread.

Sent from my iPhone

On May 22, 2018, at 7:57 PM, Tikhon Jelvis <[hidden email]> wrote:

I think the extra laziness makes sense here—it matches the behavior of common functions like &&. My general expectation is that functions are as lazy as they can be and, in the case of operators with two arguments, that evaluation goes left-to-right. (Again like &&.)

On Tue, May 22, 2018 at 4:37 PM, David Feuer <[hidden email]> wrote:
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:
> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Strictness of Semigroup instance for Maybe

Andrew Martin
In reply to this post by Andrew Martin
Ah, here it is: https://www.reddit.com/r/haskell/comments/7nmcrj/what_evaluation_strategy_to_use_for_a_new/ds3ykie/. Relevant excerpt:

I often see people saying they can get away with working with a "Lazy" annotation in a strict language all the time. Strict languages with "opt-in" laziness tend to lack the courage of their conviction after making that statement, though. You can get a sense for how honest that appraisal is by looking at the definition of Monoid in their language. If you wanted to capture all of the power of a lazy language, it'd need to be marked Lazy in one or both of its arguments. If its only one then Dual gets hosed. (See many of the versions of scalaz) If neither then you dare not implement any or all via the Any or All monoid, lest you lose short-circuiting (&&) evaluation, if you have it at all. So you now have an abstraction, but dare not use it. You find that in a language that is strict, these notions like Monoid, Applicative, etc. all really need to pick up variants based on your strictness in each argument, and frankly, nobody bothers and it all gets swept under the rug.


On Wed, May 23, 2018 at 7:21 AM, Andrew Martin <[hidden email]> wrote:
I feel the the way concerning being lazy as possible and being left-strict where there is a symmetric choice to be made. This seems to be a common theme is base, although I’ve never seen it officially endorsed. I have seen Edward Kmett talk about this on reddit (contrasting it with the Monoid classes in strict-by-default languages), but I cannot find the thread.

Sent from my iPhone

On May 22, 2018, at 7:57 PM, Tikhon Jelvis <[hidden email]> wrote:

I think the extra laziness makes sense here—it matches the behavior of common functions like &&. My general expectation is that functions are as lazy as they can be and, in the case of operators with two arguments, that evaluation goes left-to-right. (Again like &&.)

On Tue, May 22, 2018 at 4:37 PM, David Feuer <[hidden email]> wrote:
I think extra laziness here would be a bit surprising.

On Tue, May 22, 2018 at 5:57 PM, Donnacha Oisín Kidney
<[hidden email]> wrote:
> The current semigroup instance  for Maybe looks like  this:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b       = b
>         a       <> Nothing = a
>         Just a  <> Just b  = Just (a <> b)
>
> However, it could be lazier:
>
>     instance Semigroup a => Semigroup (Maybe a) where
>         Nothing <> b = b
>         Just a  <> b = Just (maybe a (a<>) b)
>
> This causes different behaviour for Data.Semigroup.First and
> Data.Monoid.First:
>
>     >>>  Data.Monoid.getFirst . foldMap pure $ [1..]
>     Just 1
>     >>>  fmap Data.Semigroup.getFirst . Data.Semigroup.getOption . foldMap
> (pure.pure) $ [1..]
>     _|_
>
> A different definition for `Option` gets back the old behaviour:
>
>     newtype LeftOption a = LeftOption { getLeftOption :: Maybe a }
>
>     instance Semigroup a => Semigroup (LeftOption a) where
>       LeftOption Nothing <> ys = ys
>       LeftOption (Just x) <> LeftOption ys = LeftOption (Just (maybe x (x<>)
> ys))
>
>     instance Semigroup a => Monoid (LeftOption a) where
>       mempty = LeftOption Nothing
>       mappend = (<>)
>
>     >>> fmap Data.Semigroup.getFirst . getLeftOption . foldMap (LeftOption .
> Just . Data.Semigroup.First) $ [1..]
>     Just 1
>
> Is there any benefit to the extra strictness? Should this be changed?
>
> Another consideration is that the definition could equivalently be
> right-strict, to get the desired behaviour for Last, but I think the
> left-strict definition probably follows the conventions more.
>
> I originally posted this to reddit
> (https://www.reddit.com/r/haskell/comments/8lbzan/semigroup_maybe_too_strict/)
> and was encouraged to post it here.
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries



--
-Andrew Thaddeus Martin

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries