Lysxia / generic-data

Generic data types in Haskell, utilities for GHC.Generics
https://hackage.haskell.org/package/generic-data
MIT License
44 stars 9 forks source link

Microsurgery to change the type of all fields by applying a newtype? #32

Closed ocharles closed 4 years ago

ocharles commented 4 years ago

This may already be possible, but I want to define a Monoid instance for:

data PalletUsage =
  PalletUsage
    { pallets :: Rational
    , strips :: Rational
    }

by viewing it as isomorphic to (Sum Rational, Sum Rational).

Could this be done with some kind of microsurgery?

Lysxia commented 4 years ago

Yes! This transformation to apply a type constructor to every field is the OnFields (micro)surgery.

You can then use Surgery' to "apply a surgery", i.e., modify the Generic instance of a type. So Surgery' (OnFields Sum) PalletUsage has a Generic instance as if you wrote

data PalletUsage =
  PalletUsage
    { pallets :: Sum Rational
    , strips :: Sum Rational
    }

And then you can derive Semigroup and Monoid for PalletUsage via GenericProduct (Surgery' (OnFields Sum) PalletUsage).

(It turns out you have to use GenericProduct here, while Generically isn't well-suited for deriving Monoid via surgeries because of its new Monoid instance...)

{-# LANGUAGE DeriveGeneric, DerivingVia #-}
module B where

import Data.Monoid
import GHC.Generics (Generic)
import Generic.Data (GenericProduct(..))
import Generic.Data.Microsurgery

data PalletUsage = PalletUsage Rational Rational
  deriving (Show, Generic)
  deriving (Semigroup, Monoid) via SumSurgery PalletUsage

type SumSurgery a = GenericProduct (Surgery' (OnFields Sum) a)

There are some things here I could add to the documentation, so thanks for the question!

ocharles commented 4 years ago

Ah, very nice! This answers my question, thank you! Do you want to leave this open to write docs? And thank you for a very very nice library, it's really changing how we write code at work

Lysxia commented 4 years ago

You're welcome!

Yes, let's keep this open as a reminder, I'll get to it at the end of the week.