Skip to content
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

A strongly-kinded nonce generator #160

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions parameterized-utils.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ library
Data.Parameterized.NatRepr
Data.Parameterized.Nonce
Data.Parameterized.Nonce.Transformers
Data.Parameterized.Nonce.Strong
Data.Parameterized.Nonce.Unsafe
Data.Parameterized.Pair
Data.Parameterized.Peano
Expand Down
137 changes: 137 additions & 0 deletions src/Data/Parameterized/Nonce/Strong.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{-|
Description : Strongly-kinded version of "Data.Parameterized.Nonce"
Copyright : (c) Galois, Inc 2024
Maintainer : Langston Barrett <[email protected]>

The \"brand\" type parameter of 'Nonce.NonceGenerator' and 'Nonce.Nonce' has
kind 'Type', making it easy to confuse with other type variables of the same
kind. This module introduces a @newtype@ wrapper for the types and functions
in "Data.Parameterized.Nonce" with a dedicated kind for brands ('NonceBrand').
Using this module turns some classes of incorrect type signatures into type
(kind) errors, helping to find issues earlier in the development process.

The primary downside is that we cannot offer an analog to
'Nonce.runSTNonceGenerator', the type of which would be ill-kinded under this
scheme.
-}

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.Parameterized.Nonce.Strong
( NonceBrand
, NonceGenerator
, Nonce
, indexValue
, freshNonce
, countNoncesGenerated
-- * Accessing a nonce generator
, newSTNonceGenerator
, newIONonceGenerator
, withIONonceGenerator
, withSTNonceGenerator
-- * Global nonce generator
, GlobalNonceGenerator
, globalNonceGenerator
, withGlobalSTNonceGenerator
) where

import Control.Monad.ST (ST)
import Data.Coerce (coerce)
import Data.Kind (Type)
import Data.Hashable (Hashable)
import Data.Word (Word64)

import Data.Parameterized.Classes (HashableF, OrdF, TestEquality)
import qualified Data.Parameterized.Nonce as Nonce
import Data.Parameterized.Some (Some(Some))

-- | Kind of brand type variables used in 'NonceGenerator', 'Nonce'.
--
-- Such variables function similarly to that of the 'ST' monad, see
-- "Data.Parameterized.Nonce" for more information.
newtype NonceBrand = NonceBrand Type

-- | See 'Nonce.GlobalNonceGenerator'.
type GlobalNonceGenerator = 'NonceBrand Nonce.GlobalNonceGenerator

-- | Not exported
type family Unwrap (nk :: NonceBrand) :: Type where
Unwrap ('NonceBrand s) = s

-- | See 'Nonce.NonceGenerator'.
newtype NonceGenerator (m :: Type -> Type) (s :: NonceBrand)
= NonceGenerator { getNonceGenerator :: Nonce.NonceGenerator m (Unwrap s) }

-- | See 'Nonce.Nonce'.
newtype Nonce (s :: NonceBrand) (tp :: k)
= Nonce { getNonce :: Nonce.Nonce (Unwrap s) tp }
deriving (Eq, Ord, Hashable, HashableF, OrdF, Show, TestEquality)

-- See comment in "Data.Parameterized.Nonce"
type role Nonce nominal nominal

-- | See 'Nonce.indexValue'.
indexValue :: Nonce s tp -> Word64
indexValue = Nonce.indexValue . getNonce

-- | See 'Nonce.freshNonce'.
freshNonce ::
forall m s k (tp :: k).
Functor m =>
NonceGenerator m s ->
m (Nonce s tp)
freshNonce (NonceGenerator ng) = Nonce <$> Nonce.freshNonce ng

-- | See 'Nonce.countNoncesGenerated'.
countNoncesGenerated :: NonceGenerator m s -> m Integer
countNoncesGenerated = Nonce.countNoncesGenerated . getNonceGenerator

-- | See 'Nonce.newSTNonceGenerator'.
newSTNonceGenerator :: ST t (Some (NonceGenerator (ST t)))
newSTNonceGenerator = do
Some (ng :: Nonce.NonceGenerator (ST t) s) <- Nonce.newSTNonceGenerator
let ng' :: NonceGenerator (ST t) ('NonceBrand s)
ng' = NonceGenerator ng
pure (Some ng')

-- | See 'Nonce.newIONonceGenerator'.
newIONonceGenerator :: IO (Some (NonceGenerator IO))
newIONonceGenerator = do
Some (ng :: Nonce.NonceGenerator IO s) <- Nonce.newIONonceGenerator
let ng' :: NonceGenerator IO ('NonceBrand s)
ng' = NonceGenerator ng
pure (Some ng')

-- | See 'Nonce.withSTNonceGenerator'.
withSTNonceGenerator :: (forall s . NonceGenerator (ST t) s -> ST t r) -> ST t r
withSTNonceGenerator f = do
Some r <- newSTNonceGenerator
f r

-- | See 'Nonce.withIONonceGenerator'.
withIONonceGenerator :: (forall s . NonceGenerator IO s -> IO r) -> IO r
withIONonceGenerator f = do
Some r <- newIONonceGenerator
f r

------------------------------------------------------------------------
-- * GlobalNonceGenerator

-- | See 'Nonce.globalNonceGenerator'.
globalNonceGenerator :: NonceGenerator IO GlobalNonceGenerator
globalNonceGenerator = NonceGenerator Nonce.globalNonceGenerator

-- | See 'Nonce.withGlobalSTNonceGenerator'.
withGlobalSTNonceGenerator ::
(forall (t :: Type) (s :: NonceBrand). NonceGenerator (ST t) s -> ST t r) ->
r
withGlobalSTNonceGenerator f =
Nonce.withGlobalSTNonceGenerator (coerce f)
Loading