1. 12
  1.  

  2. 3

    I feel like this problem arises because Haskell has only one canonical instance of a typeclass per type. In Scala, Coq, Agda, etc. it’s much easier to talk about instances as ‘values’ of a record type that describes the shape of the algebraic structure in question - in the dependantly typed world it contains signatures for the laws as well. This has its own drawbacks though, both from a pedagogical and UX point of view. I would expand more, but alas I am typing this on an IPad whilst travelling abroad…

    Edit: one way to look at it is that typeclasses like Monoid a are special record types (that live in a separate namespace in Haskell) that describe the shape that corresponding instances must conform to. Instances are singleton record values that conform to those special record types and are magically given names by the compiler, with a check to ensure they do not overlap with other instances.

    1. 2

      This has its own drawbacks though, both from a pedagogical and UX point of view.

      And refactoring. In Scala I always have to be careful about moving functions around to different modules. It’s stuff like “oh no, someone wrote their own Int Monoid and so my code broke”

      Thinking about refactoring is the worst. You end up just leaving code being bad.

    2. 2

      How is IO a monoid? It’s not even the right kind, is it?

      1. 3

        IO a is. It was proposed back in 2014 (Monoid instance for IO and discussion) and added in GHC 8:

        instance Monoid a => Monoid (IO a) where
            mempty = pure mempty
            mappend = liftA2 mappend
        

        Relevant bit from Gabriel Gonzalez’s talk at LambdaConf: https://youtu.be/WsA7GtUQeB8?t=17m43s. That whole talk is pretty great by the way.

        1. 2

          Oh, sure, IO a for a that forms a Monoid would be monoidal. Isn’t that just the case automatically by dint of being an applicative (since it’s a monad)? That’s how it tends to work in Scala.

          1. 1

            Not really, at least that’s not the case in Haskell as far as I know. You’re right that since it’s a monad, it’s also an applicative functor (and a functor too) but you don’t need Monoid f for a Functor f instance, or for Applicative f for that matter. Check out Typeclassopedia if you like :)

            1. 2

              No I mean that Monoid a, Applicative m should imply Monoid m a without needing to be a special case for m = IO

              1. 1

                I don’t think that there are any laws that ensure that with Monoid a and Applicative m you automatically get Monoid (m a), but can’t think of any examples off the top of my head. Also defining that instance means you can’t (or at least shouldn’t) define Monoid instances for anything which is a member of both classes. Generally instances of that sort (automatic based on a type being in these specific classes) seems like a good idea but actually isn’t.

                1. 2

                  The applicative laws are exactly the monoid laws. It creates an associative operation around an associative operation, so the laws are satisfied. This creates a useful type Ap:

                  https://hackage.haskell.org/package/reducers-3.12.1/docs/Data-Semigroup-Applicative.html

                  1. 2

                    It’s more like “Given Monoid a and Applicative f we can always construct a law-abiding Monoid (f a)”. We might not choose to actually have the instance reflected in code, but one is always there.

                  2. 1

                    Sure. I was just playing around:

                    {-# language FlexibleInstances #-}
                    
                    import Data.Monoid
                    import Control.Monad
                    import Control.Applicative
                    
                    instance (Monoid a, Applicative m) => Monoid (m a) where
                        mempty = pure mempty
                        mappend = liftA2 mappend
                    

                    Though in practice I’d use newtype Ap f m to avoid overlapping instances.

          2. 1