packages feed

witch-0.2.1.0: src/lib/Witch/Casting.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Witch.Casting where

import qualified Data.Bifunctor as Bifunctor
import qualified Data.Function as Function
import qualified Witch.Cast as Cast

-- | This type mostly exists to make it easier to derive instances through
-- some other type. It does this by leaning on the 'Cast.Cast' type class. For
-- example, consider this data type:
--
-- > data Toggle = Off | On
-- > instance Cast Toggle String where
-- >   cast toggle = case toggle of
-- >     Off -> "off"
-- >     On -> "on"
--
-- If you wanted to implement a 'Show' instance for @Toggle@ by going through
-- a 'String', you would need to write something like this:
--
-- > instance Show Toggle where
-- >   show = show . into @String
--
-- That may not very complicated, but at the same time it is not very
-- interesting. And it can be tricky if you want to keep other instances (like
-- 'Read') in sync. That's where the 'Casting' type comes in! You can
-- derive the above instance like so:
--
-- > data Toggle = Off | On
-- >   deriving Show via Casting Toggle String
newtype Casting source target
  = Casting source

-- | Uses @coerce@.
instance Cast.Cast s (Casting s t)

-- | Uses @coerce@.
instance Cast.Cast (Casting s t) s

instance (Cast.Cast t s, Bounded t) => Bounded (Casting s t) where
  maxBound = Cast.cast $ Cast.cast @t @s maxBound
  minBound = Cast.cast $ Cast.cast @t @s minBound

instance (Cast.Cast s t, Cast.Cast t s, Enum t) => Enum (Casting s t) where
  fromEnum = fromEnum . Cast.cast @s @t . Cast.cast
  toEnum = Cast.cast . Cast.cast @t @s . toEnum

instance (Cast.Cast s t, Eq t) => Eq (Casting s t) where
  (==) = Function.on (==) $ Cast.cast @s @t . Cast.cast

instance (Cast.Cast s t, Ord t) => Ord (Casting s t) where
  compare = Function.on compare $ Cast.cast @s @t . Cast.cast

instance (Cast.Cast t s, Read t) => Read (Casting s t) where
  readsPrec = fmap (fmap . Bifunctor.first $ Cast.cast . Cast.cast @t @s) . readsPrec

instance (Cast.Cast s t, Show t) => Show (Casting s t) where
  show = show . Cast.cast @s @t . Cast.cast