Skip to content

Commit

Permalink
Merge pull request #128 from input-output-hk/newhoggy/improved-featur…
Browse files Browse the repository at this point in the history
…e-ergonomics

Improved feature ergonomics
  • Loading branch information
newhoggy authored Jul 25, 2023
2 parents 396e6bd + 3ef0c97 commit 2b6d95c
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 126 deletions.
2 changes: 2 additions & 0 deletions cardano-api/cardano-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ library internal
Cardano.Api.Eras
Cardano.Api.Error
Cardano.Api.Feature
Cardano.Api.Feature.ConwayEraOnwards
Cardano.Api.Feature.ShelleyToBabbageEra
Cardano.Api.Fees
Cardano.Api.Genesis
Cardano.Api.GenesisParameters
Expand Down
27 changes: 19 additions & 8 deletions cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}

{-# OPTIONS_GHC -Wno-deprecations #-}

module Test.Gen.Cardano.Api.Typed
( genFeatureValueInEra
( genFeaturedInEra
, genMaybeFeaturedInEra

, genAddressByron
, genAddressInEra
Expand Down Expand Up @@ -122,7 +125,6 @@ import Cardano.Api hiding (txIns)
import qualified Cardano.Api as Api
import Cardano.Api.Byron (KeyWitness (ByronKeyWitness),
WitnessNetworkIdOrByronAddress (..))
import Cardano.Api.Feature
import Cardano.Api.Script (scriptInEraToRefScript)
import Cardano.Api.Shelley (GovernancePoll (..), GovernancePollAnswer (..), Hash (..),
KESPeriod (KESPeriod),
Expand Down Expand Up @@ -732,16 +734,25 @@ genTxBody era = do
Left err -> fail (displayError err)
Right txBody -> pure txBody

-- | Generate a 'FeatureValue' for the given 'CardanoEra' with the provided generator.
genFeatureValueInEra :: ()
-- | Generate a 'Featured' for the given 'CardanoEra' with the provided generator.
genFeaturedInEra :: ()
=> Alternative f
=> feature era
-> f a
-> f (Featured feature era a)
genFeaturedInEra witness gen =
Featured witness <$> gen

-- | Generate a 'Featured' for the given 'CardanoEra' with the provided generator.
genMaybeFeaturedInEra :: ()
=> FeatureInEra feature
=> Alternative f
=> f a
-> CardanoEra era
-> f (FeatureValue feature era a)
genFeatureValueInEra gen =
featureInEra (pure NoFeatureValue) $ \witness ->
pure NoFeatureValue <|> fmap (FeatureValue witness) gen
-> f (Maybe (Featured feature era a))
genMaybeFeaturedInEra gen =
featureInEra (pure Nothing) $ \witness ->
pure Nothing <|> fmap Just (genFeaturedInEra witness gen)

genTxScriptValidity :: CardanoEra era -> Gen (TxScriptValidity era)
genTxScriptValidity era = case txScriptValiditySupportedInCardanoEra era of
Expand Down
43 changes: 2 additions & 41 deletions cardano-api/internal/Cardano/Api/Certificate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ module Cardano.Api.Certificate (
-- * Data family instances
AsType(..),

-- * GADTs for Conway/Shelley differences
ShelleyToBabbageEra(..),
ConwayEraOnwards(..),

-- * Internal functions
filterUnRegCreds,
selectStakeCredential,
Expand All @@ -79,7 +75,8 @@ import Cardano.Api.Address
import Cardano.Api.DRepMetadata
import Cardano.Api.EraCast
import Cardano.Api.Eras
import Cardano.Api.Feature
import Cardano.Api.Feature.ConwayEraOnwards
import Cardano.Api.Feature.ShelleyToBabbageEra
import Cardano.Api.Governance.Actions.VotingProcedure
import Cardano.Api.HasTypeProxy
import Cardano.Api.Keys.Praos
Expand Down Expand Up @@ -316,42 +313,6 @@ data DRepMetadataReference =
-- Constructor functions
--

data ConwayEraOnwards era where
ConwayEraOnwardsConway :: ConwayEraOnwards ConwayEra

deriving instance Show (ConwayEraOnwards era)
deriving instance Eq (ConwayEraOnwards era)

instance FeatureInEra ConwayEraOnwards where
featureInEra no yes = \case
ByronEra -> no
ShelleyEra -> no
AllegraEra -> no
MaryEra -> no
AlonzoEra -> no
BabbageEra -> no
ConwayEra -> yes ConwayEraOnwardsConway

data ShelleyToBabbageEra era where
ShelleyToBabbageEraShelley :: ShelleyToBabbageEra ShelleyEra
ShelleyToBabbageEraAllegra :: ShelleyToBabbageEra AllegraEra
ShelleyToBabbageEraMary :: ShelleyToBabbageEra MaryEra
ShelleyToBabbageEraAlonzo :: ShelleyToBabbageEra AlonzoEra
ShelleyToBabbageEraBabbage :: ShelleyToBabbageEra BabbageEra

deriving instance Show (ShelleyToBabbageEra era)
deriving instance Eq (ShelleyToBabbageEra era)

instance FeatureInEra ShelleyToBabbageEra where
featureInEra no yes = \case
ByronEra -> no
ShelleyEra -> yes ShelleyToBabbageEraShelley
AllegraEra -> yes ShelleyToBabbageEraAllegra
MaryEra -> yes ShelleyToBabbageEraMary
AlonzoEra -> yes ShelleyToBabbageEraAlonzo
BabbageEra -> yes ShelleyToBabbageEraBabbage
ConwayEra -> no

data StakeAddressRequirements era where
StakeAddrRegistrationConway
:: ConwayEraOnwards era
Expand Down
40 changes: 40 additions & 0 deletions cardano-api/internal/Cardano/Api/Eras.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ module Cardano.Api.Eras
, InAnyCardanoEra(..)
, CardanoLedgerEra

-- * FeatureInEra
, FeatureInEra(..)
, maybeFeatureInEra
, featureInShelleyBasedEra

-- * Deprecated aliases
, Byron
, Shelley
Expand Down Expand Up @@ -71,10 +76,14 @@ import Ouroboros.Consensus.Shelley.Eras as Consensus (StandardAllegra,

import Control.DeepSeq
import Data.Aeson (FromJSON (..), ToJSON, toJSON, withText)
import Data.Kind
import qualified Data.Text as Text
import Data.Type.Equality (TestEquality (..), (:~:) (Refl))
import Data.Typeable (Typeable)

-- ----------------------------------------------------------------------------
-- Eras

-- | A type used as a tag to distinguish the Byron era.
data ByronEra

Expand Down Expand Up @@ -124,6 +133,37 @@ instance HasTypeProxy ConwayEra where
data AsType ConwayEra = AsConwayEra
proxyToAsType _ = AsConwayEra

-- ----------------------------------------------------------------------------
-- FeatureInEra

-- | A class for producing values for features that are supported in some eras
-- but not others.
class FeatureInEra (feature :: Type -> Type) where
-- | Determine the value to use for a feature in a given 'CardanoEra'.
-- Note that the negative case is the first argument, and the positive case is the second as per
-- the 'either' function convention.
featureInEra :: ()
=> a -- ^ Value to use if the feature is not supported in the era
-> (feature era -> a) -- ^ Function to get thealue to use if the feature is supported in the era
-> CardanoEra era -- ^ Era to check
-> a -- ^ The value to use

maybeFeatureInEra :: ()
=> FeatureInEra feature
=> CardanoEra era -- ^ Era to check
-> Maybe (feature era) -- ^ The feature if supported in the era
maybeFeatureInEra = featureInEra Nothing Just

-- | Determine the value to use for a feature in a given 'ShelleyBasedEra'.
featureInShelleyBasedEra :: ()
=> FeatureInEra feature
=> a
-> (feature era -> a)
-> ShelleyBasedEra era
-> a
featureInShelleyBasedEra no yes =
featureInEra no yes . shelleyBasedToCardanoEra

-- ----------------------------------------------------------------------------
-- Deprecated aliases
--
Expand Down
90 changes: 19 additions & 71 deletions cardano-api/internal/Cardano/Api/Feature.hs
Original file line number Diff line number Diff line change
@@ -1,98 +1,46 @@
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}

{-# OPTIONS_GHC -Wno-deprecations #-}

module Cardano.Api.Feature
( FeatureValue (..)
, FeatureInEra(..)
, featureInShelleyBasedEra
, valueOrDefault
, asFeatureValue
, asFeatureValueInShelleyBasedEra
, isFeatureValue
( Featured (..)
, asFeaturedInEra
, asFeaturedInShelleyBasedEra
) where

import Cardano.Api.Eras

import Data.Kind

-- | A class for features that are supported in some eras but not others.
class FeatureInEra (feature :: Type -> Type) where
-- | Determine the value to use for a feature in a given 'CardanoEra'.
-- Note that the negative case is the first argument, and the positive case is the second as per
-- the 'either' function convention.
featureInEra :: ()
=> a -- ^ Value to use if the feature is not supported in the era
-> (feature era -> a) -- ^ Function to get thealue to use if the feature is supported in the era
-> CardanoEra era -- ^ Era to check
-> a -- ^ The value to use

instance FeatureInEra ShelleyBasedEra where
featureInEra no yes = \case
ByronEra -> no
ShelleyEra -> yes ShelleyBasedEraShelley
AllegraEra -> yes ShelleyBasedEraAllegra
MaryEra -> yes ShelleyBasedEraMary
AlonzoEra -> yes ShelleyBasedEraAlonzo
BabbageEra -> yes ShelleyBasedEraBabbage
ConwayEra -> yes ShelleyBasedEraConway

-- | Determine the value to use for a feature in a given 'ShelleyBasedEra'.
featureInShelleyBasedEra :: ()
=> FeatureInEra feature
=> a
-> (feature era -> a)
-> ShelleyBasedEra era
-> a
featureInShelleyBasedEra no yes = featureInEra no yes . shelleyBasedToCardanoEra

-- | A value of type @'FeatureValue' feature era a@ is either:
data FeatureValue feature era a where
-- | A value is available for this feature in this era
FeatureValue
-- | A value only if the feature is supported in this era
data Featured feature era a where
Featured
:: feature era
-- ^ The witness that the feature is supported in this era
-> a
-- ^ The value to use
-> FeatureValue feature era a

-- | No value is available for this feature in this era
NoFeatureValue
:: FeatureValue feature era a

deriving instance (Eq a, Eq (feature era)) => Eq (FeatureValue feature era a)
deriving instance (Show a, Show (feature era)) => Show (FeatureValue feature era a)
-> Featured feature era a

-- | Determine if a value is defined.
--
-- If the value is not defined, it could be because the feature is not supported or
-- because the feature is supported but the value is not available.
isFeatureValue :: FeatureValue feature era a -> Bool
isFeatureValue = \case
NoFeatureValue -> False
FeatureValue _ _ -> True
deriving instance (Eq a, Eq (feature era)) => Eq (Featured feature era a)
deriving instance (Show a, Show (feature era)) => Show (Featured feature era a)

-- | Get the value if it is defined, otherwise return the default value.
valueOrDefault :: a -> FeatureValue feature era a -> a
valueOrDefault defaultValue = \case
NoFeatureValue -> defaultValue
FeatureValue _ a -> a
instance Functor (Featured feature era) where
fmap f (Featured feature a) = Featured feature (f a)

-- | Attempt to construct a 'FeatureValue' from a value and era.
-- If the feature is not supported in the era, then 'NoFeatureValue' is returned.
asFeatureValue :: ()
asFeaturedInEra :: ()
=> FeatureInEra feature
=> a
-> CardanoEra era
-> FeatureValue feature era a
asFeatureValue value = featureInEra NoFeatureValue (`FeatureValue` value)
-> Maybe (Featured feature era a)
asFeaturedInEra value = featureInEra Nothing (Just . flip Featured value)

-- | Attempt to construct a 'FeatureValue' from a value and a shelley-based-era.
asFeatureValueInShelleyBasedEra :: ()
asFeaturedInShelleyBasedEra :: ()
=> FeatureInEra feature
=> a
-> ShelleyBasedEra era
-> FeatureValue feature era a
asFeatureValueInShelleyBasedEra value = asFeatureValue value . shelleyBasedToCardanoEra
-> Maybe (Featured feature era a)
asFeaturedInShelleyBasedEra value = asFeaturedInEra value . shelleyBasedToCardanoEra
56 changes: 56 additions & 0 deletions cardano-api/internal/Cardano/Api/Feature/ConwayEraOnwards.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

module Cardano.Api.Feature.ConwayEraOnwards
( ConwayEraOnwards(..)
, conwayEraOnwardsConstraints
, conwayEraOnwardsToCardanoEra
, conwayEraOnwardsToShelleyBasedEra
) where

import Cardano.Api.Eras

import Cardano.Crypto.Hash.Class (HashAlgorithm)
import qualified Cardano.Ledger.Api as L

data ConwayEraOnwards era where
ConwayEraOnwardsConway :: ConwayEraOnwards ConwayEra

deriving instance Show (ConwayEraOnwards era)
deriving instance Eq (ConwayEraOnwards era)

instance FeatureInEra ConwayEraOnwards where
featureInEra no yes = \case
ByronEra -> no
ShelleyEra -> no
AllegraEra -> no
MaryEra -> no
AlonzoEra -> no
BabbageEra -> no
ConwayEra -> yes ConwayEraOnwardsConway

type ConwayEraOnwardsConstraints era =
( L.EraCrypto (ShelleyLedgerEra era) ~ L.StandardCrypto
, HashAlgorithm (L.HASH (L.EraCrypto (ShelleyLedgerEra era)))
, L.ConwayEraTxBody (ShelleyLedgerEra era)
)

conwayEraOnwardsConstraints
:: ConwayEraOnwards era
-> (ConwayEraOnwardsConstraints era => a)
-> a
conwayEraOnwardsConstraints = \case
ConwayEraOnwardsConway -> id

conwayEraOnwardsToCardanoEra :: ConwayEraOnwards era -> CardanoEra era
conwayEraOnwardsToCardanoEra = shelleyBasedToCardanoEra . conwayEraOnwardsToShelleyBasedEra

conwayEraOnwardsToShelleyBasedEra :: ConwayEraOnwards era -> ShelleyBasedEra era
conwayEraOnwardsToShelleyBasedEra = \case
ConwayEraOnwardsConway -> ShelleyBasedEraConway
Loading

0 comments on commit 2b6d95c

Please sign in to comment.