公開技術情報

[English] [Japanese]

10.6. エラーハンドリング編

ここでは LuneScript のエラーハンドリングについて説明します。

エラーハンドリング

現状の LuneScript は、大域脱出をサポートしていません。

何等かのエラーが発生した場合、関数の戻り値でエラーを通知する必要があります。

エラーの通知方法としては、大きく次の 2 つがあります。

  • nilable を利用し、エラーを nil で表現する。
  • alge 型 __Ret を利用する。

nilable を利用する場合

関数の戻り値に nilable を利用することで、 その関数のエラーを表現できます。

// @lnsFront: ok
fn func(flag:bool): str! {
   if not flag {
       return nil;
   }
   return "hoge";
}

上記の関数は処理が成功した場合 str を返し、 失敗した場合は nil を返します。

この方法は、実現が簡単であるという利点がありますが、 どういったエラーが発生したのかを示す情報を示せないという欠点があります。

この欠点をカバーするには、次のようにエラー内容を示す多値を返すことで、 この欠点をカバーできます。

// @lnsFront: ok
fn func(flag:bool): str!, str! {
   if not flag {
       return nil, "error";
   }
   return "hoge", nil;
}

alge 型 __Ret を利用する場合

次の alge 型 __Ret を利用すると、多値返却を使用せずに、エラーの内容を示すことができます。

// @lnsFront: skip
alge __Ret<T1,T2> {
  Ok(val:T1),
  Err(val:T2),
}

alge 型 __Ret は、 v1.5.2 から利用可能です。

たとえば、前述の func() を __Ret を使って表現すると次になります。

// @lnsFront: ok
fn func(flag:bool): __Ret<str,str> {
   if not flag {
       return .Err("error");
   }
   return .Ok( "hoge" );
}

! 演算子

エラーを返す関数において、 その関数の処理内で実行するサブ関数がエラーを返す場合、 サブ関数毎にエラーの判定を行なう必要があり、処理が煩雑になります。

例えば、次はサブ関数 sub() があり、 この関数を 3 回コールする func() があります。

func() は、3 回エラー判定処理が必要になります。

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : int! {
      if val > 3 {
	 return nil;
      }
      return val;
   }
   pub fn func( val:int ) : str! {
      let! work1 = self.sub( val + 1 ) {  // check error
         return nil;
      };
      let! work2 = self.sub( val + 2 ) { // check error
         return nil;
      };
      let! work3 = self.sub( val + 3 ) { // check error
         return nil;
      };
      return "%s" (work1 + work2 + work3);
   }
}
let hoge = new Hoge();
print( "%s" (hoge.func( 0 )) );
print( "%s" (hoge.func( 1 )) );

! 演算子は、上記のエラーチェックを簡略化するものです。

! 演算子の仕様を説明をする前に、 上記のコードを ! 演算子を使って書き換えたコードがどうなるかを見た方が イメージし易いので、次にコードを示します。

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : int! {
      if val > 3 {
	 return nil;
      }
      return val;
   }
   pub fn func( val:int ) : str! {
      let work1 = self.sub( val + 1 )!;
      let work2 = self.sub( val + 2 )!;
      let work3 = self.sub( val + 3 )!;
      return "%s" (work1 + work2 + work3);
   }
}
let hoge = new Hoge();
print( "%s" (hoge.func( 0 )) );
print( "%s" (hoge.func( 1 )) );

! を使った方のコードは、エラー判定処理が簡略化されていることが分かると思います。

2つの値を返す場合は、次のようになります。

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : int!, str! {
      if val > 3 {
	 return nil, "err";
      }
      return val, nil;
   }
   pub fn func( val:int ) : int!, str! {
      let work1 = self.sub( val + 1 )!;
      let work2 = self.sub( val + 2 )!;
      let work3 = self.sub( val + 3 )!;
      return work1 + work2 + work3, nil;
   }
}
let hoge = new Hoge();
print( "%s,%s" ( hoge.func( 0 )) );
print( "%s,%s" ( hoge.func( 1 )) );

nilable ではなく __Ret を使った場合は以下になります。

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : __Ret<int,__Er> {
      if val > 3 {
	 return .Err( __serr( "err" ) );
      }
      return .Ok( val );
   }
   pub fn func( val:int ) : __Ret<str,__Er> {
      let work1 = self.sub( val + 1 )!;
      let work2 = self.sub( val + 2 )!;
      let work3 = self.sub( val + 3 )!;
      return .Ok( "%s" (work1 + work2 + work3) );
   }
}
let hoge = new Hoge();
for count = 0, 1 {
   match hoge.func( count ) {
      case .Ok( val ) {
         print( "ok", val );
      }
      case .Err( err ) {
         print( "ng", err.$txt );
      }
   }
}

! 演算子の仕様

! 演算子を利用するには、 次の仕様を満す必要があります。

  • ! 演算子の直前の値の型 T が nilable か __Ret 型である。

    • ! T が nilable の場合、 T に続く型 T2 があっても良い。
  • ! 演算子を利用している statement を含む関数の戻り値の型 R が次を満す。

    • T が nilable の場合、R も nilable である。

      • T2 がある場合、R2 があり、 R2 は T2 を代入可能である。
    • T が __Ret<T1,T2> の場合、 R は __Ret<R1,R2> である。

      • ここで R2 は T2 を代入可能な型でなければならない。
      • T1, R1 は任意の型で良い。

上記の pub fn func( val:int ) : int!, str! を使ったサンプルで説明すると、

  • func() の戻り値は int!, str! である。
  • ! 演算子の直前の値 sub() の型は int!, str! である。
  • つまり T1 = int!, T2 = str! , R1 = int!, R2 = str! となる。
  • よって、 T2=R2=str! となり、 ! 演算子が使用可能となる。

また、上記の __Ret を使ったサンプルで言うと、

  • func() の戻り値は __Ret<str,__Er> である。
  • ! 演算子の直前の値 sub() の型は __Ret<int,__Er> である。
  • つまり T1 = int, T2 = __Er , R1 = str, R2 = __Er となる。
  • よって、 T2=R2=__Er となり、 ! 演算子が使用可能となる。

上記を仕様を満す場合、 ! 演算子は次の動作になります。

  • ! 演算子の直前の値が .Err の場合、 そのコードを含む関数の戻り値にその値をセットして return する。
  • ! 演算子の直前の値が .Ok の場合、 ! 演算子の評価結果を .Ok の値として処理を続ける。

また、 ! 演算子 を利用可能な箇所は、../ebnf に示す次の statement に限ります。

  • stmt_exp
  • if
  • decl_var

上記以外の statement については、随時対応します。

__Er 型

__Ret のエラー時の情報を示す型として __Er 型を追加しています。

この型は次の interface です。

// @lnsFront: skip
pub interface __Er {
   fn get_txt():str;
}

また、 __Er 型のインスタンスを生成する関数として、 次の __serr() 関数を用意しています。

// @lnsFront: skip
pub fn __err(mess:str): __Er;

タプルを利用した多値返却関数のエラーハンドリング

タプルを利用することで、多値返却関数のエラーハンドリングが可能です。

// @lnsFront: ok
fn sub(flag:bool) : (int,str)!,str! {
   if flag {
      return (= 1,"abc"), nil;
   }
   return nil, "err";
}

fn func(flag:bool) : (int,str)!,str! {
   let val1, val2 = sub(flag)!...;
   let val3, val4 = sub(flag)!...;
   print( val1, val2, val3, val4 );
   return (= val1 + val3, val2 .. val4 ), nil;
}

foreach cond in [true,false] {
   let tuple, err = func( cond );
   when! tuple {
      let val1,val2 = tuple...;
      print( "ok", val1, val2 );
   } else {
      print( "ng", err );
   }
}

__Ret を利用した場合

__Ret は Ok と Err との値を表現する alge 型です。 どちらも Ok も Err も保持できるのは一つの型の値です。

LuneScript は多値返却を利用できますが、 エラーハンドリング目的で利用する場合は __Ret 一つしか返せません。

そこで、 __Ret で保持する型にタプルを利用することで、 多値返却と同じ感覚でエラーハンドリングを行なえます。

以下は、 __Ret とタプルを組み合わせた処理のサンプルです。

func() 内で sub() を実行しています。 この時に ! を使用してエラーの移譲を行なっていつつ、 タプルを使って多値返却と同等の処理を実現しています。

// @lnsFront: ok
fn sub(flag:bool) : __Ret<(int,str),__Er> {
   if flag {
      return .Ok( (= 1,"abc") );
   }
   return .Err( __serr( "err" ));
}

fn func(flag:bool) : __Ret<int,__Er> {
   let val1, val2 = sub(flag)!...;
   let val3, val4 = sub(flag)!...;
   print( val1, val2, val3, val4 );
   return .Ok( val1 + val3 );
}

foreach cond in [true,false] {
   match func( cond ) {
      case .Ok( val ) {
         print( "ok", val );
      }
      case .Err( val ) {
         print( "ng", val.$txt );
      }
   }
}