ある関数を実行した時、「実行が成功したか」と「実行結果」が欲しい場合がよくあります。
C#の時には、タプルを使って (bool:isSuccess, string:result) を返すなどしていました。
TypeScriptでは、返却値の型をUnionにできるので、Errorまたはstring型を返すようにすることができます。
テキストファイルを読み取って、読み取れればその内容を返し、読み取れなければエラーを返す関数の例です
import {promises as fs} from 'fs' // stringまたはErrorを返す関数 const readFile = async (fileName:string): Promise<string | Error> => { try { const data = await fs.readFile(fileName,'utf-8') return data } catch(e) { if(e instanceof Error) { return e } else { return new Error('failed') } } } const main = async ()=> { const result = await readFile('./input.txt') if(result instanceof Error) // エラーかどうかを判別 { console.log('読み取りに失敗しました') } else { console.log(`ファイルの内容は${result}です`) } } main()
これで問題なく動くのですが、エラーが発生したかどうかの判別をもっとスマートにするため、Either/TaskEitherを導入します。
npm i fp-ts または yarn add fp-ts
import {promises as fs} from 'fs' import * as E from 'fp-ts/Either' import * as TE from "fp-ts/TaskEither"; const readFile = (fileName: string): TE.TaskEither<Error, string> => async () => { try { const data = await fs.readFile(fileName, 'utf-8') return E.right(data) } catch (e) { if (e instanceof Error) { return E.left(e) } else { return E.left(new Error('failed')) } } } const main = async() =>{ const result = await readFile('./input.txt')() if(E.isLeft(result)) { console.log('読み取りに失敗しました') } else { console.log(`ファイルの内容は${result.right}です`) } } main()
まず返却値の型が TaskEither
EitherはLeftとRightのどちらかの値を持ちますが、LeftにError、Rightに実行結果となるstringを持つように定義しています。
これを実行した結果がLeftかRightかを判別するために、EitherのisLeft/isRightを使っています。
isLeftならエラーとして処理して、その後は型の絞り込みによりRightだということが保証されるので、rightの値を使って処理をしています。
さらに、TaskEitherのtryCatchを使うと、TaskEitherを生成することができます。
tryCatchの第一引数に正常時に返すもの(=Right)を入れて、第二引数に異常時に返すもの(=Left)を入れます。
const readFile = (fileName: string):TE.TaskEither<Error,string> => TE.tryCatch( // 正常時に返すもの async () => { return await fs.readFile(fileName, 'utf-8') }, // 異常時に返すもの (e) => { if(e instanceof Error) { return e } else { return new Error('failed') } } )
関数型言語のことを全く知らないので詳しくはわからないのですが、Rightが正常な結果(Right = 正しい?)という扱いになるみたいです。(このあとめちゃくちゃHaskellの入門書を物色した)