Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
This module provides the Advice
datatype, along for functions for creating,
manipulating, composing and applying values of that type.
Advice
s are type-preserving transformations on IO
-effectful functions of
any number of arguments.
>>>
:{
foo0 :: IO (Sum Int) foo0 = pure (Sum 5) foo1 :: Bool -> IO (Sum Int) foo1 _ = foo0 foo2 :: Char -> Bool -> IO (Sum Int) foo2 _ = foo1 :}
They work for IO
-actions of zero arguments:
>>>
advise (printArgs stdout "foo0") foo0
foo0: Sum {getSum = 5}
And for functions of one or more arguments, provided they end on a IO
-action:
>>>
advise (printArgs stdout "foo1") foo1 False
foo1: False Sum {getSum = 5}
>>>
advise (printArgs stdout "foo2") foo2 'd' False
foo2: 'd' False Sum {getSum = 5}
Advice
s can also tweak the result value of functions:
>>>
advise (returnMempty @Top) foo2 'd' False
Sum {getSum = 0}
And they can be combined using Advice
's Monoid
instance before being
applied:
>>>
advise (printArgs stdout "foo2" <> returnMempty) foo2 'd' False
foo2: 'd' False Sum {getSum = 0}
Although sometimes composition might require harmonizing the constraints
each Advice
places on the arguments, if they differ.
Synopsis
- data Advice (ca :: Type -> Constraint) r
- makeAdvice :: forall ca r. (forall as. All ca as => NP I as -> IO (IO r -> IO r, NP I as)) -> Advice ca r
- makeArgsAdvice :: forall ca r. (forall as. All ca as => NP I as -> IO (NP I as)) -> Advice ca r
- makeExecutionAdvice :: forall ca r. (IO r -> IO r) -> Advice ca r
- advise :: forall ca r as advisee. (Multicurryable as r advisee, All ca as) => Advice ca r -> advisee -> advisee
- restrictArgs :: forall more less r. (forall x. Dict more x -> Dict less x) -> Advice less r -> Advice more r
- adviseRecord :: forall ca cr advised. AdvisedRecord ca cr advised => (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca r) -> advised IO -> advised IO
- class Top (x :: k)
- class (f x, g x) => And (f :: k -> Constraint) (g :: k -> Constraint) (x :: k)
- class (AllF c xs, SListI xs) => All (c :: k -> Constraint) (xs :: [k])
- data NP (a :: k -> Type) (b :: [k]) where
- newtype I a = I a
- cfoldMap_NP :: forall {k} c (xs :: [k]) m proxy f. (All c xs, Monoid m) => proxy c -> (forall (a :: k). c a => f a -> m) -> NP f xs -> m
- data Dict (c :: k -> Constraint) (a :: k) where
- Dict :: forall {k} (c :: k -> Constraint) (a :: k). c a => Dict c a
The Advice type
data Advice (ca :: Type -> Constraint) r Source #
A generic transformation of IO
-effectful functions of return type r
,
provided the functions satisfy certain constraint ca
on all of their
arguments.
Advice
s that don't care about the ca
constraint (because they don't
touch function arguments) can leave it polymorphic, and this facilitates
Advice
composition, but then the constraint must be given the catch-all
Top
value (using a type application) at the moment of calling advise
.
See Dep.IOAdvice.Basic for examples.
Instances
Monoid (Advice ca r) Source # | |
Semigroup (Advice ca r) Source # |
The first |
Creating Advice values
:: forall ca r. (forall as. All ca as => NP I as -> IO (IO r -> IO r, NP I as)) | The function that tweaks the arguments and the execution. |
-> Advice ca r |
The most general way of constructing Advice
s.
An Advice
is a function that transforms other functions in an
arity-polymorphic way. It receives the arguments of the advised
function packed into an n-ary product NP
, performs some
effects based on them, and returns a potentially modified version of the
arguments, along with a function for tweaking the execution of the
advised function.
>>>
:{
doesNothing :: forall ca r. Advice ca r doesNothing = makeAdvice (\args -> pure (id, args)) :}
:: forall ca r. (forall as. All ca as => NP I as -> IO (NP I as)) | The function that tweaks the arguments. |
-> Advice ca r |
Create an advice which only tweaks and/or analyzes the function arguments.
>>>
:{
doesNothing :: forall ca r. Advice ca r doesNothing = makeArgsAdvice pure :}
Create an advice which only tweaks the execution of the final monadic action.
>>>
:{
doesNothing :: forall ca r. Advice ca r doesNothing = makeExecutionAdvice id :}
Applying Advices
:: forall ca r as advisee. (Multicurryable as r advisee, All ca as) | |
=> Advice ca r | The advice to apply. |
-> advisee | A function to be adviced. |
-> advisee |
Apply an Advice
to some compatible function. The function must have its
effects in IO
, and all of its arguments must satisfy the ca
constraint.
>>>
:{
foo :: Int -> IO String foo _ = pure "foo" advisedFoo = advise (printArgs stdout "Foo args: ") foo :}
TYPE APPLICATION REQUIRED! If the ca
constraint of the Advice
remains polymorphic,
it must be supplied by means of a type application:
>>>
:{
bar :: Int -> IO String bar _ = pure "bar" advisedBar1 = advise (returnMempty @Top) bar advisedBar2 = advise @Top returnMempty bar :}
Harmonizing Advice argument constraints
Advice
values can be composed using the Monoid
instance, but only if
they have the same type parameters. It's unfortunate that—unlike with
normal function constraints—the ca
constraints of an Advice
aren't
automatically "collected" during composition.
Instead, we need to harmonize the ca
constraints of each Advice
by
turning them into the combination of all constraints. restrictArgs
helps with that.
restrictArgs
takes as parameter value-level "evidence" that one
constraint implies another. But how to construct such evidence? By using
the Dict
GADT, more precisely the deceptively simple-looking term
\Dict -> Dict
. That function "absorbs" some constraint present in the
ambient context and re-packages it a a new constraint that is implied by
the former. We can't rely on type inference here; we need to provide
enough type information to the GADT, be it as an explicit signature:
>>>
:{
stricterPrintArgs :: forall r. Advice (Show `And` Eq `And` Ord) r stricterPrintArgs = restrictArgs (\Dict -> Dict) (printArgs stdout "foo") :}
or with a type application to restrictArgs
:
>>>
stricterPrintArgs = restrictArgs @(Show `And` Eq `And` Ord) (\Dict -> Dict) (printArgs stdout "foo")
:: forall more less r. (forall x. Dict more x -> Dict less x) | Evidence that one constraint implies the other. Every |
-> Advice less r | Advice with less restrictive constraint on the args. |
-> Advice more r | Advice with more restrictive constraint on the args. |
Makes the constraint on the arguments more restrictive.
Advising and deceiving entire records
adviseRecord
is a version of advise
that, instead of working on bare
functions, transforms entire records-of-functions in one go. It also works
with newtypes containing a single function. The records must derive Generic
.
Useful with the "wrapped" style of components facilitated by Control.Monad.Dep.Has
.
>>>
:{
type Logger :: (Type -> Type) -> Type newtype Logger d = Logger {log :: String -> d ()} deriving Generic type Repository :: (Type -> Type) -> Type data Repository d = Repository { select :: String -> d [Int], insert :: [Int] -> d () } deriving Generic type Controller :: (Type -> Type) -> Type newtype Controller d = Controller {serve :: Int -> d String} deriving Generic type Env :: (Type -> Type) -> Type data Env m = Env { logger :: Logger m, repository :: Repository m, controller :: Controller m } env :: Env IO env = let logger = Logger \_ -> pure () repository = Repository {select = \_ -> pure [], insert = \_ -> pure ()} & adviseRecord @Top @Top mempty controller = Controller { serve = \_ -> pure "view" } & adviseRecord @Top @Top mempty in Env {logger, repository, controller} :}
:: forall ca cr advised. AdvisedRecord ca cr advised | |
=> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca r) | The advice to apply. |
-> advised IO | The record to advise recursively. |
-> advised IO | The advised record. |
Gives Advice
to all the functions in a record-of-functions.
The function that builds the advice receives a list of tuples (TypeRep, String)
which represent the record types and fields names we have
traversed until arriving at the advised function. This info can be useful for
logging advices. It's a list instead of a single tuple because
adviseRecord
works recursively. The elements come innermost-first.
TYPE APPLICATION REQUIRED! The ca
constraint on function arguments
and the cr
constraint on the result type must be supplied by means of a
type application. Supply Top
if no constraint is required.
"sop-core" re-exports
Some useful definitions re-exported the from "sop-core" package.
NP
is an n-ary product used to represent the arguments of advised functions.
I
is an identity functor. The arguments processed by an Advice
come wrapped in it.
cfoldMap_NP
is useful to construct homogeneous lists out of the NP
product, for example:
>>>
cfoldMap_NP (Proxy @Show) (\(I a) -> [show a]) (I False :* I (1::Int) :* Nil)
["False","1"]
A constraint that can always be satisfied.
Since: sop-core-0.2
Instances
Top (x :: k) | |
Defined in Data.SOP.Constraint |
class (f x, g x) => And (f :: k -> Constraint) (g :: k -> Constraint) (x :: k) infixl 7 #
Pairing of constraints.
Since: sop-core-0.2
Instances
(f x, g x) => And (f :: k -> Constraint) (g :: k -> Constraint) (x :: k) | |
Defined in Data.SOP.Constraint |
class (AllF c xs, SListI xs) => All (c :: k -> Constraint) (xs :: [k]) #
Require a constraint for every element of a list.
If you have a datatype that is indexed over a type-level
list, then you can use All
to indicate that all elements
of that type-level list must satisfy a given constraint.
Example: The constraint
All Eq '[ Int, Bool, Char ]
is equivalent to the constraint
(Eq Int, Eq Bool, Eq Char)
Example: A type signature such as
f :: All Eq xs => NP I xs -> ...
means that f
can assume that all elements of the n-ary
product satisfy Eq
.
Note on superclasses: ghc cannot deduce superclasses from All
constraints.
You might expect the following to compile
class (Eq a) => MyClass a foo :: (All Eq xs) => NP f xs -> z foo = [..] bar :: (All MyClass xs) => NP f xs -> x bar = foo
but it will fail with an error saying that it was unable to
deduce the class constraint
(or similar) in the
definition of AllF
Eq
xsbar
.
In cases like this you can use Dict
from Data.SOP.Dict
to prove conversions between constraints.
See this answer on SO for more details.
Instances
All (c :: k -> Constraint) ('[] :: [k]) | |
Defined in Data.SOP.Constraint cpara_SList :: proxy c -> r '[] -> (forall (y :: k0) (ys :: [k0]). (c y, All c ys) => r ys -> r (y ': ys)) -> r '[] # | |
(c x, All c xs) => All (c :: a -> Constraint) (x ': xs :: [a]) | |
Defined in Data.SOP.Constraint cpara_SList :: proxy c -> r '[] -> (forall (y :: k) (ys :: [k]). (c y, All c ys) => r ys -> r (y ': ys)) -> r (x ': xs) # |
data NP (a :: k -> Type) (b :: [k]) where #
An n-ary product.
The product is parameterized by a type constructor f
and
indexed by a type-level list xs
. The length of the list
determines the number of elements in the product, and if the
i
-th element of the list is of type x
, then the i
-th
element of the product is of type f x
.
The constructor names are chosen to resemble the names of the list constructors.
Two common instantiations of f
are the identity functor I
and the constant functor K
. For I
, the product becomes a
heterogeneous list, where the type-level list describes the
types of its components. For
, the product becomes a
homogeneous list, where the contents of the type-level list are
ignored, but its length still specifies the number of elements.K
a
In the context of the SOP approach to generic programming, an n-ary product describes the structure of the arguments of a single data constructor.
Examples:
I 'x' :* I True :* Nil :: NP I '[ Char, Bool ] K 0 :* K 1 :* Nil :: NP (K Int) '[ Char, Bool ] Just 'x' :* Nothing :* Nil :: NP Maybe '[ Char, Bool ]
Nil :: forall {k} (a :: k -> Type). NP a ('[] :: [k]) | |
(:*) :: forall {k} (a :: k -> Type) (x :: k) (xs :: [k]). a x -> NP a xs -> NP a (x ': xs) infixr 5 |
Instances
HTrans (NP :: (k1 -> Type) -> [k1] -> Type) (NP :: (k2 -> Type) -> [k2] -> Type) | |
Defined in Data.SOP.NP htrans :: forall c (xs :: l1) (ys :: l2) proxy f g. AllZipN (Prod NP) c xs ys => proxy c -> (forall (x :: k10) (y :: k20). c x y => f x -> g y) -> NP f xs -> NP g ys # hcoerce :: forall (f :: k10 -> Type) (g :: k20 -> Type) (xs :: l1) (ys :: l2). AllZipN (Prod NP) (LiftedCoercible f g) xs ys => NP f xs -> NP g ys # | |
HAp (NP :: (k -> Type) -> [k] -> Type) | |
HCollapse (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP | |
HPure (NP :: (k -> Type) -> [k] -> Type) | |
HSequence (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP hsequence' :: forall (xs :: l) f (g :: k0 -> Type). (SListIN NP xs, Applicative f) => NP (f :.: g) xs -> f (NP g xs) # hctraverse' :: forall c (xs :: l) g proxy f f'. (AllN NP c xs, Applicative g) => proxy c -> (forall (a :: k0). c a => f a -> g (f' a)) -> NP f xs -> g (NP f' xs) # htraverse' :: forall (xs :: l) g f f'. (SListIN NP xs, Applicative g) => (forall (a :: k0). f a -> g (f' a)) -> NP f xs -> g (NP f' xs) # | |
HTraverse_ (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP hctraverse_ :: forall c (xs :: l) g proxy f. (AllN NP c xs, Applicative g) => proxy c -> (forall (a :: k0). c a => f a -> g ()) -> NP f xs -> g () # htraverse_ :: forall (xs :: l) g f. (SListIN NP xs, Applicative g) => (forall (a :: k0). f a -> g ()) -> NP f xs -> g () # | |
(All (Compose Monoid f) xs, All (Compose Semigroup f) xs) => Monoid (NP f xs) | Since: sop-core-0.4.0.0 |
All (Compose Semigroup f) xs => Semigroup (NP f xs) | Since: sop-core-0.4.0.0 |
All (Compose Show f) xs => Show (NP f xs) | |
All (Compose NFData f) xs => NFData (NP f xs) | Since: sop-core-0.2.5.0 |
Defined in Data.SOP.NP | |
All (Compose Eq f) xs => Eq (NP f xs) | |
(All (Compose Eq f) xs, All (Compose Ord f) xs) => Ord (NP f xs) | |
type AllZipN (NP :: (k -> Type) -> [k] -> Type) (c :: a -> b -> Constraint) | |
Defined in Data.SOP.NP | |
type Same (NP :: (k1 -> Type) -> [k1] -> Type) | |
type Prod (NP :: (k -> Type) -> [k] -> Type) | |
type UnProd (NP :: (k -> Type) -> [k] -> Type) | |
type SListIN (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP | |
type CollapseTo (NP :: (k -> Type) -> [k] -> Type) a | |
Defined in Data.SOP.NP | |
type AllN (NP :: (k -> Type) -> [k] -> Type) (c :: k -> Constraint) | |
Defined in Data.SOP.NP |
The identity type functor.
Like Identity
, but with a shorter name.
I a |
Instances
Foldable I | |
Defined in Data.SOP.BasicFunctors fold :: Monoid m => I m -> m # foldMap :: Monoid m => (a -> m) -> I a -> m # foldMap' :: Monoid m => (a -> m) -> I a -> m # foldr :: (a -> b -> b) -> b -> I a -> b # foldr' :: (a -> b -> b) -> b -> I a -> b # foldl :: (b -> a -> b) -> b -> I a -> b # foldl' :: (b -> a -> b) -> b -> I a -> b # foldr1 :: (a -> a -> a) -> I a -> a # foldl1 :: (a -> a -> a) -> I a -> a # elem :: Eq a => a -> I a -> Bool # maximum :: Ord a => I a -> a # | |
Eq1 I | Since: sop-core-0.2.4.0 |
Ord1 I | Since: sop-core-0.2.4.0 |
Defined in Data.SOP.BasicFunctors | |
Read1 I | Since: sop-core-0.2.4.0 |
Show1 I | Since: sop-core-0.2.4.0 |
Traversable I | |
Applicative I | |
Functor I | |
Monad I | |
NFData1 I | Since: sop-core-0.2.5.0 |
Defined in Data.SOP.BasicFunctors | |
Monoid a => Monoid (I a) | Since: sop-core-0.4.0.0 |
Semigroup a => Semigroup (I a) | Since: sop-core-0.4.0.0 |
Generic (I a) | |
Read a => Read (I a) | |
Show a => Show (I a) | |
NFData a => NFData (I a) | Since: sop-core-0.2.5.0 |
Defined in Data.SOP.BasicFunctors | |
Eq a => Eq (I a) | |
Ord a => Ord (I a) | |
type Rep (I a) | |
Defined in Data.SOP.BasicFunctors |
cfoldMap_NP :: forall {k} c (xs :: [k]) m proxy f. (All c xs, Monoid m) => proxy c -> (forall (a :: k). c a => f a -> m) -> NP f xs -> m #
Specialization of hcfoldMap
.
Since: sop-core-0.3.2.0
data Dict (c :: k -> Constraint) (a :: k) where #
An explicit dictionary carrying evidence of a class constraint.
The constraint parameter is separated into a
second argument so that
is of the correct
kind to be used directly as a parameter to e.g. Dict
cNP
.
Since: sop-core-0.2
Dict :: forall {k} (c :: k -> Constraint) (a :: k). c a => Dict c a |