APP 介绍

App 声明在模块 Control.App 中,它是 base 库的一部分。它的参数是一个隐含的 Path (说明程序的执行路径是线性的还是可能抛出异常),它有一个 default 值,程序可能会抛出一个 List Error (一个可以抛出的异常类型列表, ErrorType 的同义词):

data App : {default MayThrow l : Path} ->
           (es : List Error) -> Type -> Type

它的作用与 IO 相同,但支持抛出和捕获异常,并允许我们定义更多的由错误列表 es 参数化的约束性接口。例如,一个支持控制台IO的程序:

hello : Console es => App es ()
hello = putStrLn "Hello, App world!"

我们可以在一个完整的程序中使用它,如下所示:

module Main

import Control.App
import Control.App.Console

hello : Console es => App es ()
hello = putStrLn "Hello, App world!"

main : IO ()
main = run hello

或者,一个支持控制台IO的程序,携带一个 Int 的状态,标记为 Counter

data Counter : Type where

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)

为了将其作为一个完整程序的一部分来运行,我们需要初始化状态。

main : IO ()
main = run (new 93 helloCount)

为了方便起见,我们可以一次性列出多个接口,使用 Control.App 中定义的函数 Has 来计算接口约束:

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)

Path 的目的是说明一个程序是否可以抛出异常,这样我们就可以知道在哪里引用线性资源是安全的。它被声明如下:

data Path = MayThrow | NoThrow

App 的类型中 MayThrow 是默认的。我们希望这是最常见的情况。毕竟,现实中,大多数操作都有可能的失败模式,特别是那些与外部世界交互的操作。

0Has 的声明中表示它只能在一个被擦除的上下文中运行,所以它在运行时永远不会被运行。为了在 IO 内运行一个 App ,我们使用一个初始错误列表 Init (记住 ErrorType 的同义词):

Init : List Error
Init = [AppHasIO]

run : App {l} Init a -> IO a

通过 lPath 参数进行泛化,意味着我们可以为任何应用程序调用 run,无论 PathNoThrow 还是 MayThrow。但在实践中,所有传给 run 的应用程序都不会在顶层抛出异常,因为唯一可用的异常类型是 AppHasIO,这是一个空数据类型(它没有任何值)。任何异常都会在 App 内部被引入和处理。