9.1. 論理モデル(Logical Model)

概念的に、Lean は項の評価や簡約と副作用の 実行 を区別します。項の簡約は βδ のような規則で指定され、いつでも・どこでも発生する可能性があります。正しい順序で実行されなければならない副作用は Lean の論理で抽象的に記述されます。プログラムが実行されると、Lean のランタイムシステムは記述された作用を実際に実行する責任を負います。

IO α 型は副作用を実行することによって α 型の値を返すか、エラーを投げるべきプロセスについての記述です。これは世界全体を状態とする 状態モナド として考えることができます。 StateM Nat Bool 型の値が自然数をミューテーションさせる能力を持ちながら Bool を計算することと同じように、 IO Bool 型の値は世界を変化させる可能性を有しながら Bool を計算します。エラー処理は、適切な例外モナド変換子をこの上に重ねることで達成されます。

世界全体をメモリ上で表現することはできないため、実際の実装ではその状態を表す抽象的なトークンを使用します。Lean のランタイムシステムは、プログラムの実行時に最初のトークンを提供する責任を負い、各プリミティブのアクションは世界を表すトークンを受け入れ、終了時に別のトークンを返します。これにより作用が適切な順序で発生することが保証され、Lean の項の簡約セマンティクスから副作用の実行が明確に分離されます。

一般的な再帰による停止しない処理は、 IO で説明される作用とは別に扱われます。無限ループによって終了しない可能性のあるプログラムは partial 関数として定義する必要があります。論理的な観点からは、これらは任意の定数として扱われます; IO である必要はありません。

IO の非常に重要な特性は、値が「エスケープ」する方法がないということです。いくつかの明確にマークされた安全でない演算子を使用しない限り、プログラムは IO Nat から純粋な Nat を取り出す方法がありません。これにより副作用の正しい順序が保たれ、副作用のあるプログラムが副作用を持つものとして明確にマークされることが保証されます。

9.1.1. IOEIOBaseIO モナド(The IO, EIO and BaseIO Monads)

現実世界と相互作用するプログラムで典型的に使われるモナドが2つあります:

  • IO のアクションは IO.Error 型の例外を投げるか、世界を変更することができます。

  • BaseIO のアクションは例外を投げることができませんが、世界を変更することができます。

この区別により、アクションの型シグネチャを見れば例外が発生するかどうかがわかります。 BaseIO アクションは必要に応じて自動的に IO に昇格します。

🔗def
IO : TypeType
🔗def
BaseIO : TypeType

An EIO monad that cannot throw exceptions.

IOBaseIO はどちらもエラーの型をパラメータとする EIO のインスタンスです。 IOEIO IO.Error として定義される一方、 BaseIOEIO Empty として定義されます。 Lean 以外のライブラリへのバインディングなど、状況によっては EIO をカスタムのエラー型で使用すると便利です。これによってそれらのアクションとそれ以外の IO アクションの間に境界を設けてエラーハンドリングされることが保証されます。

🔗def
EIO (ε : Type) : TypeType
🔗def
IO.lazyPure {α : Type} (fn : Unitα) : IO α
🔗def
BaseIO.toIO {α : Type} (act : BaseIO α) : IO α
🔗def
BaseIO.toEIO {α ε : Type} (act : BaseIO α)
    : EIO ε α
🔗def
EIO.toBaseIO {ε α : Type} (act : EIO ε α)
    : BaseIO (Except ε α)
🔗def
EIO.toIO {ε α : Type} (f : εIO.Error)
    (act : EIO ε α) : IO α
🔗def
EIO.toIO' {ε α : Type} (act : EIO ε α)
    : IO (Except ε α)
🔗def
IO.toEIO {ε α : Type} (f : IO.Errorε)
    (act : IO α) : EIO ε α

9.1.2. エラーとエラーハンドリング(Errors and Error Handling)

IO モナドにおけるエラー処理は、他の 例外モナド と同じ機能を使用します。特に、例外のスローとキャッチは MonadExceptOf 型クラス のメソッドを使用します。 IO で投げられる例外は IO.Error 型を持ちます。このコンストラクタは、ファイルが存在しないなど、ほとんどのオペレーティングシステムで発生する低レベルのエラーを表します。最もよく使われるエラーは userError で、これは他のすべてのケースをカバーし、問題を説明する文字列を含みます。

🔗inductive type
IO.Error : Type

Imitate the structure of IOErrorType in Haskell: https://hackage.haskell.org/package/base-4.12.0.0/docs/System-IO-Error.html#t:IOErrorType

Constructors

alreadyExists (filename : Option String) (osCode : UInt32)
    (details : String) : IO.Error
otherError (osCode : UInt32) (details : String) : IO.Error
resourceBusy (osCode : UInt32) (details : String) : IO.Error
resourceVanished (osCode : UInt32) (details : String)
    : IO.Error
unsupportedOperation (osCode : UInt32) (details : String)
    : IO.Error
hardwareFault (osCode : UInt32) (details : String)
    : IO.Error
unsatisfiedConstraints (osCode : UInt32) (details : String)
    : IO.Error
illegalOperation (osCode : UInt32) (details : String)
    : IO.Error
protocolError (osCode : UInt32) (details : String)
    : IO.Error
timeExpired (osCode : UInt32) (details : String) : IO.Error
interrupted (filename : String) (osCode : UInt32)
    (details : String) : IO.Error
noFileOrDirectory (filename : String) (osCode : UInt32)
    (details : String) : IO.Error
invalidArgument (filename : Option String) (osCode : UInt32)
    (details : String) : IO.Error
permissionDenied (filename : Option String)
    (osCode : UInt32) (details : String) : IO.Error
resourceExhausted (filename : Option String)
    (osCode : UInt32) (details : String) : IO.Error
inappropriateType (filename : Option String)
    (osCode : UInt32) (details : String) : IO.Error
noSuchThing (filename : Option String) (osCode : UInt32)
    (details : String) : IO.Error
unexpectedEof : IO.Error
userError (msg : String) : IO.Error
🔗def
IO.Error.toString : IO.ErrorString
🔗def
IO.ofExcept.{u_1} {ε : Type u_1} {α : Type}
    [ToString ε] (e : Except ε α) : IO α
🔗def
EIO.catchExceptions {ε α : Type} (act : EIO ε α)
    (h : εBaseIO α) : BaseIO α
🔗def
IO.userError (s : String) : IO.Error
例外のスローとキャッチ

このプログラムは制御フローに例外を使用して繰り返しパスワードを要求します。例外の構文は IO だけでなく、すべての例外モナドで使用できます。不正なパスワードが提供されると例外が投げられ、パスワードチェックを繰り返すループでキャッチされます。正しいパスワードが指定された場合、制御はチェックを通過してループを終了し、他の例外は再度投げられます。

def accessControl : IO Unit := do IO.println "What is the password?" let password ( IO.getStdin).getLine if password.trim != "secret" then throw (.userError "Incorrect password") else return def repeatAccessControl : IO Unit := do repeat try accessControl break catch | .userError "Incorrect password" => continue | other => throw other def main : IO Unit := do repeatAccessControl IO.println "Access granted!"

以下の入力で実行すると:

stdinpublicinfosecondtrysecret

このプログラムは以下を出力します:

stdoutWhat is the password?What is the password?What is the password?Access granted!

9.1.2.1. IO エラーの構成(Constructing IO Errors)

🔗def
IO.Error.mkUnsupportedOperation
    : UInt32StringIO.Error
🔗def
IO.Error.mkUnsatisfiedConstraints
    : UInt32StringIO.Error
🔗def
IO.Error.mkProtocolError
    : UInt32StringIO.Error
🔗def
IO.Error.mkResourceBusy
    : UInt32StringIO.Error
🔗def
IO.Error.mkResourceVanished
    : UInt32StringIO.Error
🔗def
IO.Error.mkNoSuchThing
    : UInt32StringIO.Error
🔗def
IO.Error.mkNoSuchThingFile
    : StringUInt32StringIO.Error
🔗def
IO.Error.mkEofError : UnitIO.Error
🔗def
IO.Error.mkPermissionDenied
    : UInt32StringIO.Error
🔗def
IO.Error.mkNoFileOrDirectory
    : StringUInt32StringIO.Error
🔗def
IO.Error.mkTimeExpired
    : UInt32StringIO.Error
🔗def
IO.Error.fopenErrorToString (gist fn : String)
    (code : UInt32) : Option StringString
🔗def
IO.Error.mkAlreadyExists
    : UInt32StringIO.Error
🔗def
IO.Error.mkInvalidArgument
    : UInt32StringIO.Error
🔗def
IO.Error.mkHardwareFault
    : UInt32StringIO.Error
🔗def
IO.Error.mkResourceExhausted
    : UInt32StringIO.Error
🔗def
IO.Error.mkInappropriateType
    : UInt32StringIO.Error
🔗def
IO.Error.mkOtherError
    : UInt32StringIO.Error
🔗def
IO.Error.otherErrorToString (gist : String)
    (code : UInt32) : Option StringString
🔗def
IO.Error.mkInvalidArgumentFile
    : StringUInt32StringIO.Error
🔗def
IO.Error.mkResourceExhaustedFile
    : StringUInt32StringIO.Error
🔗def
IO.Error.mkAlreadyExistsFile
    : StringUInt32StringIO.Error
🔗def
IO.Error.mkIllegalOperation
    : UInt32StringIO.Error
🔗def
IO.Error.mkPermissionDeniedFile
    : StringUInt32StringIO.Error
🔗def
IO.Error.mkInterrupted
    : StringUInt32StringIO.Error
🔗def
IO.Error.mkInappropriateTypeFile
    : StringUInt32StringIO.Error