From 5e642791d393cb6be99ededb017bb2c79c8eef36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Bla=C5=BEevi=C4=87?= Date: Mon, 17 Jan 2022 11:45:24 -0500 Subject: [PATCH 1/4] Added class FieldsFromData --- Plutarch/Rec.hs | 42 ++++++++++++++++++++++++++- examples/Examples/LetRec.hs | 57 +++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/Plutarch/Rec.hs b/Plutarch/Rec.hs index 02594ad24..68c7b6c81 100644 --- a/Plutarch/Rec.hs +++ b/Plutarch/Rec.hs @@ -1,7 +1,10 @@ +{-# LANGUAGE DefaultSignatures #-} + module Plutarch.Rec ( PRecord (PRecord, getRecord), ScottEncoded, ScottEncoding, + FieldsFromData (fieldFromData), field, letrec, pletrec, @@ -12,7 +15,9 @@ import Data.Functor.Compose (Compose) import Data.Kind (Type) import Data.Monoid (Dual (Dual, getDual), Endo (Endo, appEndo), Sum (Sum, getSum)) import Numeric.Natural (Natural) -import Plutarch (PlutusType (PInner, pcon', pmatch'), phoistAcyclic, plam, punsafeCoerce, (#), (:-->)) +import Plutarch (PlutusType (PInner, pcon', pmatch'), phoistAcyclic, plam, plet, punsafeCoerce, (#), (:-->)) +import Plutarch.Bool (pif, (#==)) +import Plutarch.Builtin (PAsData, PBuiltinList, PData, pasConstr, pforgetData, pfstBuiltin, psndBuiltin) import Plutarch.Internal ( PType, RawTerm (RApply, RLamAbs, RVar), @@ -20,6 +25,8 @@ import Plutarch.Internal ( TermResult (TermResult, getDeps, getTerm), mapTerm, ) +import Plutarch.List (phead, ptail) +import Plutarch.Trace (ptraceError) import qualified Rank2 newtype PRecord r s = PRecord {getRecord :: r (Term s)} @@ -100,6 +107,39 @@ variables = Rank2.cotraverse var id , getDeps = [] } +newtype FocusFromData s a b = FocusFromData {getFocus :: Term s (PAsData a :--> PAsData b)} + +class FieldsFromData r where + -- | Converts a Haskell field function to a function term that extracts the 'Data' encoding of the field from the + -- encoding of the whole record. + fieldFromData :: (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) t) + -> Term s (PAsData (PRecord r) :--> PAsData t) + default fieldFromData :: (Rank2.Distributive r, Rank2.Traversable r) + => (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) t) + -> Term s (PAsData (PRecord r) :--> PAsData t) + fieldFromData f = getFocus (f fieldFoci) + +fieldFoci :: forall r s. (Rank2.Distributive r, Rank2.Traversable r) => r (FocusFromData s (PRecord r)) +fieldFoci = Rank2.cotraverse focus id + where + focus :: (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) a) -> FocusFromData s (PRecord r) a + focus ref = ref ordered + ordered :: r (FocusFromData s (PRecord r)) + ordered = evalState (Rank2.traverse next $ initial @r) id + next :: f a -> State (Term s (PBuiltinList PData) -> Term s (PBuiltinList PData)) (FocusFromData s (PRecord r) a) + next _ = do + rest <- get + put ((ptail #) . rest) + return $ + FocusFromData $ punsafeCoerce $ fromFields ((phead #) . rest) + fromFields :: (Term s (PBuiltinList PData) -> Term s a) -> Term s (PAsData (PRecord r) :--> a) + fromFields f = plam $ \d-> + plet (pasConstr # pforgetData d) $ \constr -> + pif + (pfstBuiltin # constr #== 0) + (f $ psndBuiltin # constr) + (ptraceError "fieldFromData expects a sole constructor") + initial :: Rank2.Distributive r => r (Compose Maybe (Term s)) initial = Rank2.distribute Nothing diff --git a/examples/Examples/LetRec.hs b/examples/Examples/LetRec.hs index 890e825fc..273260fb6 100644 --- a/examples/Examples/LetRec.hs +++ b/examples/Examples/LetRec.hs @@ -2,13 +2,16 @@ module Examples.LetRec (tests) where -import Plutarch (pcon', pmatch', printTerm) +import Plutarch (pcon', pmatch', printTerm, punsafeBuiltin, punsafeCoerce) import Plutarch.Bool (PBool (PFalse, PTrue), pif, (#==)) +import Plutarch.Builtin (PAsData, PBuiltinList (PNil), PData, PIsData, pasConstr, pdata, pforgetData, pfromData, pfstBuiltin, psndBuiltin) import Plutarch.Integer (PInteger) +import Plutarch.List (phead, ptail) import Plutarch.Prelude -import Plutarch.Rec (PRecord (PRecord), ScottEncoded, ScottEncoding, field, letrec) +import Plutarch.Rec (FieldsFromData, PRecord (PRecord), ScottEncoded, ScottEncoding, field, fieldFromData, letrec) import Plutarch.Rec.TH (deriveAll) -import Plutarch.String (PString) +import Plutarch.String (PString, pdecodeUtf8, pencodeUtf8) +import qualified PlutusCore as PLC import qualified Rank2.TH import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) @@ -30,6 +33,45 @@ type instance ScottEncoded EvenOdd a = (PInteger :--> PBool) :--> (PInteger :--> $(Rank2.TH.deriveAll ''EvenOdd) $(deriveAll ''SampleRecord) -- also autoderives the @type instance ScottEncoded@ +instance FieldsFromData SampleRecord + +instance PIsData (PRecord SampleRecord) where + pfromData = strictRecordFromData + pdata = recordData + +--recordData :: (forall t. Term s (ScottEncoding SampleRecord t)) -> Term s (PAsData (PRecord SampleRecord)) +recordData :: forall s. Term s (PRecord SampleRecord) -> Term s (PAsData (PRecord SampleRecord)) +recordData r = pmatch r $ \(PRecord SampleRecord{sampleBool, sampleInt, sampleString})-> + punsafeBuiltin PLC.ConstrData # (0 :: Term s PInteger) #$ + pconsBuiltin # pforgetData (pdata sampleBool) #$ + pconsBuiltin # pforgetData (pdata sampleInt) #$ + pconsBuiltin # pforgetData (pdata $ pencodeUtf8 # sampleString) #$ + pcon PNil + +pconsBuiltin :: Term s (a :--> PBuiltinList a :--> PBuiltinList a) +pconsBuiltin = phoistAcyclic $ pforce $ punsafeBuiltin PLC.MkCons + +strictRecordFromData :: Term s (PAsData (PRecord SampleRecord)) -> Term s (PRecord SampleRecord) +strictRecordFromData d = + plet (pasConstr # pforgetData d) $ \constr -> + pif + (pfstBuiltin # constr #== 0) + (fillInFields #$ psndBuiltin # constr) + perror + where + fillInFields :: Term s (PBuiltinList PData :--> PRecord SampleRecord) + fillInFields = plam $ \bis -> + plet (phead # bis) $ \b -> + plet (ptail # bis) $ \is -> + plet (phead # is) $ \i -> + plet (phead #$ ptail # is) $ \s -> + pcon + ( PRecord $ + SampleRecord + (pfromData $ punsafeCoerce b) + (pfromData $ punsafeCoerce i) + (pdecodeUtf8 #$ pfromData $ punsafeCoerce s) + ) sampleRecord :: Term (s :: S) (ScottEncoding SampleRecord (t :: PType)) sampleRecord = @@ -61,6 +103,9 @@ evenOdd = letrec evenOddRecursion , odd = plam $ \n -> pif (n #== 0) (pcon PFalse) (even #$ n - 1) } +sampleData :: Term s (PAsData (PRecord SampleRecord)) +sampleData = pdata (punsafeCoerce sampleRecord) + tests :: HasTester => TestTree tests = testGroup @@ -83,4 +128,10 @@ tests = , testCase "even 4" $ equal' (evenOdd # field even # (4 :: Term s PInteger)) "(program 1.0.0 True)" , testCase "even 5" $ equal' (evenOdd # field even # (5 :: Term s PInteger)) "(program 1.0.0 False)" ] + , testGroup + "Data" + [ testCase "pdata" $ printTerm sampleData @?= "(program 1.0.0 ((\\i0 -> i1 False 6 \"Salut, Monde!\") (\\i0 -> \\i0 -> \\i0 -> constrData 0 (force mkCons ((\\i0 -> constrData (force ifThenElse i1 1 0) [ ]) i3) (force mkCons (iData i2) (force mkCons (bData (encodeUtf8 i1)) [ ]))))))" + , testCase "fieldFromData term" $ (printTerm $ plam $ \dat-> plam pfromData #$ fieldFromData sampleInt # dat) @?= "(program 1.0.0 (\\i0 -> unIData ((\\i0 -> (\\i0 -> force (force ifThenElse (equalsInteger (force (force fstPair) i1) 0) (delay (force headList (force tailList (force (force sndPair) i1)))) (delay error))) (unConstrData i1)) i1)))" + , testCase "fieldFromData value" $ equal' (fieldFromData sampleInt # sampleData) "(program 1.0.0 #06)" + ] ] From 46bab7a004ff354511d6142634eca9745b96bc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Bla=C5=BEevi=C4=87?= Date: Mon, 17 Jan 2022 14:21:22 -0500 Subject: [PATCH 2/4] Added recordFromFieldReaders --- Plutarch/Rec.hs | 50 ++++++++++++++++++++++++++++--------- examples/Examples/LetRec.hs | 35 ++++++++------------------ 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/Plutarch/Rec.hs b/Plutarch/Rec.hs index 68c7b6c81..2d0c80b84 100644 --- a/Plutarch/Rec.hs +++ b/Plutarch/Rec.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DefaultSignatures #-} module Plutarch.Rec ( + DataReader(DataReader, readData), PRecord (PRecord, getRecord), ScottEncoded, ScottEncoding, @@ -8,14 +9,15 @@ module Plutarch.Rec ( field, letrec, pletrec, + recordFromFieldReaders, ) where import Control.Monad.Trans.State.Lazy (State, evalState, get, put) -import Data.Functor.Compose (Compose) +import Data.Functor.Compose (Compose(Compose, getCompose)) import Data.Kind (Type) import Data.Monoid (Dual (Dual, getDual), Endo (Endo, appEndo), Sum (Sum, getSum)) import Numeric.Natural (Natural) -import Plutarch (PlutusType (PInner, pcon', pmatch'), phoistAcyclic, plam, plet, punsafeCoerce, (#), (:-->)) +import Plutarch (PlutusType (PInner, pcon', pmatch'), pcon, phoistAcyclic, plam, plet, punsafeCoerce, (#), (:-->)) import Plutarch.Bool (pif, (#==)) import Plutarch.Builtin (PAsData, PBuiltinList, PData, pasConstr, pforgetData, pfstBuiltin, psndBuiltin) import Plutarch.Internal ( @@ -107,7 +109,21 @@ variables = Rank2.cotraverse var id , getDeps = [] } +newtype DataReader s a = DataReader {readData :: Term s (PAsData a) -> Term s a} newtype FocusFromData s a b = FocusFromData {getFocus :: Term s (PAsData a :--> PAsData b)} +newtype FocusFromDataList s a = FocusFromDataList {getItem :: Term s (PBuiltinList PData) -> Term s (PAsData a)} + +-- | Converts a record of field DataReaders to a DataReader of the whole +-- record. If you only need a single field or two, use `fieldFromData` +-- instead. +recordFromFieldReaders :: forall r s. (Rank2.Apply r, Rank2.Distributive r, Rank2.Traversable r, FieldsFromData r) + => r (DataReader s) -> DataReader s (PRecord r) +recordFromFieldReaders reader = DataReader $ verifySoleConstructor readRecord + where + readRecord :: Term s (PBuiltinList PData) -> Term s (PRecord r) + readRecord dat = pcon $ PRecord $ Rank2.liftA2 (flip readData . getCompose) (fields dat) reader + fields :: Term s (PBuiltinList PData) -> r (Compose (Term s) PAsData) + fields bis = (\f-> Compose $ getItem f bis) Rank2.<$> fieldListFoci class FieldsFromData r where -- | Converts a Haskell field function to a function term that extracts the 'Data' encoding of the field from the @@ -125,20 +141,30 @@ fieldFoci = Rank2.cotraverse focus id focus :: (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) a) -> FocusFromData s (PRecord r) a focus ref = ref ordered ordered :: r (FocusFromData s (PRecord r)) + ordered = fieldsFromRecord Rank2.<$> fieldListFoci + fieldsFromRecord :: FocusFromDataList s a -> FocusFromData s (PRecord r) a + fieldsFromRecord (FocusFromDataList f) = FocusFromData $ plam $ verifySoleConstructor f + +fieldListFoci :: forall r s. (Rank2.Distributive r, Rank2.Traversable r) => r (FocusFromDataList s) +fieldListFoci = Rank2.cotraverse focus id + where + focus :: (r (FocusFromDataList s) -> FocusFromDataList s a) -> FocusFromDataList s a + focus ref = ref ordered + ordered :: r (FocusFromDataList s) ordered = evalState (Rank2.traverse next $ initial @r) id - next :: f a -> State (Term s (PBuiltinList PData) -> Term s (PBuiltinList PData)) (FocusFromData s (PRecord r) a) + next :: f a -> State (Term s (PBuiltinList PData) -> Term s (PBuiltinList PData)) (FocusFromDataList s a) next _ = do rest <- get put ((ptail #) . rest) - return $ - FocusFromData $ punsafeCoerce $ fromFields ((phead #) . rest) - fromFields :: (Term s (PBuiltinList PData) -> Term s a) -> Term s (PAsData (PRecord r) :--> a) - fromFields f = plam $ \d-> - plet (pasConstr # pforgetData d) $ \constr -> - pif - (pfstBuiltin # constr #== 0) - (f $ psndBuiltin # constr) - (ptraceError "fieldFromData expects a sole constructor") + return $ FocusFromDataList (punsafeCoerce . (phead #) . rest) + +verifySoleConstructor :: (Term s (PBuiltinList PData) -> Term s a) -> (Term s (PAsData (PRecord r)) -> Term s a) +verifySoleConstructor f d = + plet (pasConstr # pforgetData d) $ \constr -> + pif + (pfstBuiltin # constr #== 0) + (f $ psndBuiltin # constr) + (ptraceError "verifySoleConstructor failed") initial :: Rank2.Distributive r => r (Compose Maybe (Term s)) initial = Rank2.distribute Nothing diff --git a/examples/Examples/LetRec.hs b/examples/Examples/LetRec.hs index 273260fb6..edf279920 100644 --- a/examples/Examples/LetRec.hs +++ b/examples/Examples/LetRec.hs @@ -4,11 +4,11 @@ module Examples.LetRec (tests) where import Plutarch (pcon', pmatch', printTerm, punsafeBuiltin, punsafeCoerce) import Plutarch.Bool (PBool (PFalse, PTrue), pif, (#==)) -import Plutarch.Builtin (PAsData, PBuiltinList (PNil), PData, PIsData, pasConstr, pdata, pforgetData, pfromData, pfstBuiltin, psndBuiltin) +import Plutarch.Builtin (PAsData, PBuiltinList (PNil), PIsData, pdata, pforgetData, pfromData) import Plutarch.Integer (PInteger) -import Plutarch.List (phead, ptail) import Plutarch.Prelude -import Plutarch.Rec (FieldsFromData, PRecord (PRecord), ScottEncoded, ScottEncoding, field, fieldFromData, letrec) +import Plutarch.Rec (DataReader(DataReader, readData), FieldsFromData, PRecord (PRecord), ScottEncoded, ScottEncoding, + field, fieldFromData, letrec, recordFromFieldReaders) import Plutarch.Rec.TH (deriveAll) import Plutarch.String (PString, pdecodeUtf8, pencodeUtf8) import qualified PlutusCore as PLC @@ -36,7 +36,7 @@ $(deriveAll ''SampleRecord) -- also autoderives the @type instance ScottEncoded@ instance FieldsFromData SampleRecord instance PIsData (PRecord SampleRecord) where - pfromData = strictRecordFromData + pfromData = readData (recordFromFieldReaders sampleReader) pdata = recordData --recordData :: (forall t. Term s (ScottEncoding SampleRecord t)) -> Term s (PAsData (PRecord SampleRecord)) @@ -51,27 +51,11 @@ recordData r = pmatch r $ \(PRecord SampleRecord{sampleBool, sampleInt, sampleSt pconsBuiltin :: Term s (a :--> PBuiltinList a :--> PBuiltinList a) pconsBuiltin = phoistAcyclic $ pforce $ punsafeBuiltin PLC.MkCons -strictRecordFromData :: Term s (PAsData (PRecord SampleRecord)) -> Term s (PRecord SampleRecord) -strictRecordFromData d = - plet (pasConstr # pforgetData d) $ \constr -> - pif - (pfstBuiltin # constr #== 0) - (fillInFields #$ psndBuiltin # constr) - perror - where - fillInFields :: Term s (PBuiltinList PData :--> PRecord SampleRecord) - fillInFields = plam $ \bis -> - plet (phead # bis) $ \b -> - plet (ptail # bis) $ \is -> - plet (phead # is) $ \i -> - plet (phead #$ ptail # is) $ \s -> - pcon - ( PRecord $ - SampleRecord - (pfromData $ punsafeCoerce b) - (pfromData $ punsafeCoerce i) - (pdecodeUtf8 #$ pfromData $ punsafeCoerce s) - ) +sampleReader :: SampleRecord (DataReader s) +sampleReader = SampleRecord{ + sampleBool = DataReader pfromData, + sampleInt = DataReader pfromData, + sampleString = DataReader $ \d-> pdecodeUtf8 #$ pfromData $ punsafeCoerce d} sampleRecord :: Term (s :: S) (ScottEncoding SampleRecord (t :: PType)) sampleRecord = @@ -133,5 +117,6 @@ tests = [ testCase "pdata" $ printTerm sampleData @?= "(program 1.0.0 ((\\i0 -> i1 False 6 \"Salut, Monde!\") (\\i0 -> \\i0 -> \\i0 -> constrData 0 (force mkCons ((\\i0 -> constrData (force ifThenElse i1 1 0) [ ]) i3) (force mkCons (iData i2) (force mkCons (bData (encodeUtf8 i1)) [ ]))))))" , testCase "fieldFromData term" $ (printTerm $ plam $ \dat-> plam pfromData #$ fieldFromData sampleInt # dat) @?= "(program 1.0.0 (\\i0 -> unIData ((\\i0 -> (\\i0 -> force (force ifThenElse (equalsInteger (force (force fstPair) i1) 0) (delay (force headList (force tailList (force (force sndPair) i1)))) (delay error))) (unConstrData i1)) i1)))" , testCase "fieldFromData value" $ equal' (fieldFromData sampleInt # sampleData) "(program 1.0.0 #06)" + , testCase "pfromData" $ (printTerm $ plam $ \d-> punsafeCoerce (pfromData d :: Term _ (PRecord SampleRecord)) # field sampleInt) @?= "(program 1.0.0 ((\\i0 -> (\\i0 -> (\\i0 -> (\\i0 -> \\i0 -> (\\i0 -> force (force ifThenElse (equalsInteger (i3 i1) 0) (delay (\\i0 -> i1 ((\\i0 -> equalsInteger (i5 (unConstrData i1)) 1) (i5 (i7 i2))) (unIData (i5 (i6 (i7 i2)))) (decodeUtf8 (unBData (i5 (i6 (i6 (i7 i2)))))))) (delay error))) (unConstrData i1) (\\i0 -> \\i0 -> \\i0 -> i2)) (force (force fstPair))) (force headList)) (force tailList)) (force (force sndPair))))" ] ] From 1cc5c4d27931898717754c0cfa1b1670cf2f9013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Bla=C5=BEevi=C4=87?= Date: Mon, 17 Jan 2022 15:14:55 -0500 Subject: [PATCH 3/4] Reorganized the definitions --- Plutarch/Rec.hs | 71 +++++++++++++++++++------------------ examples/Examples/LetRec.hs | 5 ++- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/Plutarch/Rec.hs b/Plutarch/Rec.hs index 2d0c80b84..c3fafc958 100644 --- a/Plutarch/Rec.hs +++ b/Plutarch/Rec.hs @@ -5,8 +5,9 @@ module Plutarch.Rec ( PRecord (PRecord, getRecord), ScottEncoded, ScottEncoding, - FieldsFromData (fieldFromData), + RecordFromData (fieldFoci, fieldListFoci), field, + fieldFromData, letrec, pletrec, recordFromFieldReaders, @@ -116,7 +117,7 @@ newtype FocusFromDataList s a = FocusFromDataList {getItem :: Term s (PBuiltinLi -- | Converts a record of field DataReaders to a DataReader of the whole -- record. If you only need a single field or two, use `fieldFromData` -- instead. -recordFromFieldReaders :: forall r s. (Rank2.Apply r, Rank2.Distributive r, Rank2.Traversable r, FieldsFromData r) +recordFromFieldReaders :: forall r s. (Rank2.Apply r, RecordFromData r) => r (DataReader s) -> DataReader s (PRecord r) recordFromFieldReaders reader = DataReader $ verifySoleConstructor readRecord where @@ -125,38 +126,40 @@ recordFromFieldReaders reader = DataReader $ verifySoleConstructor readRecord fields :: Term s (PBuiltinList PData) -> r (Compose (Term s) PAsData) fields bis = (\f-> Compose $ getItem f bis) Rank2.<$> fieldListFoci -class FieldsFromData r where - -- | Converts a Haskell field function to a function term that extracts the 'Data' encoding of the field from the - -- encoding of the whole record. - fieldFromData :: (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) t) - -> Term s (PAsData (PRecord r) :--> PAsData t) - default fieldFromData :: (Rank2.Distributive r, Rank2.Traversable r) - => (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) t) - -> Term s (PAsData (PRecord r) :--> PAsData t) - fieldFromData f = getFocus (f fieldFoci) - -fieldFoci :: forall r s. (Rank2.Distributive r, Rank2.Traversable r) => r (FocusFromData s (PRecord r)) -fieldFoci = Rank2.cotraverse focus id - where - focus :: (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) a) -> FocusFromData s (PRecord r) a - focus ref = ref ordered - ordered :: r (FocusFromData s (PRecord r)) - ordered = fieldsFromRecord Rank2.<$> fieldListFoci - fieldsFromRecord :: FocusFromDataList s a -> FocusFromData s (PRecord r) a - fieldsFromRecord (FocusFromDataList f) = FocusFromData $ plam $ verifySoleConstructor f - -fieldListFoci :: forall r s. (Rank2.Distributive r, Rank2.Traversable r) => r (FocusFromDataList s) -fieldListFoci = Rank2.cotraverse focus id - where - focus :: (r (FocusFromDataList s) -> FocusFromDataList s a) -> FocusFromDataList s a - focus ref = ref ordered - ordered :: r (FocusFromDataList s) - ordered = evalState (Rank2.traverse next $ initial @r) id - next :: f a -> State (Term s (PBuiltinList PData) -> Term s (PBuiltinList PData)) (FocusFromDataList s a) - next _ = do - rest <- get - put ((ptail #) . rest) - return $ FocusFromDataList (punsafeCoerce . (phead #) . rest) +-- | Converts a Haskell field function to a function term that extracts the 'Data' encoding of the field from the +-- encoding of the whole record. +fieldFromData :: RecordFromData r + => (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) t) + -> Term s (PAsData (PRecord r) :--> PAsData t) +fieldFromData f = getFocus (f fieldFoci) + +-- | Instances of this class must know how to focus on individual fields of +-- the data-encoded record. If the declared order of the record fields doesn't +-- match the encoding order, you must override the method defaults. +class (Rank2.Distributive r, Rank2.Traversable r) => RecordFromData r where + -- | Given the encoding of the whole record, every field focuses on its own encoding. + fieldFoci :: r (FocusFromData s (PRecord r)) + -- | Given the encoding of the list of all fields, every field focuses on its own encoding. + fieldListFoci :: r (FocusFromDataList s) + fieldFoci = Rank2.cotraverse focus id + where + focus :: (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) a) -> FocusFromData s (PRecord r) a + focus ref = ref foci + foci :: r (FocusFromData s (PRecord r)) + foci = fieldsFromRecord Rank2.<$> fieldListFoci + fieldsFromRecord :: FocusFromDataList s a -> FocusFromData s (PRecord r) a + fieldsFromRecord (FocusFromDataList f) = FocusFromData $ plam $ verifySoleConstructor f + fieldListFoci = Rank2.cotraverse focus id + where + focus :: (r (FocusFromDataList s) -> FocusFromDataList s a) -> FocusFromDataList s a + focus ref = ref foci + foci :: r (FocusFromDataList s) + foci = evalState (Rank2.traverse next $ initial @r) id + next :: f a -> State (Term s (PBuiltinList PData) -> Term s (PBuiltinList PData)) (FocusFromDataList s a) + next _ = do + rest <- get + put ((ptail #) . rest) + return $ FocusFromDataList (punsafeCoerce . (phead #) . rest) verifySoleConstructor :: (Term s (PBuiltinList PData) -> Term s a) -> (Term s (PAsData (PRecord r)) -> Term s a) verifySoleConstructor f d = diff --git a/examples/Examples/LetRec.hs b/examples/Examples/LetRec.hs index edf279920..8981844b0 100644 --- a/examples/Examples/LetRec.hs +++ b/examples/Examples/LetRec.hs @@ -7,7 +7,7 @@ import Plutarch.Bool (PBool (PFalse, PTrue), pif, (#==)) import Plutarch.Builtin (PAsData, PBuiltinList (PNil), PIsData, pdata, pforgetData, pfromData) import Plutarch.Integer (PInteger) import Plutarch.Prelude -import Plutarch.Rec (DataReader(DataReader, readData), FieldsFromData, PRecord (PRecord), ScottEncoded, ScottEncoding, +import Plutarch.Rec (DataReader(DataReader, readData), RecordFromData, PRecord (PRecord), ScottEncoded, ScottEncoding, field, fieldFromData, letrec, recordFromFieldReaders) import Plutarch.Rec.TH (deriveAll) import Plutarch.String (PString, pdecodeUtf8, pencodeUtf8) @@ -33,13 +33,12 @@ type instance ScottEncoded EvenOdd a = (PInteger :--> PBool) :--> (PInteger :--> $(Rank2.TH.deriveAll ''EvenOdd) $(deriveAll ''SampleRecord) -- also autoderives the @type instance ScottEncoded@ -instance FieldsFromData SampleRecord +instance RecordFromData SampleRecord instance PIsData (PRecord SampleRecord) where pfromData = readData (recordFromFieldReaders sampleReader) pdata = recordData ---recordData :: (forall t. Term s (ScottEncoding SampleRecord t)) -> Term s (PAsData (PRecord SampleRecord)) recordData :: forall s. Term s (PRecord SampleRecord) -> Term s (PAsData (PRecord SampleRecord)) recordData r = pmatch r $ \(PRecord SampleRecord{sampleBool, sampleInt, sampleString})-> punsafeBuiltin PLC.ConstrData # (0 :: Term s PInteger) #$ From cf139a0c4de862cc1dd1e3c166b769d300426c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Bla=C5=BEevi=C4=87?= Date: Mon, 17 Jan 2022 15:20:24 -0500 Subject: [PATCH 4/4] Formatting --- Plutarch/Rec.hs | 41 ++++++++++++++++++++++--------------- examples/Examples/LetRec.hs | 39 ++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/Plutarch/Rec.hs b/Plutarch/Rec.hs index c3fafc958..0f34c5b01 100644 --- a/Plutarch/Rec.hs +++ b/Plutarch/Rec.hs @@ -1,7 +1,7 @@ {-# LANGUAGE DefaultSignatures #-} module Plutarch.Rec ( - DataReader(DataReader, readData), + DataReader (DataReader, readData), PRecord (PRecord, getRecord), ScottEncoded, ScottEncoding, @@ -14,7 +14,7 @@ module Plutarch.Rec ( ) where import Control.Monad.Trans.State.Lazy (State, evalState, get, put) -import Data.Functor.Compose (Compose(Compose, getCompose)) +import Data.Functor.Compose (Compose (Compose, getCompose)) import Data.Kind (Type) import Data.Monoid (Dual (Dual, getDual), Endo (Endo, appEndo), Sum (Sum, getSum)) import Numeric.Natural (Natural) @@ -114,33 +114,42 @@ newtype DataReader s a = DataReader {readData :: Term s (PAsData a) -> Term s a} newtype FocusFromData s a b = FocusFromData {getFocus :: Term s (PAsData a :--> PAsData b)} newtype FocusFromDataList s a = FocusFromDataList {getItem :: Term s (PBuiltinList PData) -> Term s (PAsData a)} --- | Converts a record of field DataReaders to a DataReader of the whole --- record. If you only need a single field or two, use `fieldFromData` --- instead. -recordFromFieldReaders :: forall r s. (Rank2.Apply r, RecordFromData r) - => r (DataReader s) -> DataReader s (PRecord r) +{- | Converts a record of field DataReaders to a DataReader of the whole + record. If you only need a single field or two, use `fieldFromData` + instead. +-} +recordFromFieldReaders :: + forall r s. + (Rank2.Apply r, RecordFromData r) => + r (DataReader s) -> + DataReader s (PRecord r) recordFromFieldReaders reader = DataReader $ verifySoleConstructor readRecord where readRecord :: Term s (PBuiltinList PData) -> Term s (PRecord r) readRecord dat = pcon $ PRecord $ Rank2.liftA2 (flip readData . getCompose) (fields dat) reader fields :: Term s (PBuiltinList PData) -> r (Compose (Term s) PAsData) - fields bis = (\f-> Compose $ getItem f bis) Rank2.<$> fieldListFoci + fields bis = (\f -> Compose $ getItem f bis) Rank2.<$> fieldListFoci --- | Converts a Haskell field function to a function term that extracts the 'Data' encoding of the field from the --- encoding of the whole record. -fieldFromData :: RecordFromData r - => (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) t) - -> Term s (PAsData (PRecord r) :--> PAsData t) +{- | Converts a Haskell field function to a function term that extracts the 'Data' encoding of the field from the + encoding of the whole record. +-} +fieldFromData :: + RecordFromData r => + (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) t) -> + Term s (PAsData (PRecord r) :--> PAsData t) fieldFromData f = getFocus (f fieldFoci) --- | Instances of this class must know how to focus on individual fields of --- the data-encoded record. If the declared order of the record fields doesn't --- match the encoding order, you must override the method defaults. +{- | Instances of this class must know how to focus on individual fields of + the data-encoded record. If the declared order of the record fields doesn't + match the encoding order, you must override the method defaults. +-} class (Rank2.Distributive r, Rank2.Traversable r) => RecordFromData r where -- | Given the encoding of the whole record, every field focuses on its own encoding. fieldFoci :: r (FocusFromData s (PRecord r)) + -- | Given the encoding of the list of all fields, every field focuses on its own encoding. fieldListFoci :: r (FocusFromDataList s) + fieldFoci = Rank2.cotraverse focus id where focus :: (r (FocusFromData s (PRecord r)) -> FocusFromData s (PRecord r) a) -> FocusFromData s (PRecord r) a diff --git a/examples/Examples/LetRec.hs b/examples/Examples/LetRec.hs index 8981844b0..1bc37d7c7 100644 --- a/examples/Examples/LetRec.hs +++ b/examples/Examples/LetRec.hs @@ -7,8 +7,17 @@ import Plutarch.Bool (PBool (PFalse, PTrue), pif, (#==)) import Plutarch.Builtin (PAsData, PBuiltinList (PNil), PIsData, pdata, pforgetData, pfromData) import Plutarch.Integer (PInteger) import Plutarch.Prelude -import Plutarch.Rec (DataReader(DataReader, readData), RecordFromData, PRecord (PRecord), ScottEncoded, ScottEncoding, - field, fieldFromData, letrec, recordFromFieldReaders) +import Plutarch.Rec ( + DataReader (DataReader, readData), + PRecord (PRecord), + RecordFromData, + ScottEncoded, + ScottEncoding, + field, + fieldFromData, + letrec, + recordFromFieldReaders, + ) import Plutarch.Rec.TH (deriveAll) import Plutarch.String (PString, pdecodeUtf8, pencodeUtf8) import qualified PlutusCore as PLC @@ -40,21 +49,23 @@ instance PIsData (PRecord SampleRecord) where pdata = recordData recordData :: forall s. Term s (PRecord SampleRecord) -> Term s (PAsData (PRecord SampleRecord)) -recordData r = pmatch r $ \(PRecord SampleRecord{sampleBool, sampleInt, sampleString})-> - punsafeBuiltin PLC.ConstrData # (0 :: Term s PInteger) #$ - pconsBuiltin # pforgetData (pdata sampleBool) #$ - pconsBuiltin # pforgetData (pdata sampleInt) #$ - pconsBuiltin # pforgetData (pdata $ pencodeUtf8 # sampleString) #$ - pcon PNil +recordData r = pmatch r $ \(PRecord SampleRecord {sampleBool, sampleInt, sampleString}) -> + punsafeBuiltin PLC.ConstrData # (0 :: Term s PInteger) + #$ pconsBuiltin # pforgetData (pdata sampleBool) + #$ pconsBuiltin # pforgetData (pdata sampleInt) + #$ pconsBuiltin # pforgetData (pdata $ pencodeUtf8 # sampleString) + #$ pcon PNil pconsBuiltin :: Term s (a :--> PBuiltinList a :--> PBuiltinList a) pconsBuiltin = phoistAcyclic $ pforce $ punsafeBuiltin PLC.MkCons sampleReader :: SampleRecord (DataReader s) -sampleReader = SampleRecord{ - sampleBool = DataReader pfromData, - sampleInt = DataReader pfromData, - sampleString = DataReader $ \d-> pdecodeUtf8 #$ pfromData $ punsafeCoerce d} +sampleReader = + SampleRecord + { sampleBool = DataReader pfromData + , sampleInt = DataReader pfromData + , sampleString = DataReader $ \d -> pdecodeUtf8 #$ pfromData $ punsafeCoerce d + } sampleRecord :: Term (s :: S) (ScottEncoding SampleRecord (t :: PType)) sampleRecord = @@ -114,8 +125,8 @@ tests = , testGroup "Data" [ testCase "pdata" $ printTerm sampleData @?= "(program 1.0.0 ((\\i0 -> i1 False 6 \"Salut, Monde!\") (\\i0 -> \\i0 -> \\i0 -> constrData 0 (force mkCons ((\\i0 -> constrData (force ifThenElse i1 1 0) [ ]) i3) (force mkCons (iData i2) (force mkCons (bData (encodeUtf8 i1)) [ ]))))))" - , testCase "fieldFromData term" $ (printTerm $ plam $ \dat-> plam pfromData #$ fieldFromData sampleInt # dat) @?= "(program 1.0.0 (\\i0 -> unIData ((\\i0 -> (\\i0 -> force (force ifThenElse (equalsInteger (force (force fstPair) i1) 0) (delay (force headList (force tailList (force (force sndPair) i1)))) (delay error))) (unConstrData i1)) i1)))" + , testCase "fieldFromData term" $ (printTerm $ plam $ \dat -> plam pfromData #$ fieldFromData sampleInt # dat) @?= "(program 1.0.0 (\\i0 -> unIData ((\\i0 -> (\\i0 -> force (force ifThenElse (equalsInteger (force (force fstPair) i1) 0) (delay (force headList (force tailList (force (force sndPair) i1)))) (delay error))) (unConstrData i1)) i1)))" , testCase "fieldFromData value" $ equal' (fieldFromData sampleInt # sampleData) "(program 1.0.0 #06)" - , testCase "pfromData" $ (printTerm $ plam $ \d-> punsafeCoerce (pfromData d :: Term _ (PRecord SampleRecord)) # field sampleInt) @?= "(program 1.0.0 ((\\i0 -> (\\i0 -> (\\i0 -> (\\i0 -> \\i0 -> (\\i0 -> force (force ifThenElse (equalsInteger (i3 i1) 0) (delay (\\i0 -> i1 ((\\i0 -> equalsInteger (i5 (unConstrData i1)) 1) (i5 (i7 i2))) (unIData (i5 (i6 (i7 i2)))) (decodeUtf8 (unBData (i5 (i6 (i6 (i7 i2)))))))) (delay error))) (unConstrData i1) (\\i0 -> \\i0 -> \\i0 -> i2)) (force (force fstPair))) (force headList)) (force tailList)) (force (force sndPair))))" + , testCase "pfromData" $ (printTerm $ plam $ \d -> punsafeCoerce (pfromData d :: Term _ (PRecord SampleRecord)) # field sampleInt) @?= "(program 1.0.0 ((\\i0 -> (\\i0 -> (\\i0 -> (\\i0 -> \\i0 -> (\\i0 -> force (force ifThenElse (equalsInteger (i3 i1) 0) (delay (\\i0 -> i1 ((\\i0 -> equalsInteger (i5 (unConstrData i1)) 1) (i5 (i7 i2))) (unIData (i5 (i6 (i7 i2)))) (decodeUtf8 (unBData (i5 (i6 (i6 (i7 i2)))))))) (delay error))) (unConstrData i1) (\\i0 -> \\i0 -> \\i0 -> i2)) (force (force fstPair))) (force headList)) (force tailList)) (force (force sndPair))))" ] ]