Either 型? どんな型なんだろう?
エラーハンドリングをもっと楽にする方法ってないかしら?
本記事ではそんな疑問にお答えします。
Flutter のエラーハンドリングを楽にしてくれる、Either
型について解説します。
Either
型について紹介した上で、try - catch
でエラーハンドリングを行った場合のコードを改良します。
Either
を使って記述すると、以下の画像のように短いコードで端的に表すことができます。
ぜひ読んでみてください!
Either 型とは
解決したい課題
入力された文字列を数字に変換し、その数字の平方根を返すアプリケーションを考えます。
ユーザーが素直に数字を入力してくれればよいですが、
入力フォームに文字列を入力した際に、エラー(例外)が発生します。
ここで、エラーハンドリングが必要となります。
エラーハンドリングの一例がtry-catch
を使う方法です。
try {
number = double.parse(text);
} on FormatException catch (e) {
//エラー時の対応
}
try{}
内の処理を実行し、
例外が発生したらon FormatException catch (e) {}
内の処理が実行される、という方法です。
上のコードではdouble.parse()
のメソッドは()
内に
文字列が入力された場合にFormatException
という例外が発生するので、
それをcatch
で対処できるようになっています。
この方法自体に問題はないのですが、try-catch
を埋め込んだメソッドを作成する際に、少し困ることが起き得ます。
それは、正常時と例外時で違う型の結果を返したい時です。
今回のparse
のように、正常時にはparse
した結果の数値を返したい、
例外時には例外を返したい、といった場合、
メソッドの返り値を何で設定していいか困ります。
最終的にUIの表示をString
にしようとするのであれば、
メソッド内でString
への変換処理を記載することで返す型をString
に統一できますが、
メソッドが肥大化します。
また、メソッドで行うことが、
「メソッドで本来やりたいこと」+「Stringへの変換」
となり、実行内容が分散し読みにくくなってしまいます。
メソッド埋め込みをやめて、UIの記述文(build
メソッド内)でBuilder
等を使い、try-catch
を直接行うのも、build
メソッドが肥大化し、読みにくくなってしまいます。
「try-catch
をメソッドに埋め込みつつ、正常時と例外時で別々の型を返したい」
というのが今回解決したい課題となります。
Either型での解決
上記課題を解決してくれるのが、fpdartパッケージのEither
型です。
Either
型とは抽象クラスで、
正常時用にEither
型を継承したRight
型が、
例外時用に同じくEither
型を継承したLeft
型がそれぞれ用意されています。
Right
型、Left
型、共に内部に任意の型の値を保持することができます。
上記の、
「try-catch
をメソッドに埋め込みつつ、正常時と例外時で別々の型を返したい」
という課題については、Either
型を返り値として設定し、
正常時にはRight
型を、例外時にはLeft
型を返す、という方法で解決できます。
Either<FormatException, double> parseNumber(String text) {
try {
return Either.right(double.parse(text));
} on FormatException catch (e) {
return Either.left(e);
}
}
上記例では、正常時にはdouble
型を保持するRight
型の値を、
例外時にはFormatException
型を保持するLeft
型の値を返します。
まとめるとEither
型とは、返り値の型が2つの型どちらにもなりうる際に対応できる型です。
この後の基本的な使い方ではエラーハンドリングにより特化した使い方と、
Either型からの値の取り出し方について解説します。
基本的な使い方
以下のコードを修正していく形で解説していきます。
(先に紹介した比較画像の左側のコードです。)
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final TextEditingController _controller;
@override
void initState() {
_controller = TextEditingController();
_controller.text = '0';
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: 100,
child: TextField(
controller: _controller,
onChanged: (value) => setState(() {}),
),
),
Builder(
builder: (context) {
final double number;
try {
number = double.parse(_controller.text);
} on FormatException catch (e) {
return Text(
e.toString(),
style: const TextStyle(fontSize: 24),
);
}
return Text(
'sqrt($number) =${sqrt(number).toString()}',
style: const TextStyle(fontSize: 24),
);
},
),
],
),
),
);
}
}
準備
まず準備として、パッケージのインストールと、
Dartファイルへのインポート文の追加を行います。
パッケージのインストール
CLI(macならターミナル)で、自分のプロジェクトのルートにて
以下のコマンドを実行しパッケージをインストールします。
flutter pub add fpdart
パッケージのインポート
実装したいDartファイルの上部に以下のインポート文を追加し、
パッケージをインポートします。
一部クラスがmaterial
パッケージと競合するため、as
文でfp
という名前付けをしておきます。
import 'package:fpdart/fpdart.dart' as fp;
実装
Either型のメソッドを用いたtry-catch
「Either
型とは」の章では以下のコードを紹介しました。
Either<FormatException, double> parseNumber(String text) {
try {
return Either.right(double.parse(text));
} on FormatException catch (e) {
return Either.left(e);
}
}
このコードをEither
のtryCatch
メソッドで書き換えることができます。
Either<FormatException, double> parseNumber2(String text) {
return Either.tryCatch(
() => double.parse(text),
(e, s) => e as FormatException,
);
}
第1引数にてtry{}
文の中に書く処理を記述し、正常時に返したい値を返します。
第2引数にて例外時の処理を記述し、例外時に返したい値を返します。
(e
は例外時のObject
, s
はStackTrace
です。)
このメソッドをMyWidget
内、build
メソッドの後に追加します。
(Either
→fp.Either
)となることに注意です。
fp.Either<FormatException, double> parseNumber2(String text) {
return fp.Either.tryCatch(
() => double.parse(text),
(e, s) => e as FormatException,
);
}
Either型からの値の引き出し
Either
型からの値の引き出し方を解説します。
値を引き出す際にはmatch
メソッドを用いるのが便利です。
match
メソッドは、引数にてEither
型の値がRight
型だった際の関数と、Left
型だった際の関数を記述することで、共通の型を返すことのできるメソッドです。
Either.match<String>(
(l) => l.toString(),
(r) => 'sqrt($r) = ${sqrt(r).toString()}',
),
l,r
はそれぞれLeft
型, Right
型の中身の値を表します。
引数にてString
を返すメソッドを設定しています。match
メソッドを用いることで、それぞれの型ごとに別々の処理を行いつつ、
一つの型に変換することができます。
ベースコードのBuilder
ウィジェットを以下のウィジェットに置き換えてください。
Text(
parseNumber2(_controller.text).match<String>(
(l) => l.toString(),
(r) => 'sqrt($r) = ${sqrt(r).toString()}',
),
style: const TextStyle(fontSize: 24),
),
以上で書き換えは完了となります。
最終的なコードは以下のとおりです。
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fpdart/fpdart.dart' as fp;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final TextEditingController _controller;
@override
void initState() {
_controller = TextEditingController();
_controller.text = '0';
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: 100,
child: TextField(
controller: _controller,
onChanged: (value) => setState(() {}),
),
),
Text(
parseNumber2(_controller.text).match<String>(
(l) => l.toString(),
(r) => 'sqrt($r) = ${sqrt(r).toString()}',
),
style: const TextStyle(fontSize: 24),
),
],
),
),
);
}
fp.Either<FormatException, double> parseNumber2(String text) {
return fp.Either.tryCatch(
() => double.parse(text),
(e, s) => e as FormatException,
);
}
}
まとめ
本記事ではFlutter のエラーハンドリングを楽にしてくれる、Either
型について解説しました。
Either
型について紹介した上で、try - catch
でエラーハンドリングを行った場合のコードを改良します。
いかがだったでしょうか?
Either
型を用いることで、コードがスッキリと、見やすくなりました。
Either
型には他にもさまざまなメソッドが用意されています。
こちらにて紹介されていますので、興味ある方はぜひ見てみてください。
本記事があなたのアプリ開発の一助となれば幸いです。
参考
編集後記(2022/8/18)
Either
型についての記事でした。
今回は深く触れませんでしたが、
このEither
型が含まれるパッケージ、fpdart
は関数型プログラミングを補助するためのパッケージです。
関数型プログラミングとはデータと関数を分離して記述していくプログラミング手法です。
関数の組み合わせで記述していくことにより、
コードがシンプルに、読みやすくなる、というメリットがあります。
(詳しくは、こちらの記事を読んでみることをオススメします。)
意識しなければ気が付かないような内容ではありますが、
コードをよりシンプルにわかりやすくしたい、
といった時には使用を検討してみてもよいかと思います。
自分もまだまだ勉強中ですので、興味のある方は一緒に勉強しましょう!
週刊Flutter大学では、Flutterに関する技術記事、Flutter大学についての紹介記事を投稿していきます。
記事の更新情報はFlutter大学Twitterにて告知します。
ぜひぜひフォローをお願いいたします。