Idris2/docs/source/app/introapp.rst

108 lines
3.2 KiB
ReStructuredText
Raw Normal View History

Introducing App
===============
``App`` is declared as below, in a module ``Control.App``, which is part of
the ``base`` libraries.
It is parameterised by an implicit
``Path`` (which states whether the program's execution path
is linear or might throw
exceptions), which has a ``default`` value that the program
2020-08-10 12:05:23 +03:00
might throw, and a ``List Error``
(which gives a list of exception types which can be thrown, ``Error`` is
a synonym for ``Type``):
.. code-block:: idris
data App : {default MayThrow l : Path} ->
2020-08-10 12:05:23 +03:00
(es : List Error) -> Type -> Type
It serves the same purpose as ``IO``, but supports throwing and catching
exceptions, and allows us to define more constrained interfaces parameterised
2020-08-10 12:05:23 +03:00
by the list of errors ``es``.
e.g. a program which supports console IO:
.. code-block:: idris
2020-08-10 12:05:23 +03:00
hello : Console es => App es ()
hello = putStrLn "Hello, App world!"
We can use this in a complete program as follows:
.. code-block:: idris
module Main
import Control.App
import Control.App.Console
2020-08-10 12:05:23 +03:00
hello : Console es => App es ()
hello = putStrLn "Hello, App world!"
main : IO ()
main = run hello
Or, a program which supports console IO and carries an ``Int`` state,
labelled ``Counter``:
.. code-block:: idris
data Counter : Type where
2020-08-10 12:05:23 +03:00
helloCount : (Console es, State Counter Int es) => App es ()
helloCount = do c <- get Counter
put Counter (c + 1)
putStrLn "Hello, counting world"
c <- get Counter
putStrLn ("Counter " ++ show c)
To run this as part of a complete program, we need to initialise the state.
.. code-block:: idris
main : IO ()
main = run (new 93 helloCount)
For convenience, we can list multiple interfaces in one go, using a function
``Has``, defined in ``Control.App``, to compute the interface constraints:
.. code-block:: idris
2020-08-10 12:05:23 +03:00
helloCount : Has [Console, State Counter Int] es => App es ()
0 Has : List (a -> Type) -> a -> Type
Has [] es = ()
Has (e :: es') es = (e es, Has es' es)
The purpose of ``Path`` is to state whether a program can throw
exceptions, so that we can know where it is safe to reference linear
resources. It is declared as follows:
.. code-block:: idris
data Path = MayThrow | NoThrow
The type of ``App`` states that ``MayThrow`` is the default.
We expect this to be the most
common case. After all, realistically, most operations have possible failure
modes, especially those which interact with the outside world.
The ``0`` on the declaration of ``Has`` indicates that it can only
be run in an erased context, so it will never be run at run-time.
To run an ``App`` inside ``IO``, we use an initial
2020-08-10 12:05:23 +03:00
list of errors ``Init`` (recall that an ``Error`` is a
synonym for ``Type``):
.. code-block:: idris
2020-08-10 12:05:23 +03:00
Init : List Error
2021-03-10 15:27:05 +03:00
Init = [AppHasIO]
run : App {l} Init a -> IO a
Generalising the ``Path`` parameter with ``l``
means that we can invoke ``run`` for any application, whether the ``Path``
is ``NoThrow`` or ``MayThrow``. But, in practice, all applications
given to ``run`` will not throw at the top level, because the only
2021-03-10 15:27:05 +03:00
exception type available is the type ``AppHasIO``. Any exceptions
will have been introduced and handled inside the ``App``.