enumerate-function-0.0.0: sources/Enumerate/Function/Reify.hs
{-# LANGUAGE RankNTypes, LambdaCase #-}
{-| see 'reifyFunctionAtM'.
@-- doctest@
>>> :set +m
-}
module Enumerate.Function.Reify where
import Enumerate.Types
import Enumerate.Function.Types
import Enumerate.Function.Extra
import Control.Monad.Catch (MonadThrow(..), SomeException(..))
import Control.DeepSeq (NFData)
import Control.Arrow ((&&&))
{- | reify a total function.
@
>>> reifyFunction not -- Prelude 'not'
[(False,True),(True,False)]
@
-}
reifyFunction :: (Enumerable a) => (a -> b) -> [(a,b)]
reifyFunction f = reifyFunctionM (return . f)
{-# INLINABLE reifyFunction #-}
-- | reify a total function at any subset of the domain.
reifyFunctionAt :: [a] -> (a -> b) -> [(a,b)]
reifyFunctionAt domain f = reifyFunctionAtM domain (return . f)
{-# INLINABLE reifyFunctionAt #-}
-- | reify a (safely-)partial function into a map (which is implicitly partial, where @Map.lookup@ is like @($)@.
reifyFunctionM :: (Enumerable a) => (forall m. MonadThrow m => a -> m b) -> [(a,b)]
reifyFunctionM = reifyFunctionAtM enumerated
{-# INLINABLE reifyFunctionM #-}
{- | reify a (safely-)partial function at any domain.
use the functions suffixed with @M@ when your function is explicitly partial,
i.e. of type @(forall m. MonadThrow m => a -> m b)@.
when inside a function arrow, like:
@
reifyFunctionAtM :: [a] -> (forall m. MonadThrow m => a -> m b) -> [(a,b)]
reifyFunctionAtM domain f = ...
@
the @Rank2@ type (and non-concrete types) means that @f@ can only use
parametric polymorphic functions, or the methods of the @MonadThrow@ class
(namely 'throwM'), or methods of @MonadThrow@ superclasses (namely 'return', et cetera).
'MonadThrow' is a class from the @exceptions@ package that generalizes failibility.
it has instances for @Maybe@, @Either@, @[]@, @IO@, and more.
use the functions suffixed with @At@ when your domain isn't 'Enumerable',
or when you want to restrict the domain.
the most general function in this module.
>>> :{
let uppercasePartial :: (MonadThrow m) => Char -> m Char
uppercasePartial c = case c of
'a' -> return 'A'
'b' -> return 'B'
'z' -> return 'Z'
_ -> failed "uppercasePartial"
:}
@
>>> reifyFunctionAtM ['a'..'c'] uppercasePartial
[('a','A'),('b','B')]
@
if your function doesn't fail under 'MonadThrow', see:
* 'reifyFunctionAtMaybe'
* 'reifyFunctionAtList'
* 'reifyFunctionAtEither'
-}
reifyFunctionAtM :: [a] -> (Partial a b) -> [(a,b)]
-- reifyFunctionAtM :: (MonadThrow m) => [a] -> (a -> m b) -> m (Map a b)
reifyFunctionAtM domain f
= concatMap (bitraverse pure id)
. fmap (id &&& f)
$ domain
where
bitraverse g h (x,y) = (,) <$> g x <*> h y -- avoid bifunctors dependency
-- | @reifyPredicateAt = 'flip' 'filter'@
reifyPredicateAt :: [a] -> (a -> Bool) -> [a]
reifyPredicateAt = flip filter
-- reifyPredicateAtM domain p = map fst (reifyFunctionAtM domain f)
-- where
-- f x = if p x then return x else throwM (ErrorCall "False")
-- MonadThrow Maybe
-- (e ~ SomeException) => MonadThrow (Either e)
-- MonadThrow []
-- | reify a (safely-)partial function that fails specifically under @Maybe@.
reifyFunctionMaybeAt :: [a] -> (a -> Maybe b) -> [(a, b)]
reifyFunctionMaybeAt domain f = reifyFunctionAtM domain (maybe2throw f)
{-# INLINABLE reifyFunctionMaybeAt #-}
-- | reify a (safely-)partial function that fails specifically under @[]@.
reifyFunctionListAt :: [a] -> (a -> [b]) -> [(a, b)]
reifyFunctionListAt domain f = reifyFunctionAtM domain (list2throw f)
{-# INLINABLE reifyFunctionListAt #-}
-- | reify a (safely-)partial function that fails specifically under @Either SomeException@.
reifyFunctionEitherAt :: [a] -> (a -> Either SomeException b) -> [(a, b)]
reifyFunctionEitherAt domain f = reifyFunctionAtM domain (either2throw f)
{-# INLINABLE reifyFunctionEitherAt #-}
{-| reifies an *unsafely*-partial function (i.e. a function that throws exceptions or that has inexhaustive pattern matching).
forces the function to be strict.
>>> import Data.Ratio (Ratio)
>>> fmap (1/) [0..3 :: Ratio Integer]
[*** Exception: Ratio has zero denominator
>>> let (1/) = reciprocal
>>> reifyFunctionSpoonAt [0..3 :: Ratio Integer] reciprocal
[(1 % 1,1 % 1),(2 % 1,1 % 2),(3 % 1,1 % 3)]
normal caveats from violating purity (via @unsafePerformIO@) and from catchalls (via @(e :: SomeExceptions -> _)@) apply.
-}
reifyFunctionSpoonAt :: (NFData b) => [a] -> (a -> b) -> [(a, b)]
reifyFunctionSpoonAt domain f = reifyFunctionMaybeAt domain (totalizeFunction f)
-- | reify a binary total function
reifyFunction2 :: (Enumerable a, Enumerable b) => (a -> b -> c) -> [(a,[(b,c)])]
reifyFunction2 f = reifyFunction2At enumerated enumerated f
{-# INLINABLE reifyFunction2 #-}
-- | reify a binary total function at some domain
reifyFunction2At :: [a] -> [b] -> (a -> b -> c) -> [(a,[(b,c)])]
reifyFunction2At as bs f = reifyFunction2AtM as bs (\x y -> pure (f x y))
{-# INLINABLE reifyFunction2At #-}
-- | reify a binary (safely-)partial function
reifyFunction2M :: (Enumerable a, Enumerable b) => (forall m. MonadThrow m => a -> b -> m c) -> [(a,[(b,c)])]
reifyFunction2M f = reifyFunction2AtM enumerated enumerated f
{-# INLINABLE reifyFunction2M #-}
-- | reify a binary (safely-)partial function at some domain
reifyFunction2AtM :: [a] -> [b] -> (forall m. MonadThrow m => a -> b -> m c) -> [(a,[(b,c)])]
reifyFunction2AtM as bs f = reifyFunctionAt as (\a -> reifyFunctionAtM bs (f a))