33{-# LANGUAGE DefaultSignatures #-}
44{-# LANGUAGE PolyKinds #-}
55
6- module Data.Spine (HasSpine (.. ), deriveSpine , OfSpine (.. )) where
6+ module Data.Spine where
7+
8+ -- TODO
9+ -- (HasSpine (..), deriveSpine, OfSpine (..))
710
811import Prelude
912
13+ import Data.Data (Proxy )
14+ import Data.List (elemIndex )
15+ import Data.Map qualified as Map
16+ import Data.Maybe (mapMaybe )
17+ import Data.Void (Void )
18+ import GHC.Natural (Natural )
19+ import GHC.Records (HasField )
20+ import GHC.TypeLits (KnownSymbol , symbolVal )
1021import Language.Haskell.TH
1122import Language.Haskell.TH.Syntax
1223
24+ import PlutusTx (FromData , ToData , UnsafeFromData , unstableMakeIsData )
25+
1326-- | Definitions
1427
1528{- | Spine is datatype, which tags constructors of ADT.
2235 ) =>
2336 HasSpine sop
2437 where
25- type Spine sop
38+ type Spine sop = spine | spine -> sop
2639 getSpine :: sop -> Spine sop
2740
41+ -- | Version of `HasSpine` knowing its Plutus Data encoding
42+ class
43+ ( Enum (Spine sop )
44+ , Bounded (Spine sop )
45+ , HasSpine sop
46+ , UnsafeFromData sop
47+ , ToData sop
48+ , FromData sop
49+ ) =>
50+ HasPlutusSpine sop
51+ where
52+ fieldsMap :: Map. Map (Spine sop ) [String ]
53+
54+ toNat :: Int -> Natural
55+ toNat = fromInteger . toInteger
56+
57+ spineFieldsNum :: forall sop . (HasPlutusSpine sop ) => Spine sop -> Natural
58+ spineFieldsNum spine = toNat $ length $ (fieldsMap @ sop ) Map. ! spine
59+
60+ -- TODO: use spine do discriminate
61+ fieldNum ::
62+ forall sop label .
63+ (HasPlutusSpine sop , KnownSymbol label ) =>
64+ Proxy label ->
65+ Natural
66+ fieldNum proxyLabel =
67+ head $ mapMaybe fieldIndex (Map. elems $ fieldsMap @ sop )
68+ where
69+ fieldName = symbolVal proxyLabel
70+ fieldIndex dict = toNat <$> elemIndex fieldName dict
71+
72+ allSpines :: forall sop . (HasPlutusSpine sop ) => [Spine sop ]
73+ allSpines = [Prelude. minBound .. Prelude. maxBound ]
74+
75+ instance HasSpine Void where
76+ type Spine Void = Void
77+ getSpine = \ case {}
78+
2879instance (HasSpine sop1 , HasSpine sop2 ) => HasSpine (sop1 , sop2 ) where
2980 type Spine (sop1 , sop2 ) = (Spine sop1 , Spine sop2 )
3081 getSpine (d1, d2) = (getSpine d1, getSpine d2)
@@ -33,15 +84,14 @@ instance (HasSpine sop) => HasSpine (Maybe sop) where
3384 type Spine (Maybe sop ) = Maybe (Spine sop )
3485 getSpine = fmap getSpine
3586
36- -- | Newtype encoding sop value of fixed known spine
37- newtype OfSpine (x :: Spine datatype ) = UnsafeMkOfSpine { getValue :: datatype }
87+ -- Deriving utils
3888
39- -- | Deriving utils
4089addSuffix :: Name -> String -> Name
4190addSuffix (Name (OccName name) flavour) suffix =
4291 Name (OccName $ name <> suffix) flavour
4392
44- reifyDatatype :: Name -> Q (Name , [Name ])
93+ -- FIXME: cleaner return type
94+ reifyDatatype :: Name -> Q (Name , [Name ], [[Name ]])
4595reifyDatatype ty = do
4696 (TyConI tyCon) <- reify ty
4797 (name, cs :: [Con ]) <-
@@ -50,7 +100,17 @@ reifyDatatype ty = do
50100 NewtypeD _ n _ _ cs _ -> pure (n, [cs])
51101 _ -> fail " deriveTags: only 'data' and 'newtype' are supported"
52102 csNames <- mapM consName cs
53- return (name, csNames)
103+ csFields <- mapM consFields cs
104+ return (name, csNames, csFields)
105+ where
106+ fieldName (name, _, _) = name
107+ consFields (RecC _ fields) = return $ map fieldName fields
108+ consFields (NormalC _ fields) | length fields == 0 = return []
109+ consFields _ =
110+ fail $
111+ " Spine: only Sum-of-Products are supported, but "
112+ <> show ty
113+ <> " is not"
54114
55115consName :: (MonadFail m ) => Con -> m Name
56116consName cons =
@@ -61,7 +121,7 @@ consName cons =
61121
62122deriveTags :: Name -> String -> [Name ] -> Q [Dec ]
63123deriveTags ty suff classes = do
64- (tyName, csNames) <- reifyDatatype ty
124+ (tyName, csNames, _ ) <- reifyDatatype ty
65125 -- XXX: Quasi-quote splice does not work for case matches list
66126 let cs = map (\ name -> NormalC (addSuffix name suff) [] ) csNames
67127 v =
@@ -70,7 +130,7 @@ deriveTags ty suff classes = do
70130
71131deriveMapping :: Name -> String -> Q Exp
72132deriveMapping ty suff = do
73- (_, csNames) <- reifyDatatype ty
133+ (_, csNames, _ ) <- reifyDatatype ty
74134 -- XXX: Quasi-quote splice does not work for case matches list
75135 let
76136 matches =
@@ -87,9 +147,7 @@ deriveSpine name = do
87147 let
88148 suffix = " Spine"
89149 spineName = addSuffix name suffix
90- spineDec <- deriveTags name suffix [''Eq, ''Ord, ''Enum, ''Show]
91- -- TODO: derive Sing
92- -- TODO: derive HasField (OfSpine ...)
150+ spineDec <- deriveTags name suffix [''Eq, ''Ord, ''Enum, ''Show, ''Bounded]
93151
94152 decls <-
95153 [d |
@@ -98,3 +156,19 @@ deriveSpine name = do
98156 getSpine = $(deriveMapping name suffix)
99157 |]
100158 return $ spineDec <> decls
159+
160+ derivePlutusSpine :: Name -> Q [Dec ]
161+ derivePlutusSpine name = do
162+ decls <- deriveSpine name
163+ isDataDecls <- unstableMakeIsData name
164+
165+ (_, _, fieldsNames') <- reifyDatatype name
166+ let fieldsNames = map (map nameBase) fieldsNames'
167+ instanceDecls <-
168+ [d |
169+ instance HasPlutusSpine $(conT name) where
170+ fieldsMap =
171+ Map.fromList $ zip (allSpines @($(conT name))) fieldsNames
172+ |]
173+
174+ return $ decls <> isDataDecls <> instanceDecls
0 commit comments