{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PackageImports #-}
module Internal.Task where
import qualified Prelude as P
import qualified "text-utf8" Data.Text
import qualified "text-utf8" Data.Text.Encoding
import qualified Control.Exception.Safe as Control
import qualified Data.ByteString.Lazy as ByteString
import qualified GHC.Stack as Stack
import qualified Internal.Shortcut as Shortcut
import qualified Control.Concurrent.MVar as MVar
import qualified Control.Concurrent.Async as Async
import qualified System.IO
import qualified List
import qualified Dict
import qualified String
import qualified Result
import qualified Tuple
import qualified Debug
import qualified System.IO
import qualified Data.Time.Clock as Clock
import qualified GHC.Stack as Stack
import qualified Internal.Utils as Utils
import Prelude (IO, Show, Functor, Monad, Applicative, FilePath, sequence_, pure, return, fmap, show)
import Control.Monad (void)
import Internal.Shortcut
import Basics
import Maybe (Maybe (..))
import Result (Result (..))
import String (String)
import Dict (Dict)
import List (List)
import Array (Array)
import Set (Set)
import Char (Char)
{-| A task is a __description__ of what you need to do. Like a todo
list. Or like a grocery list. Or like GitHub issues. So saying "the task is
to tell me the current POSIX time" does not complete the task! You need
`perform` tasks or `attempt` tasks.
-}
newtype Task x a =
Task { toIO :: IO (Result x a) }
instance Functor (Task a) where
fmap func task =
Task <| do
fmap (Result.map func) (toIO task)
instance Applicative (Task a) where
pure a =
succeed a
(<*>) func task =
Task <| do
rFunc <- toIO func
rTask <- toIO task
let apply f t = f t
return (Result.map2 apply rFunc rTask)
instance Monad (Task a) where
task >>= func =
Task <| do
result <- toIO task
case result of
Ok ok -> toIO (func ok)
Err err -> return (Err err)
-- BASICS
{-| Just having a `Task` does not mean it is done. We must `perform` the task:
> import Cherry.Prelude
>
> main =
> Task.perform Time.now
-}
perform :: Task Never a -> IO a
perform task = do
Ok a <- attempt task
return a
{-| Like `perform`, except for tasks which can fail.
-}
attempt :: Task x a -> IO (Result x a)
attempt task =
toIO task
{-| A task that succeeds immediately when run. Often useful in the last
statement of a `do` block.
> import Time
>
> timeAndZone :: Task x (Time.Posix, Time.Zone)
> timeAndZone = do
> time <- Time.now
> timezone <- Time.here
> Task.succeed (time, timezone)
-}
succeed :: a -> Task x a
succeed a =
Task <| return (Ok a)
{-| A task that fails immediately when run. Like with `succeed`, this can be
used with `andThen` to check on the outcome of another task.
> type Error = NotFound
>
> notFound :: Task Error a
> notFound =
> fail NotFound
-}
fail :: x -> Task x a
fail x =
Task <| return (Err x)
{-| Start with a list of tasks, and turn them into a single task that returns a
list. The tasks will be run in order one-by-one and if any task fails the whole
sequence fails.
> sequence [ succeed 1, succeed 2 ] == succeed [ 1, 2 ]
-}
sequence :: List (Task x a) -> Task x (List a)
sequence tasks =
List.foldr (map2 (:)) (succeed []) tasks
{-| Recover from a failure in a task. If the given task fails, we use the
callback to recover.
> fail "file not found"
> |> onError (\msg -> succeed 42)
> -- succeed 42
>
> succeed 9
> |> onError (\msg -> succeed 42)
> -- succeed 9
-}
onError :: (x -> Task y a) -> Task x a -> Task y a
onError func task =
Task <| do
result <- toIO task
case result of
Ok ok -> return (Ok ok)
Err err -> toIO (func err)
{-| Transform the error value. This can be useful if you need a bunch of error
types to match up.
> data Error
> = Http Http.Error
> | WebGL WebGL.Error
>
> getResources :: Task Error Resource
> getResources =
> sequence
> [ mapError Http serverTask
> , mapError WebGL textureTask
> ]
-}
mapError :: (x -> y) -> Task x a -> Task y a
mapError func task =
onError (fail << func) task
{-| Run tasks in parallel.
-}
parallel :: List (Task x a) -> Task x (List a)
parallel tasks =
Task <| Shortcut.map P.sequence (Async.forConcurrently tasks toIO)
{-| -}
fromResult :: Result x a -> Task x a
fromResult result =
case result of
Ok a -> succeed a
Err x -> fail x