lorentz-0.11.0: src/Lorentz/ReferencedByName.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{-# LANGUAGE FunctionalDependencies #-}
-- | Referenced-by-name versions of some instructions.
--
-- They allow to "dig" into stack or copy elements of stack referring them
-- by label.
module Lorentz.ReferencedByName
( -- * Constraints
HasNamedVar
, HasNamedVars
, (:=)
-- * Instructions
, dupL
, dupLNamed
-- * Other
, VarIsUnnamed
) where
import Data.Constraint ((\\))
import Data.Singletons (Sing)
import Named (NamedF(..))
import Lorentz.ADT
import Lorentz.Base
import Lorentz.Coercions
import Lorentz.Instr
import Michelson.Text
import Michelson.Typed
import Util.Label
import Util.Peano
import Util.Type
import Util.TypeLits
-- Errors
----------------------------------------------------------------------------
type family StackElemNotFound name :: ErrorMessage where
StackElemNotFound name =
'Text "Element with name `" ':<>: 'ShowType name ':<>:
'Text "` is not present on stack"
data NamedVariableNotFound name
-- Helpers
----------------------------------------------------------------------------
-- | Name of a variable on stack.
data VarNamed = VarNamed Symbol | VarUnnamed | VarNameDummy
-- | Get variable name.
type family VarName (a :: Type) :: VarNamed where
VarName (NamedF _ _ name) = 'VarNamed name
VarName _ = 'VarUnnamed
type family AnyVN :: VarNamed
-- Attach an error message to given variable.
-- If its evaluation is stuck, compiler's attempt to display this type
-- will cause the given error being displayed.
type family Assert (vn :: VarNamed) (err :: Constraint) :: VarNamed where
Assert 'VarNameDummy _ = AnyVN
Assert vn _ = vn
type family VarNamePretty' (x :: Type) (vn :: VarNamed) :: VarNamed where
VarNamePretty' x vn = Assert vn
(TypeError
('Text "Not clear which name `" ':<>: 'ShowType x ':<>: 'Text "` variable has" ':$$:
'Text "Consider adding `VarIsUnnamed " ':<>: 'ShowType x ':<>: 'Text "` constraint" ':$$:
'Text "or carrying a named variable instead"
)
)
-- | 'VarName' with pretty error message.
type VarNamePretty x = VarNamePretty' x (VarName x)
-- | Requires type @x@ to be an unnamed variable.
--
-- When e.g. 'dupL' sees a polymorphic variable, it can't judge whether
-- is it a variable we are seeking for or not; @VarIsUnnamed@ helps to
-- assure the type system that given variable won't be named.
type VarIsUnnamed x = VarName x ~ 'VarUnnamed
----------------------------------------------------------------------------
-- Dup
----------------------------------------------------------------------------
-- | Indicates that stack @s@ contains a @name :! var@ or @name :? var@ value.
{- Implementation notes:
We intentially keep this typeclass as simple as possible so that if a user has
@
myFunc :: Integer : s :-> Integer : s
myFunc = something that requires @"globalVar" :! Integer@
@
then user gets clear @Missing `HasEnv s globalVar Integer` instance@ error message,
and can easily add this constraint to his methods.
-}
class HasNamedVar (s :: [Type]) (name :: Symbol) (var :: Type)
| s name -> var where
-- | 1-based position of the variable on stack.
varPosition :: VarPosition s name var
type ConstraintVarPosition s n =
( SingI n, KnownPeano n, n > 'Z ~ 'True
, RequireLongerOrSameLength s n, RequireLongerOrSameLength (ToTs s) n
)
data VarPosition (s :: [Type]) (name :: Symbol) (var :: Type) where
VarPosition
:: (ConstraintVarPosition s n)
=> (Sing (n :: Nat))
-> VarPosition s name var
instance ( TypeError (StackElemNotFound name)
, var ~ NamedVariableNotFound name
) =>
HasNamedVar '[] name var where
varPosition = error "impossible"
instance ( ElemHasNamedVar (ty : s) name var
(VarNamePretty ty == 'VarNamed name)
) =>
HasNamedVar (ty : s) name var where
varPosition = elemVarPosition @(ty : s) @name @var @(VarNamePretty ty == 'VarNamed name)
-- Helper for handling each separate variable on stack
class ElemHasNamedVar s name var (nameMatch :: Bool)
| s name nameMatch -> var where
elemVarPosition :: VarPosition s name var
instance (ty ~ NamedF f var name) =>
ElemHasNamedVar (ty : s) name var 'True where
elemVarPosition = VarPosition (SS SZ)
instance (HasNamedVar s name var) =>
ElemHasNamedVar (ty : s) name var 'False where
elemVarPosition = case varPosition @s @name @var of
VarPosition n -> VarPosition (SS n)
-- | Version of 'HasNamedVar' for multiple variables.
--
-- >>> type HasContext = HasNamedVars s ["x" := Integer, "f" := Lambda MText MText]
type family HasNamedVars (s :: [Type]) (vs :: [NamedField]) :: Constraint where
HasNamedVars _ '[] = ()
HasNamedVars s ((n := ty) ': vs) = (HasNamedVar s n ty, HasNamedVars s vs)
-- | Get the variable at @n@-th position on stack, assuming that caller is sure
-- that stack is long enough.
--
-- @martoon: I'm not ready to fight the compiler regarding numerous
-- complex constraints, so just assuring it that those constraints will hold.
--
-- @heitor.toledo: GHC doesn't seem to enjoy it that we use 'ConstraintDUPNLorentz',
-- so I replaced it with the expanded definition.
dupLUnsafe
:: forall n s var.
(ConstraintVarPosition s n)
=> Sing (n :: Nat) -> s :-> var : s
dupLUnsafe _ =
dupNPeano @n
\\ provideConstraintUnsafe @(Take (Decrement n) s ++ (var : Drop n s) ~ s)
\\ provideConstraintUnsafe @(Take (Decrement n) (ToTs s) ++ (ToT var : Drop n (ToTs s)) ~ ToTs s)
-- | Version of 'dupL' that leaves a named variable on stack.
dupLNamed
:: forall var name s.
(HasNamedVar s name var)
=> Label name -> s :-> (name :! var) : s
dupLNamed _ =
case varPosition @s @name @var of
VarPosition sn -> dupLUnsafe sn
-- | Take the element with given label on stack and copy it on top.
--
-- If there are multiple variables with given label, the one closest
-- to the top of the stack is picked.
dupL :: forall var name s.
(HasNamedVar s name var)
=> Label name -> s :-> var : s
dupL l = dupLNamed l # fromNamed l
-- Samples
----------------------------------------------------------------------------
_dupSample1
:: [Integer, Natural, "a" :! (), "b" :! MText, "c" :! ByteString]
:-> [MText, Integer, Natural, "a" :! (), "b" :! MText, "c" :! ByteString]
_dupSample1 =
dupL #b
_dupSample2
:: (HasNamedVar s "x" Natural)
=> (Integer : s)
:-> (Natural : Integer : s)
_dupSample2 =
dupL #x
_dupSample3
:: (HasNamedVar s "x" Natural, VarIsUnnamed store)
=> (store : s)
:-> (Natural : store : s)
_dupSample3 =
dupL #x