-
Notifications
You must be signed in to change notification settings - Fork 502
Plinth: Add Bounded typeclass and deriveBounded #7482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| {-# LANGUAGE BlockArguments #-} | ||
| {-# LANGUAGE DataKinds #-} | ||
| {-# LANGUAGE OverloadedStrings #-} | ||
| {-# LANGUAGE TemplateHaskell #-} | ||
| {-# LANGUAGE NoImplicitPrelude #-} | ||
| {-# LANGUAGE TypeApplications #-} | ||
| {-# OPTIONS_GHC -fplugin PlutusTx.Plugin #-} | ||
| {-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:max-cse-iterations=0 #-} | ||
| {-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:max-simplifier-iterations-pir=0 #-} | ||
| {-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:max-simplifier-iterations-uplc=0 #-} | ||
| {-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:no-optimize #-} | ||
| {-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:no-simplifier-beta #-} | ||
| {-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:no-simplifier-evaluate-builtins #-} | ||
|
|
||
| module Bounded.Spec where | ||
|
|
||
| import PlutusTx | ||
| import PlutusTx.Test (goldenPirReadable) | ||
| import Test.Tasty.Extras | ||
| import PlutusTx.Bounded | ||
| import PlutusTx.Prelude | ||
| import PlutusTx.Plugin (plc) | ||
| import Data.Proxy (Proxy (..)) | ||
|
|
||
| data SomeVeryLargeEnum | ||
| = E1 | ||
| | E2 | ||
| | E3 | ||
| | E4 | ||
| | E5 | ||
| | E6 | ||
| | E7 | ||
| | E8 | ||
| | E9 | ||
| | E10 | ||
| deriveBounded ''SomeVeryLargeEnum | ||
|
|
||
| data SingleConstructor a = SingleConstructor Bool a () | ||
| deriveBounded ''SingleConstructor | ||
|
|
||
| newtype PhantomADT e = PhantomADT () | ||
| deriveBounded ''PhantomADT | ||
|
|
||
| minAndMax :: Bounded a => (a, a) | ||
| minAndMax = (minBound,maxBound) | ||
|
|
||
| compiledSomeVeryLargeEnum :: CompiledCode (SomeVeryLargeEnum, SomeVeryLargeEnum) | ||
| compiledSomeVeryLargeEnum = plc (Proxy @"compiledSomeVeryLargeEnum") minAndMax | ||
|
|
||
| compiledSingleConstructor :: CompiledCode (SingleConstructor Ordering, SingleConstructor Ordering) | ||
| compiledSingleConstructor = plc (Proxy @"compiledSingleConstructor") minAndMax | ||
|
|
||
| {- here cannot use Ordering or Either as the phantom type because of | ||
| pir compile error (unrelated to Bounded): | ||
| GHC Core to PLC plugin: Error: Error from the PIR compiler: | ||
| Error during compilation: Type bindings cannot appear in recursive let, use datatypebind instead | ||
| See https://github.com/IntersectMBO/plutus/issues/7498 | ||
| -} | ||
| compiledPhantomADT :: CompiledCode (PhantomADT Bool, PhantomADT Bool) | ||
| compiledPhantomADT = plc (Proxy @"compiledPhantomADT") minAndMax | ||
|
|
||
| tests :: TestNested | ||
| tests = | ||
| testNested | ||
| "Bounded" | ||
| [ testNestedGhc | ||
| [ goldenPirReadable "SomeVeryLargeEnum" compiledSomeVeryLargeEnum | ||
| , goldenPirReadable "SingleConstructor" compiledSingleConstructor | ||
| , goldenPirReadable "PhantomADT" compiledPhantomADT | ||
| ] | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| ### Added | ||
|
|
||
| - Bounded typeclass for Plinth same as Haskell's Bounded | ||
| - A deriveBounded mechanism to derive Bounded for certain Plinth datatypes, | ||
| similar to Haskell's `deriving stock Bounded` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| {-# LANGUAGE TemplateHaskell #-} | ||
| {-# OPTIONS_GHC -Wno-orphans #-} | ||
|
|
||
| module PlutusTx.Bounded (Bounded (..), deriveBounded) where | ||
|
|
||
| import PlutusTx.Bool | ||
| import PlutusTx.Bounded.Class | ||
| import PlutusTx.Bounded.TH | ||
| import PlutusTx.Ord | ||
|
|
||
| deriveBounded ''Bool | ||
| deriveBounded ''Ordering | ||
| deriveBounded ''() | ||
| deriveBounded ''(,) | ||
| deriveBounded ''(,,) | ||
| deriveBounded ''(,,,) | ||
| deriveBounded ''(,,,,) | ||
| deriveBounded ''(,,,,,) | ||
| deriveBounded ''(,,,,,,) | ||
| deriveBounded ''(,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,,,,,,,) | ||
| deriveBounded ''(,,,,,,,,,,,,,,,,,,,,,,,,,,) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| module PlutusTx.Bounded.Class (Bounded (..)) where | ||
|
|
||
| class Bounded a where | ||
| minBound :: a | ||
| maxBound :: a |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| {-# LANGUAGE TemplateHaskellQuotes #-} | ||
|
|
||
| module PlutusTx.Bounded.TH (Bounded (..), deriveBounded) where | ||
|
|
||
| import Control.Monad | ||
| import Data.Deriving.Internal (varTToName) | ||
| import Data.Foldable | ||
| import Language.Haskell.TH as TH | ||
| import Language.Haskell.TH.Datatype as TH | ||
| import PlutusTx.Bounded.Class | ||
| import Prelude hiding (Bounded (..)) | ||
|
|
||
| data MinMax = Min | Max | ||
|
|
||
| -- | Derive PlutusTx.Bounded typeclass for datatypes, much like `deriving stock Bounded` does for Haskell | ||
| deriveBounded :: TH.Name -> TH.Q [TH.Dec] | ||
| deriveBounded name = do | ||
| TH.DatatypeInfo | ||
| { TH.datatypeName = tyConName | ||
| , TH.datatypeInstTypes = tyVars0 | ||
| , TH.datatypeCons = cons | ||
| } <- | ||
| TH.reifyDatatype name | ||
|
|
||
| roles <- reifyRoles name | ||
|
|
||
| let | ||
| -- The purpose of the `TH.VarT . varTToName` roundtrip is to remove the kind | ||
| -- signatures attached to the type variables in `tyVars0`. Otherwise, the | ||
| -- `KindSignatures` extension would be needed whenever `length tyVars0 > 0`. | ||
| tyVars = TH.VarT . varTToName <$> tyVars0 | ||
|
|
||
| nonPhantomTyVars = VarT . varTToName . snd <$> filter ((/= PhantomR) . fst) (zip roles tyVars0) | ||
|
|
||
| instanceCxt :: TH.Cxt | ||
| instanceCxt = TH.AppT (TH.ConT ''Bounded) <$> nonPhantomTyVars | ||
|
|
||
| instanceType :: TH.Type | ||
| instanceType = TH.AppT (TH.ConT ''Bounded) $ foldl' TH.AppT (TH.ConT tyConName) tyVars | ||
|
|
||
| when (null cons) $ | ||
| fail $ | ||
| "Can't make a derived instance of `Bounded " | ||
| ++ show tyConName | ||
| ++ "`: " | ||
| ++ show tyConName | ||
| ++ "must be an enumeration type (an enumeration consists of one or more nullary, non-GADT constructors) or " | ||
| ++ show tyConName | ||
| ++ " must have precisely one constructor" | ||
|
|
||
| pure | ||
| <$> instanceD | ||
| ( pure $ case cons of | ||
| [_] -> instanceCxt -- if single constructor, add instance context | ||
| _ -> [] | ||
| ) | ||
| (pure instanceType) | ||
| [ funD 'minBound (pure $ deriveXBound Min cons) | ||
| , TH.pragInlD 'minBound TH.Inlinable TH.FunLike TH.AllPhases | ||
| , funD 'maxBound (pure $ deriveXBound Max cons) | ||
| , TH.pragInlD 'maxBound TH.Inlinable TH.FunLike TH.AllPhases | ||
| ] | ||
|
|
||
| deriveXBound :: MinMax -> [ConstructorInfo] -> Q Clause | ||
| deriveXBound minMax [ConstructorInfo {constructorName = nameL, constructorFields = fields}] = | ||
| pure | ||
| ( TH.Clause | ||
| [] | ||
| (NormalB $ foldr (const (`AppE` (VarE $ fromMinMax minMax))) (ConE nameL) fields) | ||
| [] | ||
| ) | ||
| where | ||
| fromMinMax :: MinMax -> Name | ||
| fromMinMax Min = 'minBound | ||
| fromMinMax Max = 'maxBound | ||
| deriveXBound minMax cons = do | ||
| unless allConsNoFields $ fail "Can't make a derived instance of Bounded when constructor has fields" | ||
| pure | ||
| ( TH.Clause | ||
| [] | ||
| (NormalB $ ConE $ constructorName $ fromMinMax minMax cons) | ||
| [] | ||
| ) | ||
| where | ||
| fromMinMax :: MinMax -> ([a] -> a) | ||
| fromMinMax Min = head | ||
| fromMinMax Max = last | ||
| allConsNoFields = foldl (\acc c -> acc && null (constructorFields c)) True cons |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| instance PlutusTx.Bounded.Class.Bounded GHC.Types.Ordering | ||
| where {PlutusTx.Bounded.Class.minBound = GHC.Types.LT; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.minBound #-}; | ||
| PlutusTx.Bounded.Class.maxBound = GHC.Types.GT; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.maxBound #-}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| instance PlutusTx.Bounded.Class.Bounded (Bounded.Spec.PhantomADT e_0) | ||
| where {PlutusTx.Bounded.Class.minBound = Bounded.Spec.PhantomADT PlutusTx.Bounded.Class.minBound; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.minBound #-}; | ||
| PlutusTx.Bounded.Class.maxBound = Bounded.Spec.PhantomADT PlutusTx.Bounded.Class.maxBound; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.maxBound #-}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| instance PlutusTx.Bounded.Class.Bounded a_0 => PlutusTx.Bounded.Class.Bounded (Bounded.Spec.SingleConstructor a_0) | ||
| where {PlutusTx.Bounded.Class.minBound = Bounded.Spec.SingleConstructor PlutusTx.Bounded.Class.minBound PlutusTx.Bounded.Class.minBound PlutusTx.Bounded.Class.minBound; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.minBound #-}; | ||
| PlutusTx.Bounded.Class.maxBound = Bounded.Spec.SingleConstructor PlutusTx.Bounded.Class.maxBound PlutusTx.Bounded.Class.maxBound PlutusTx.Bounded.Class.maxBound; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.maxBound #-}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| instance PlutusTx.Bounded.Class.Bounded Bounded.Spec.SomeVeryLargeEnum | ||
| where {PlutusTx.Bounded.Class.minBound = Bounded.Spec.E1; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.minBound #-}; | ||
| PlutusTx.Bounded.Class.maxBound = Bounded.Spec.E10; | ||
| {-# INLINABLE PlutusTx.Bounded.Class.maxBound #-}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| instance PlutusTx.Bounded.Class.Bounded GHC.Tuple.Prim.() | ||
| where {PlutusTx.Bounded.Class.minBound = GHC.Tuple.Prim.(); | ||
| {-# INLINABLE PlutusTx.Bounded.Class.minBound #-}; | ||
| PlutusTx.Bounded.Class.maxBound = GHC.Tuple.Prim.(); | ||
| {-# INLINABLE PlutusTx.Bounded.Class.maxBound #-}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| {-# LANGUAGE DerivingStrategies #-} | ||
| {-# LANGUAGE OverloadedStrings #-} | ||
| {-# LANGUAGE TemplateHaskell #-} | ||
| {-# LANGUAGE TypeApplications #-} | ||
|
|
||
| module Bounded.Spec (boundedTests) where | ||
|
|
||
| import PlutusTx.Bounded as Tx | ||
| import PlutusTx.Test.Golden | ||
| import Test.Tasty | ||
| import Test.Tasty.Extras | ||
| import Test.Tasty.HUnit | ||
| import Prelude hiding (Eq (..), error) | ||
| import Prelude qualified as HS (Bounded (..), Eq (..), Show (..)) | ||
|
|
||
| data SomeVeryLargeEnum | ||
| = E1 | ||
| | E2 | ||
| | E3 | ||
| | E4 | ||
| | E5 | ||
| | E6 | ||
| | E7 | ||
| | E8 | ||
| | E9 | ||
| | E10 | ||
| deriving stock (HS.Eq, HS.Bounded, HS.Show) | ||
| deriveBounded ''SomeVeryLargeEnum | ||
|
|
||
| data SingleConstructor a = SingleConstructor Bool a () | ||
| deriveBounded ''SingleConstructor | ||
|
|
||
| newtype PhantomADT e = PhantomADT () | ||
| deriving stock (HS.Eq, HS.Bounded, HS.Show) | ||
| deriveBounded ''PhantomADT | ||
|
|
||
| boundedTests :: TestTree | ||
| boundedTests = | ||
| let | ||
| in testGroup | ||
| "PlutusTx.Enum tests" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be "PlutusTx.Bounded tests"? |
||
| [ testCase "conforms to haskell" $ (Tx.minBound @SomeVeryLargeEnum, Tx.maxBound @SomeVeryLargeEnum) @?= (HS.minBound, HS.maxBound) | ||
| , testCase "phantom" $ Tx.minBound @(PhantomADT _) @?= HS.minBound | ||
| , runTestNested | ||
| ["test", "Bounded", "Golden"] | ||
| [ $(goldenCodeGen "SomeVeryLargeEnum" (deriveBounded ''SomeVeryLargeEnum)) | ||
| , $(goldenCodeGen "Unit" (deriveBounded ''())) | ||
| , $(goldenCodeGen "Ordering" (deriveBounded ''Ordering)) | ||
| , $(goldenCodeGen "SingleConstructor" (deriveBounded ''SingleConstructor)) | ||
| , $(goldenCodeGen "PhantomADT" (deriveBounded ''PhantomADT)) | ||
| ] | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,7 +34,6 @@ data SomeVeryLargeEnum | |
| deriving stock (HS.Eq, HS.Enum, HS.Bounded, HS.Show) | ||
| deriveEnum ''SomeVeryLargeEnum | ||
|
|
||
| -- we lack Tx.Bounded so we use Haskell's for the tests | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to use PlutusTx.Bounded in these tests now?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. now as we have it, is it a good time to use Plinth's Bounded then? |
||
| enumTests :: TestTree | ||
| enumTests = | ||
| let | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried this one too:
Which results in
But GHC's deriving mechanism accepts it. Perhaps this example is a bit silly because such a datatype doesn't make much sense in a strict plutus-tx? At least there is some small mismatch with GHC's deriving which we could document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's strange. @bezirg please see if you can reproduce.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that is strange because I did test it in the plugin side. I will try to reproduce.
Typeclasses seem to be finicky in compilation.