エラーモナド

提供: tezos-wiki
移動先: 案内検索

.. _error_monad:

エラーMonad[編集]

これは、「michelson-lang.com」のブログ記事から修正されています。

モナドに慣れていない場合は、数分でチュートリアルを読んでください。私はPhilip Wadlerのこの paper <http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf> __から個人的には多くを得ていますが、オンラインで入手できる他の大量のもの。あなたのために働くものを見つけてください。エラーモナドはモナドが行っているようにひどく恐ろしいことではないので、あなたがその要点を理解していると感じたら、戻って何が起こっているのかを理解できるかどうかを見てください。

以下の例では、多くのモナドが提供するいくつかの便利な操作を省略します。追加したい場合は、難しくありません。

==なぜあなたはエラーを要求していますか?

Tezosでは、不適切な入力によってノードをクラッシュさせることは望ましくありません。この可能性を回避するために、システムでエラー処理の例外を使用すべきでないと判断されました。代わりに、エラーモナドを使用します。この設計では、出力を使用する前にエラーを処理または伝達するよう強制されます。例外は依然として使用されることがありますが、これは主にクライアント側であり、内部エラーの場合のみです。

並行性のために使用するLwtライブラリにも組み込みます。これはエラーモナドと組み合わされ、コードベース全体にわたって再び使用されます。 Lwtモナドは他の言語の約束とよく似ています。

さらなる騒ぎをせずに、エラーモナドを書いてみましょう。

単純なエラーモナドのバージョン[編集]

ここには非常に単純なエラーモナドがあります。

..コード:: ocaml

モジュールエラー:sig
  タイプ 'a t
  (*タイプt *の値を作成します)
  戻り値: 'a - &gt; 'いや
  (*計算が失敗した場合*)
  値の誤差: 'a t
  (*エラーモナドの値に演算を適用する*)
  val(&gt;&gt;?): 'a t - &gt; ( 'a - &gt; b t) - &gt; 'b t(* bind *)
終了=構造体
  タイプ 'a t = Ok of' a |エラー
  return x = Ok x
  let error =エラー
  let(&gt;&gt ;?)の値func =
    ?と一致する値
    | Ok x - &gt; func x
    |エラー - &gt;エラー
終了

それで、これはTezosが使うものですか?実際には、後で使用する多くの構造が既に用意されています。基本的な考え方は、正しい値を返し、操作が失敗した場合はエラーを返すことです。エラーモジュールの外側では、実際にエラー値をイントロスペクトすることはできません。バインドを使用して、値の正確性/不正性のみをディスパッチできます。

ここで何が間違っていますか?

  • エラーケースに関する情報は報告できません
  • Tezos全体のエラーメッセージの品質を改善するために使用されるエラートレースは報告できません
  • いくつかのエラーは処理できず、実行を継続できません

若干の改善[編集]

エラーに説明文字列を含めることで、エラー報告を強化しましょう。今度はエラーとともにメッセージを報告することができます。これで十分ですか?あんまり。印刷の仕方に柔軟性はありません。エラートレースを作成することはできず、エラーを処理してプログラムの実行を再開することはできません。

..コード:: ocaml

モジュールエラー:sig
  タイプ 'a t
  戻り値: 'a - &gt; 'いや
  valエラー:文字列 - &gt; 'いや
  val(&gt;&gt;?): 'a t - &gt; ( 'a - &gt; b t) - &gt; 'b t(* bind *)
  val print_value:( 'a - &gt; string) - &gt; 'a t - &gt;単位
終了=構造体
  タイプ 'a t = Ok of' a |文字列のエラー
  return x = Ok x
  エラーs =エラーs
  let(&gt;&gt ;?)の値func =
    ?と一致する値
    | Ok x - &gt; func x
    |エラーs - &gt;エラーs
  let print_value func = function
    | Ok x - &gt; Printf.printf&quot;成功:%s \ n&quot; (func x)
    |エラーs - &gt; Printf.printf "エラー:%s \ n" s
終了

トレース[編集]

基本的な構造が完成したので、トレースを含めるためのメカニズムを追加できます。メモとして、上記のエラータイプは、OCaml標準ライブラリの result タイプです。トレースは、エラーメッセージのリストです。失敗したと思われる呼び出しがあり、一連のエラーを返す場合は、その結果を trace 関数にラップすることができます。その呼び出しが失敗すると、追加のエラーが追加されます。

..コード:: ocaml

モジュールエラー:sig
  タイプ 'a t
  戻り値: 'a - &gt; 'いや
  valエラー:文字列 - &gt; 'いや
  val(&gt;&gt;?): 'a t - &gt; ( 'a - &gt; b t) - &gt; 'b t(* bind *)
  val print_value:( 'a - &gt; string) - &gt; 'a t - &gt;単位
  valトレース:文字列 - &gt; 'a t - &gt; 'いや
終了=構造体
  タイプ 'a t =(' a、string list)result
  return x = Ok x
  エラーとする=エラー[s]
  let(&gt;&gt ;?)の値func =
    ?と一致する値
    | Ok x - &gt; func x
    |エラーエラー - &gt;エラーエラー
  let print_value func = function
    | Ok x - &gt; Printf.printf&quot;成功:%s \ n&quot; (func x)
    |エラー[s] - &gt; Printf.printf "エラー:%s \ n" s
    |エラーエラー - &gt; Printf.printf&quot;エラー:\ t%s \ n&quot; (String.concat "\ n \ t"エラー)
  トレースエラー=機能させる
    | Ok x - &gt; OK x
    |エラーエラー - &gt;エラー(error :: errors)
終了

よりわかりやすいメッセージ[編集]

トレースはうまくいきますが、メッセージにもっと興味深いデータを格納できるようにしたいと考えています。これを行うには、拡張可能なバリアント型を使用します。拡張可能なバリアントを使用すると、排他性チェックの代償としてバリアントタイプに新しいケースを追加することができます。この作業をうまく行うには、2つの新しいメカニズムが必要になります。 1つはエラー登録方式です。実際のエラーモナドでは、データエンコーディングモジュールが必要です。これは、すべてのデータがTezosでエンコード/デコードされる方法です。このモジュールは、将来の投稿の主題であるはずのコードベースのもう少し複雑な部分です。任意の新しいエラーを宣言できるので、エラーごとにプリンタを追加する方法があります。

新しいエラーハンドラを追加するときは、 register_handler 関数を使用します。この関数はエラーを受け取り、文字列オプションを返す関数をとります。これらの関数は次のようになります。

..コード:: ocaml

 type error + =文字列のExplosion_failure * int ;;

register_error
  (関数
    | Explosion_failure(s、i) - &gt;
        いくつか(Printf.sprintf "すべてが展開されました:%s at%d" s)
    | _&gt;なし)

また、 error 関数の名前を fail に変更します。これは、実際のErrormonadモジュールで使用されている規則です。また、必要に応じてディスパッチできるように 't 型を公開しています。これはTezosコードベースで数回使用されます。

..コード:: ocaml

モジュールエラー:sig
  タイプエラー= ..
  タイプ 'a t =(' a、エラーリスト)結果
  戻り値: 'a - &gt; 'いや
  val失敗:error - &gt; 'いや
  val(&gt;&gt;):( 'a - &gt; b t) - &gt; 'a t - &gt; 'b t(* bind *)
  val print_value:( 'a - &gt; string) - &gt; 'a t - &gt;単位
  valトレース:error - &gt; 'a t - &gt; 'いや
終了=構造体
  タイプエラー= ..
  タイプ 'a t =(' a、エラーリスト)結果
  失敗エラー=エラー[エラー]
  return x = Ok x
  let(&gt;&gt ;?)func = function
    | Ok x - &gt; func x
    |エラーエラー - &gt;エラーエラー
  let registered = ref []
  let register_error handler =
    登録済み= =(ハンドラー::!登録済み)
  let default_handler error =
    &quot;登録されていないエラー&quot; ^ Obj。(extension_name @@ extension_constructorエラー)
  let to_string error =
    recをfind_handler = functionにする
      | [] - &gt; default_handlerエラー
      |ハンドラ::ハンドラ - &gt;
          マッチハンドラのエラーを
            |なし - &gt; find_handlerハンドラ
            |いくつかのs - &gt; s
          終わり
    in find_handler!登録済み
  let print_value func = function
    | Ok x - &gt; Printf.printf&quot;成功:%s \ n&quot; (func x)
    |エラー[s] - &gt; Printf.printf "エラー:%s \ n" (to_string s)
    |エラーエラー - &gt; Printf.printf&quot;エラー:\ t%s \ n&quot; (String.concat "\ n \ t"(List.map to_string errors))
  トレースエラー=機能させる
    | Ok x - &gt; OK x
    |エラーエラー - &gt;エラー(error :: errors)
終了

Lwt.t をミックスに配置する[編集]

Tezosはスレッドのために Lwtライブラリ<https://ocsigen.org/lwt/3.2.1/manual/manual> __を使用します。 Lwtモナドは、エラーモナドモジュールと混在しています。これには、追加のコンビネータを追加し、Lwtからいくつかの関数を再エクスポートする必要があります。

また、Tezosコードベースで使用されているように、 t のタイプを tzresult に変更します。

..コード:: ocaml

モジュールエラー:sig
  タイプエラー= ..
  タイプ 'a tzresult =(' a、エラーリスト)結果
  大丈夫です: 'a - &gt; 'tzresult
  戻り値: 'a - &gt; 'tzresult Lwt.t
  val error:エラー - &gt; 'tzresult
  val失敗:error - &gt; 'tzresult Lwt.t
  val(&gt;&gt;?): 'a tzresult - &gt; ( 'a - &gt; b tzresult) - &gt; 'b tzresult(* bind *)
  val(&gt;&gt; =): 'a tzresult Lwt.t - ( 'a - &gt; b tzresult Lwt.t) - &gt; 'b tzresult Lwt.t
  val(&gt; =): 'a Lwt.t - ( 'a - &gt; b Lwt.t) - &gt; 'b Lwt.t
  val print_value:( 'a - &gt; string) - &gt; 'tzresult Lwt.t - &gt;単位Lwt.t
  valトレース:error - &gt; 'tzresult Lwt.t - &gt; 'tzresult Lwt.t
終了=構造体
  タイプエラー= ..
  タイプ 'a tzresult =(' a、エラーリスト)結果
  失敗エラー= Lwt.return(エラー[エラー])
  エラーエラー=(エラー[エラー])
  let ok x = Ok x
  return x = Lwt.return(ok x)
  let(&gt;&gt ;?)の値func =
    ?と一致する値
    | Ok x - &gt; func x
    |エラーエラー - &gt;エラーエラー
  let(&gt; =)= Lwt.bind
  let(&gt; =?)の値func =
    値>&gt; =関数
    | Ok x - &gt; func x
    |エラーエラー - &gt; Lwt.return(エラーエラー)
  let registered = ref []
  let register_error handler =
    登録済み= =(ハンドラー::!登録済み)
  let default_handler error =
    &quot;登録されていないエラー&quot; ^ Obj。(extension_name @@ extension_constructorエラー)
  let to_string error =
    recをfind_handler = functionにする
      | [] - &gt; default_handlerエラー
      |ハンドラ::ハンドラ - &gt;
          マッチハンドラのエラーを
            |なし - &gt; find_handlerハンドラ
            |いくつかのs - &gt; s
          終わり
    in find_handler!登録済み
  let print_value func value =
    値>&gt; =楽しい値 - &gt;
    一致する値を
      | Ok x - &gt; Printf.printf&quot;成功:%s \ n&quot; (func x)
      |エラー[s] - &gt; Printf.printf "エラー:%s \ n" (to_string s)
      |エラーエラー - &gt; Printf.printf&quot;エラー:\ t%s \ n&quot; (String.concat "\ n \ t"(List.map to_string errors))
    終わり; Lwt.return()
  トレースエラー値=
    値>&gt; =関数
    | Ok x - &gt;戻り値x
    |エラーエラー - &gt; Lwt.return(Error(error :: errors))
終了

実際のTezosエラーモナド[編集]

実際のTezosのエラーモナドはいくつかを追加します。まず、エラーには3つのカテゴリがあります。

  • :リテラル: \ Temporary` - 契約の残高が低すぎて意図した操作を実行できないなど、将来有効な操作の結果生じるエラーです。これは、契約の残高にさらに追加することで修正できます。
  • :リテラル: \ Branch` - チェーンの1つのブランチで発生しますが、別のブランチでは発生しないエラーです。たとえば、古いプロトコルバージョンまたは将来のプロトコルバージョンの操作を受け取ります。
  • :リテラル: \ Permanent` - 操作が決して有効にならないために回復できないエラー。たとえば、無効な?表記

登録スキームでは、データエンコーディングも使用されます。以下は、バリデーター</api / odoc / tezos-node-shell / Tezos_node_shell / Validator / index.html&gt; __の例です。

..コード:: ocaml

 register_error_kind
    `永久
    ?id:&quot; validator.wrong_level&quot;
    ?タイトル:&quot;間違ったレベル&quot;
    ?description:&quot;ブロックレベルは期待されたものではありません&quot;
    ?pp:(楽しいppf(e、g) - &gt;
        Format.fprintf ppf
          "宣言されたレベル%1dは%1dではありません" g e)
    Data_encoding。(obj2
                     (req "expected" int32)
                     (req "提供された" int32))
    (関数Wrong_level(e、g) - > Some(e、g)| _ - > None)
    (fun(e、g) - &gt; Wrong_level(e、g))

エラーには、カテゴリ、ID、タイトル、説明、およびエンコーディングが使用されます。エンコーディングタイプのオプションの値にエラーを返す関数と、エンコードされたタイプの値を取得してエラー値を作成する関数を指定する必要があります。きれいなプリンタを任意に指定することもできますが、省略することもできます。

実際のエラーモナドとそのトレース機能は、この関数では契約を解析します:

..コード:: ocaml

 let parse_script
  :?type_logger:(int *(Script.expr list * Script.expr list) - &gt; unit) - &gt;
  コンテキスト - &gt; Script.storage - &gt; Script.code - &gt; ex_script tzresult Lwt.t
  = fun?type_logger ctxt
    { ストレージ; storage_type = init_storage_type}
    {コード; arg_type; ret_type; storage_type} - &gt;
    トレース
      (Ill_formed_type(some "parameter"、arg_type))
      (Lwt.return(parse_ty arg_type))&gt; =? fun(Ex_ty arg_type) - &gt;
    トレース
      (Ill_formed_type(いくつかの "return"、ret_type))
      (Lwt.return(parse_ty ret_type))&gt;&gt; =?楽しみ(Ex_ty ret_type) - &gt;
    トレース
      (Ill_formed_type(いくつかの "初期ストレージ"、init_storage_type))
      (Lwt.return(parse_ty init_storage_type))&gt; =? fun(Ex_ty init_storage_type) - &gt;
    トレース
      (Ill_formed_type(いくつかの "storage"、storage_type))
      (Lwt.return(parse_ty storage_type))&gt; =?楽しい(Ex_ty storage_type) - &gt;
    arg_type_full = Pair_t(arg_type、storage_type)を
    ret_type_full = Pair_t(ret_type、storage_type)を
    Lwt.return(ty_eq init_storage_type storage_type)&gt;&gt; =?楽しい(Eq _) - &gt;
    トレース
      (Ill_typed_data(None、storage、storage_type))
      (parse_data?type_logger ctxt storage_type storage)&gt; =?楽しいストレージ - &gt;
    トレース
      (Ill_typed_contract(code、arg_type、ret_type、storage_type、[]))
      (parse_returning(Toplevel {storage_type})ctxt?type_logger arg_type_full ret_type_full code)
    &gt; =?楽しいコード - &gt;
    return(Ex_script {code; arg_type; ret_type; storage; storage_type})

型チェックプロセスの各特定の型エラーは、プログラムのどの部分が不正であったかを説明する、より一般的なエラーに包まれています。これにより、エラー報告が改善されます。また、関数間で使用されるバインド演算子が表示され、エラーが発生しない場合にのみ処理を継続することができます。この関数は、 Lwt モナドでも動作します。これは、大部分がエラーモナドによって隠されています。