{-# LANGUAGE FlexibleContexts #-}

{-|
Module      : AWS.Lambda.Runtime.Value
Description : Runtime methods useful when constructing Haskell handlers for the AWS Lambda Custom Runtime.
Copyright   : (c) Nike, Inc., 2018
License     : BSD3
Maintainer  : nathan.fairhurst@nike.com, fernando.freire@nike.com
Stability   : stable

These are runtimes designed for AWS Lambda, which accept a handler and return
an application that will retreive and execute events as long as a container
continues to exist.

These runtimes expect handlers that accept a parsed JSON AST
('Data.Aeson.Types.Value') as the input, instead some particular type with a FromJSON
instance.  Handlers using these runtimes must take care of the conversion and
handle errors explicitly.  Handlers that should throw an exception or never
expect to be invoked with an invalid payload, should simply use the runtimes in
the "AWS.Lambda.Runtime" module.

Each example shows the conversion from the Value type to the target FromJSON
type.

Many of these runtimes use "AWS.Lambda.Combinators" under the hood.
For those interested in peeking below the abstractions provided here,
please refer to that module.
-}

module AWS.Lambda.Runtime.Value (
  pureRuntime,
  pureRuntimeWithContext,
  fallibleRuntime,
  fallibleRuntimeWithContext,
  ioRuntime,
  ioRuntimeWithContext,
  readerTRuntime,
  mRuntimeWithContext
) where

import           AWS.Lambda.RuntimeClient (RuntimeClientConfig, getRuntimeClientConfig,
                                           getNextData, sendEventError, sendEventSuccess)
import           AWS.Lambda.Combinators   (withIOInterface,
                                           withFallibleInterface,
                                           withPureInterface,
                                           withoutContext)
import           AWS.Lambda.Context       (LambdaContext(..), HasLambdaContext(..), runReaderTLambdaContext)
import           Control.Exception        (SomeException, displayException)
import           Control.Monad            (forever)
import           Control.Monad.Catch      (MonadCatch, try)
import           Control.Monad.IO.Class   (MonadIO, liftIO)
import           Control.Monad.Reader     (MonadReader, ReaderT, local)
import           Data.Aeson               (ToJSON, Value)
import           Data.Bifunctor           (first)
import           Data.Text                (unpack)
import           System.Environment       (setEnv)

runtimeLoop :: (HasLambdaContext r, MonadReader r m, MonadCatch m, MonadIO m, ToJSON result) => RuntimeClientConfig ->
  (Value -> m result) -> m ()
runtimeLoop :: RuntimeClientConfig -> (Value -> m result) -> m ()
runtimeLoop RuntimeClientConfig
runtimeClientConfig Value -> m result
fn = do
  -- Get an event
  (ByteString
reqIdBS, Value
event, Either String LambdaContext
eCtx) <- IO (ByteString, Value, Either String LambdaContext)
-> m (ByteString, Value, Either String LambdaContext)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (ByteString, Value, Either String LambdaContext)
 -> m (ByteString, Value, Either String LambdaContext))
-> IO (ByteString, Value, Either String LambdaContext)
-> m (ByteString, Value, Either String LambdaContext)
forall a b. (a -> b) -> a -> b
$ RuntimeClientConfig
-> IO (ByteString, Value, Either String LambdaContext)
getNextData RuntimeClientConfig
runtimeClientConfig

  Either String result
result <- case Either String LambdaContext
eCtx of
    Left String
e -> Either String result -> m (Either String result)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String result -> m (Either String result))
-> Either String result -> m (Either String result)
forall a b. (a -> b) -> a -> b
$ String -> Either String result
forall a b. a -> Either a b
Left String
e
    Right LambdaContext
ctx ->
      (r -> r) -> m (Either String result) -> m (Either String result)
forall r (m :: * -> *) a. MonadReader r m => (r -> r) -> m a -> m a
local (LambdaContext -> r -> r
forall r. HasLambdaContext r => LambdaContext -> r -> r
withContext LambdaContext
ctx) (m (Either String result) -> m (Either String result))
-> m (Either String result) -> m (Either String result)
forall a b. (a -> b) -> a -> b
$ do
        -- Propagate the tracing header (Exception safe for this env var name)
        IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ String -> String -> IO ()
setEnv String
"_X_AMZN_TRACE_ID" (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> String
unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ LambdaContext -> Text
xRayTraceId LambdaContext
ctx

        {- Catching like this is _usually_ considered bad practice, but this is a true
             case where we want to both catch all errors and propogate information about them.
             See: http://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Exception.html#g:4
        -}
        -- Put any exceptions in an Either
        Either SomeException result
caughtResult <- m result -> m (Either SomeException result)
forall (m :: * -> *) e a.
(MonadCatch m, Exception e) =>
m a -> m (Either e a)
try (Value -> m result
fn Value
event)
        -- Map the Either (via first) so it is an `Either String a`
        Either String result -> m (Either String result)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String result -> m (Either String result))
-> Either String result -> m (Either String result)
forall a b. (a -> b) -> a -> b
$ (SomeException -> String)
-> Either SomeException result -> Either String result
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (SomeException -> String
forall e. Exception e => e -> String
displayException :: SomeException -> String) Either SomeException result
caughtResult

  IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ case Either String result
result of
    Right result
r -> RuntimeClientConfig -> ByteString -> result -> IO ()
forall a.
ToJSON a =>
RuntimeClientConfig -> ByteString -> a -> IO ()
sendEventSuccess RuntimeClientConfig
runtimeClientConfig ByteString
reqIdBS result
r
    Left String
e  -> RuntimeClientConfig -> ByteString -> String -> IO ()
sendEventError RuntimeClientConfig
runtimeClientConfig ByteString
reqIdBS String
e


-- | For any monad that supports IO/catch/Reader LambdaContext.
--
-- Use this if you need caching behavours or are comfortable manipulating monad
-- transformers and want full control over your monadic interface.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Context (LambdaContext(..), runReaderTLambdaContext)
--     import AWS.Lambda.Runtime (mRuntimeWithContext)
--     import Control.Monad.Reader (ReaderT, ask)
--     import Control.Monad.State.Lazy (StateT, evalStateT, get, put)
--     import Control.Monad.Trans (liftIO)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import Data.Text (unpack)
--     import System.Environment (getEnv)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: Value -> StateT Int (ReaderT LambdaContext IO) String
--     myHandler jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> return $ "My name is HAL, what's yours?"
--         Just Named { name } -> do
--           LambdaContext { functionName } <- ask
--           greeting <- liftIO $ getEnv \"GREETING\"
--
--           greetingCount <- get
--           put $ greetingCount + 1
--
--           return $ greeting ++ name ++ " (" ++ show greetingCount ++ ") from " ++ unpack functionName ++ "!"
--
--     main :: IO ()
--     main = runReaderTLambdaContext (evalStateT (mRuntimeWithContext myHandler) 0)
-- @
mRuntimeWithContext :: (HasLambdaContext r, MonadCatch m, MonadReader r m, MonadIO m, ToJSON result) =>
  (Value -> m result) -> m ()
mRuntimeWithContext :: (Value -> m result) -> m ()
mRuntimeWithContext Value -> m result
fn = do
  RuntimeClientConfig
runtimeClientConfig <- IO RuntimeClientConfig -> m RuntimeClientConfig
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO RuntimeClientConfig
getRuntimeClientConfig

  m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ RuntimeClientConfig -> (Value -> m result) -> m ()
forall r (m :: * -> *) result.
(HasLambdaContext r, MonadReader r m, MonadCatch m, MonadIO m,
 ToJSON result) =>
RuntimeClientConfig -> (Value -> m result) -> m ()
runtimeLoop RuntimeClientConfig
runtimeClientConfig Value -> m result
fn


-- | For functions that can read the lambda context and use IO within the same monad.
--
-- Use this for handlers that need any form of side-effect such as reading
-- environment variables or making network requests, and prefer to access the
-- AWS Lambda Context in the same monad.
-- However, do not use this runtime if you need stateful (caching) behaviors.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Context (LambdaContext(..))
--     import AWS.Lambda.Runtime (readerTRuntime)
--     import Control.Monad.Reader (ReaderT, ask)
--     import Control.Monad.Trans (liftIO)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import Data.Text (unpack)
--     import System.Environment (getEnv)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: Value -> ReaderT LambdaContext IO String
--     myHandler jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> return $ "My name is HAL, what's yours?"
--         Just Named { name } -> do
--           LambdaContext { functionName } <- ask
--           greeting <- liftIO $ getEnv \"GREETING\"
--           return $ greeting ++ name ++ " from " ++ unpack functionName ++ "!"
--
--     main :: IO ()
--     main = readerTRuntime myHandler
-- @
readerTRuntime :: ToJSON result =>
  (Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime :: (Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime = ReaderT LambdaContext IO () -> IO ()
forall (m :: * -> *) a. ReaderT LambdaContext m a -> m a
runReaderTLambdaContext (ReaderT LambdaContext IO () -> IO ())
-> ((Value -> ReaderT LambdaContext IO result)
    -> ReaderT LambdaContext IO ())
-> (Value -> ReaderT LambdaContext IO result)
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
.  (Value -> ReaderT LambdaContext IO result)
-> ReaderT LambdaContext IO ()
forall r (m :: * -> *) result.
(HasLambdaContext r, MonadCatch m, MonadReader r m, MonadIO m,
 ToJSON result) =>
(Value -> m result) -> m ()
mRuntimeWithContext

-- | For functions with IO that can fail in a pure way (or via throw).
--
-- Use this for handlers that need any form of side-effect such as reading
-- environment variables or making network requests, and also need the
-- AWS Lambda Context as input.
-- However, do not use this runtime if you need stateful (caching) behaviors.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Context (LambdaContext(..))
--     import AWS.Lambda.Runtime (ioRuntimeWithContext)
--     import Control.Monad.Trans (liftIO)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import Data.Text (unpack)
--     import System.Environment (getEnv)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: LambdaContext -> Value -> IO (Either String String)
--     myHandler (LambdaContext { functionName }) jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> return $ pure "My name is HAL, what's yours?"
--         Just Named { name } -> do
--           greeting <- liftIO $ getEnv \"GREETING\"
--           return $ pure $ greeting ++ name ++ " from " ++ unpack functionName ++ "!"
--
--     main :: IO ()
--     main = ioRuntimeWithContext myHandler
-- @
ioRuntimeWithContext :: ToJSON result =>
  (LambdaContext -> Value -> IO (Either String result)) -> IO ()
ioRuntimeWithContext :: (LambdaContext -> Value -> IO (Either String result)) -> IO ()
ioRuntimeWithContext = (Value -> ReaderT LambdaContext IO result) -> IO ()
forall result.
ToJSON result =>
(Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime ((Value -> ReaderT LambdaContext IO result) -> IO ())
-> ((LambdaContext -> Value -> IO (Either String result))
    -> Value -> ReaderT LambdaContext IO result)
-> (LambdaContext -> Value -> IO (Either String result))
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LambdaContext -> Value -> IO (Either String result))
-> Value -> ReaderT LambdaContext IO result
forall c (m :: * -> *) b a.
(MonadReader c m, MonadIO m) =>
(c -> b -> IO (Either String a)) -> b -> m a
withIOInterface

-- | For functions with IO that can fail in a pure way (or via throw).
--
-- Use this for handlers that need any form of side-effect such as reading
-- environment variables or making network requests.
-- However, do not use this runtime if you need stateful (caching) behaviors.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Runtime (ioRuntime)
--     import Control.Monad.Trans (liftIO)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import System.Environment (getEnv)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: Value -> IO (Either String String)
--     myHandler jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> return $ pure "My name is HAL, what's yours?"
--         Just Named { name } -> do
--           greeting <- liftIO $ getEnv \"GREETING\"
--           return $ pure $ greeting ++ name
--
--     main :: IO ()
--     main = ioRuntime myHandler
-- @
ioRuntime :: ToJSON result =>
  (Value -> IO (Either String result)) -> IO ()
ioRuntime :: (Value -> IO (Either String result)) -> IO ()
ioRuntime = (Value -> ReaderT LambdaContext IO result) -> IO ()
forall result.
ToJSON result =>
(Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime ((Value -> ReaderT LambdaContext IO result) -> IO ())
-> ((Value -> IO (Either String result))
    -> Value -> ReaderT LambdaContext IO result)
-> (Value -> IO (Either String result))
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LambdaContext -> Value -> IO (Either String result))
-> Value -> ReaderT LambdaContext IO result
forall c (m :: * -> *) b a.
(MonadReader c m, MonadIO m) =>
(c -> b -> IO (Either String a)) -> b -> m a
withIOInterface ((LambdaContext -> Value -> IO (Either String result))
 -> Value -> ReaderT LambdaContext IO result)
-> ((Value -> IO (Either String result))
    -> LambdaContext -> Value -> IO (Either String result))
-> (Value -> IO (Either String result))
-> Value
-> ReaderT LambdaContext IO result
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Value -> IO (Either String result))
-> LambdaContext -> Value -> IO (Either String result)
forall a b. a -> b -> a
withoutContext

-- | For pure functions that can still fail.
--
-- Use this for simple handlers that just translate input to output without side-effects,
-- but can fail and need the AWS Lambda Context as input.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Context (LambdaContext(..))
--     import AWS.Lambda.Runtime (fallibleRuntimeWithContext)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import Data.Text (unpack)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: LambdaContext -> Value -> Either String String
--     myHandler (LambdaContext { functionName }) jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> Right "My name is HAL, what's yours?"
--         Just Named { name } ->
--           if name == \"World\" then
--             Right $ "Hello, World from " ++ unpack functionName ++ "!"
--           else
--             Left "Can only greet the world."
--
--     main :: IO ()
--     main = fallibleRuntimeWithContext myHandler
-- @
fallibleRuntimeWithContext :: ToJSON result =>
  (LambdaContext -> Value -> Either String result) -> IO ()
fallibleRuntimeWithContext :: (LambdaContext -> Value -> Either String result) -> IO ()
fallibleRuntimeWithContext = (Value -> ReaderT LambdaContext IO result) -> IO ()
forall result.
ToJSON result =>
(Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime ((Value -> ReaderT LambdaContext IO result) -> IO ())
-> ((LambdaContext -> Value -> Either String result)
    -> Value -> ReaderT LambdaContext IO result)
-> (LambdaContext -> Value -> Either String result)
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LambdaContext -> Value -> Either String result)
-> Value -> ReaderT LambdaContext IO result
forall c (m :: * -> *) b a.
MonadReader c m =>
(c -> b -> Either String a) -> b -> m a
withFallibleInterface

-- | For pure functions that can still fail.
--
-- Use this for simple handlers that just translate input to output without side-effects,
-- but can fail.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Runtime (fallibleRuntime)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: Value -> Either String String
--     myHandler jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> Right "My name is HAL, what's yours?"
--         Just Named { name } ->
--           if name == \"World\" then
--             Right "Hello, World!"
--           else
--             Left "Can only greet the world."
--
--     main :: IO ()
--     main = fallibleRuntime myHandler
-- @
fallibleRuntime :: ToJSON result =>
  (Value -> Either String result) -> IO ()
fallibleRuntime :: (Value -> Either String result) -> IO ()
fallibleRuntime = (Value -> ReaderT LambdaContext IO result) -> IO ()
forall result.
ToJSON result =>
(Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime ((Value -> ReaderT LambdaContext IO result) -> IO ())
-> ((Value -> Either String result)
    -> Value -> ReaderT LambdaContext IO result)
-> (Value -> Either String result)
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LambdaContext -> Value -> Either String result)
-> Value -> ReaderT LambdaContext IO result
forall c (m :: * -> *) b a.
MonadReader c m =>
(c -> b -> Either String a) -> b -> m a
withFallibleInterface ((LambdaContext -> Value -> Either String result)
 -> Value -> ReaderT LambdaContext IO result)
-> ((Value -> Either String result)
    -> LambdaContext -> Value -> Either String result)
-> (Value -> Either String result)
-> Value
-> ReaderT LambdaContext IO result
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Value -> Either String result)
-> LambdaContext -> Value -> Either String result
forall a b. a -> b -> a
withoutContext

-- | For pure functions that can never fail that also need access to the context.
--
-- Use this for simple handlers that just translate input to output without side-effects,
-- but that need the AWS Lambda Context as input.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Context (LambdaContext(..))
--     import AWS.Lambda.Runtime (pureRuntimeWithContext)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import Data.Text (unpack)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: LambdaContext -> Value -> Either String String
--     myHandler (LambdaContext { functionName }) jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> Right "My name is HAL, what's yours?"
--         Just Named { name } ->
--           Right $ "Hello, " ++ name ++ " from " ++ unpack functionName ++ "!"
--
--     main :: IO ()
--     main = pureRuntimeWithContext myHandler
-- @
pureRuntimeWithContext :: ToJSON result =>
  (LambdaContext -> Value -> result) -> IO ()
pureRuntimeWithContext :: (LambdaContext -> Value -> result) -> IO ()
pureRuntimeWithContext = (Value -> ReaderT LambdaContext IO result) -> IO ()
forall result.
ToJSON result =>
(Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime ((Value -> ReaderT LambdaContext IO result) -> IO ())
-> ((LambdaContext -> Value -> result)
    -> Value -> ReaderT LambdaContext IO result)
-> (LambdaContext -> Value -> result)
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LambdaContext -> Value -> result)
-> Value -> ReaderT LambdaContext IO result
forall c (m :: * -> *) b a.
MonadReader c m =>
(c -> b -> a) -> b -> m a
withPureInterface

-- | For pure functions that can never fail.
--
-- Use this for simple handlers that just translate input to output without side-effects.
--
-- @
--     {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-}
--
--     module Main where
--
--     import AWS.Lambda.Runtime (pureRuntime)
--     import Data.Aeson (Value, FromJSON, parseJSON)
--     import Data.Aeson.Types (parseMaybe)
--     import GHC.Generics (Generic)
--
--     data Named = Named {
--       name :: String
--     } deriving Generic
--     instance FromJSON Named
--
--     myHandler :: Value -> String
--     myHandler jsonAst =
--       case parseMaybe parseJSON jsonAst of
--         Nothing -> "My name is HAL, what's yours?"
--         Just Named { name } ->
--           "Hello, " ++ name ++ "!"
--
--     main :: IO ()
--     main = pureRuntime myHandler
-- @
pureRuntime :: ToJSON result => (Value -> result) -> IO ()
pureRuntime :: (Value -> result) -> IO ()
pureRuntime = (Value -> ReaderT LambdaContext IO result) -> IO ()
forall result.
ToJSON result =>
(Value -> ReaderT LambdaContext IO result) -> IO ()
readerTRuntime ((Value -> ReaderT LambdaContext IO result) -> IO ())
-> ((Value -> result) -> Value -> ReaderT LambdaContext IO result)
-> (Value -> result)
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LambdaContext -> Value -> result)
-> Value -> ReaderT LambdaContext IO result
forall c (m :: * -> *) b a.
MonadReader c m =>
(c -> b -> a) -> b -> m a
withPureInterface ((LambdaContext -> Value -> result)
 -> Value -> ReaderT LambdaContext IO result)
-> ((Value -> result) -> LambdaContext -> Value -> result)
-> (Value -> result)
-> Value
-> ReaderT LambdaContext IO result
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Value -> result) -> LambdaContext -> Value -> result
forall a b. a -> b -> a
withoutContext