morley-1.15.0: src/Michelson/Typed/Sing.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{-# LANGUAGE StandaloneKindSignatures #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Module, providing singleton boilerplate for
-- 'T' data types.
--
-- Some functions from Data.Singletons are provided alternative version here.
-- Some instances which are usually generated with TH are manually implemented
-- as they require some specific constraints, namely 'Typeable' and/or
-- 'Converge', not provided in instances generated by TH.
module Michelson.Typed.Sing
( SingT (..)
, castSingE
, castM
, eqP
, requireEq
) where
import Data.Type.Equality ((:~:)(..))
import Data.Singletons (Demote, KindOf, SingI(..), demote)
import Data.Singletons.TH (genSingletons, singDecideInstance)
import Data.Singletons.TH.Options (Options(..), defaultOptions, withOptions)
import Language.Haskell.TH (Name, mkName, nameBase)
import Fmt ((+||), (||+))
import Michelson.Typed.T (T(..))
import Util.Sing (SingI1(..), castSing, eqI)
-- | 'SingI' and 'SDecide' instances for the 'T' kind.
$(let singPrefix, sPrefix :: Name -> Name
singPrefix nm = mkName ("Sing" ++ nameBase nm)
sPrefix nm = mkName ("S" ++ nameBase nm) in
withOptions defaultOptions{singledDataConName = sPrefix, singledDataTypeName = singPrefix} $
concat <$> sequence [genSingletons [''T], singDecideInstance ''T]
)
---------------------------------------------
-- 'withSingI' usage
--------------------------------------------
-- | Previously, we were using 'SingI' constraints in 'SingT'
-- constructors. That was not so optimal because we have been
-- spending too much space at runtime. Instead of that, we process
-- values of 'SingT' using the function 'withSingI' in those places
-- where the 'SingI' constraint is required. 'withSingI' allows one
-- to create the 'SingI' context for a given 'Sing'.
---------------------------------------------
-- Singleton-related helpers for T
----------------------------------------------
instance SingI1 'TList where
withSingI1 x = x
instance SingI k => SingI1 ('TMap k) where
withSingI1 x = x
castSingE
:: forall (a :: T) (b :: T) t. (SingI a, SingI b)
=> t a -> Either Text (t b)
castSingE = maybeToRight errMsg . castSing
where
errMsg = "Type mismatch: expected " +|| demote @a ||+
", got " +|| demote @b ||+ ""
-- | Monadic version of 'castSing'.
-- Throws an error using the given function if the cast fails.
castM
:: forall (a :: T) (b :: T) t m. (SingI a, SingI b, Monad m)
=> t a
-> (forall x. Demote (KindOf a) -> Demote (KindOf b) -> m x)
-> m (t b)
castM a throwErr =
requireEq @a @b throwErr <&> \Refl -> a
-- | Monadic version of 'eqI'.
-- Throws an error using the given function if the two types are not equal.
requireEq
:: forall (a :: T) (b :: T) m. (SingI a, SingI b, Monad m)
=> (forall x. Demote (KindOf a) -> Demote (KindOf b) -> m x)
-> m (a :~: b)
requireEq throwErr =
case eqI @a @b of
Just p -> pure p
Nothing -> throwErr (demote @a) (demote @b)
-- | Version of 'eqI' that uses 'Proxy'
eqP :: forall (a :: T) (b :: T). (SingI a, SingI b) => Proxy a -> Proxy b -> Maybe (a :~: b)
eqP _ _ = eqI @a @b