packages feed

lawful-conversions-0.4.0: src/library/LawfulConversions/Proxies/ViaIsSome.hs

module LawfulConversions.Proxies.ViaIsSome where

import LawfulConversions.Algebra
import LawfulConversions.Prelude
import qualified Test.QuickCheck as QuickCheck

-- |
-- Helper for deriving common instances on types which have an instance of @'IsSome' a@ using the @DerivingVia@ extension.
--
-- E.g.,
--
-- > newtype Percent = Percent Double
-- >   deriving newtype (Show, Eq, Ord)
-- >   deriving (Read, Arbitrary) via (ViaIsSome Double Percent)
-- >
-- > instance IsSome Double Percent where
-- >   to (Percent double) = double
-- >   maybeFrom double =
-- >     if double < 0 || double > 1
-- >       then Nothing
-- >       else Just (Percent double)
--
-- In the code above all the instances that are able to construct the values of 'Percent' are automatically derived based on the @IsSome Double Percent@ instance.
-- This guarantees that they only construct values that pass thru the checks defined in 'maybeFrom'.
newtype ViaIsSome a b = ViaIsSome b

instance (IsSome a b) => IsSome a (ViaIsSome a b) where
  to (ViaIsSome a) = to a
  maybeFrom = fmap ViaIsSome . maybeFrom

instance IsSome b (ViaIsSome a b) where
  to = coerce

instance IsSome (ViaIsSome a b) b where
  to = coerce

instance IsMany b (ViaIsSome a b)

instance IsMany (ViaIsSome a b) b

instance Is b (ViaIsSome a b)

instance Is (ViaIsSome a b) b

instance (IsSome a b, Show a) => Show (ViaIsSome a b) where
  show (ViaIsSome a) = show (to @a a)

instance (IsSome a b, Read a) => Read (ViaIsSome a b) where
  readPrec = do
    a <- readPrec
    case maybeFrom @a a of
      Just a -> pure (ViaIsSome a)
      Nothing -> fail "Value is not from the subset"

instance (IsSome a b, IsString a) => IsString (ViaIsSome a b) where
  fromString =
    maybe (error "Value is not from the subset") ViaIsSome . maybeFrom @a . fromString

instance (IsSome a b, Eq a) => Eq (ViaIsSome a b) where
  (==) = on (==) (to @a)

instance (IsSome a b, Ord a) => Ord (ViaIsSome a b) where
  compare = on compare (to @a)

instance (IsSome a b, QuickCheck.Arbitrary a) => QuickCheck.Arbitrary (ViaIsSome a b) where
  arbitrary =
    QuickCheck.suchThatMap QuickCheck.arbitrary (maybeFrom @a)
  shrink value = do
    shrunkValue <- QuickCheck.shrink (to @a value)
    shrunkValue
      & maybeFrom
      & maybeToList