fp-tsの動画の内容の文字化 - 2

fp-tsのtutorial動画があるが
中身を文字で見たかったが　それを公開しているところがなかったので書いてみようと思った
※あまりにも長くなったので分割

# 動画
https://youtube.com/@MrFunctor

# 前回
https://qiita.com/kuangzhixitian3/items/6d795fe231c0a6df48ac

## Chapter 3: Either
https://www.youtube.com/watch?v=o91UKmLwBOk&list=PLUMXrUa_EuePN94nJ2hAui5nWDj8RO3lH&index=6

### OptionとEitherの比較

```plantuml
card None as c1
card "Left<E>" as c2

c1 -> c2 
```

<br>

```plantuml
card "Some<A>" as c1
card "Right<A>" as c2

c1 -> c2 
```

<br>

```plantuml
card "Either<E, A>" as n1
card "Left<E>" as n2
card "Right<A>" as n3
title "Either<E, A> = Left<E> | Right<A>"
n1 -- n2
n1 -- n3
```

### Eitherを利用するやり方

```plantuml
node "account1_オブジェクト" as n1
card "balance" as n2 {
card 70
}
card "frozen" as n3 {
card false
}
n1 -- n2
n1 -- n3
```

```plantuml
card "50" as c1
card "{ balance: 70, frozen: false }" as c2
card "E.right({ \n   balance: 20, \n   frozen: false \n})" as c3

c1 --> c3 
c2 --> c3 : ""pay""
```

<br>

```plantuml
node "account2_オブジェクト" as n1
card "balance" as n2 {
card 30
}
card "frozen" as n3 {
card false
}
n1 -- n2
n1 -- n3
```

```plantuml
card "50" as c1
card "{ balance: 30, frozen: false }" as c2
card "E.left({ \n   type: 'NotEnoughBalance', \n   message: 'Cannot pay 50 with a balance of 30!' \n})" as c3

c1 --> c3 
c2 --> c3 : ""pay""
```

<br>

```plantuml
node "account3_オブジェクト" as n1
card "balance" as n2 {
card 100
}
card "frozen" as n3 {
card true
}
n1 -- n2
n1 -- n3
```

```plantuml
card "50" as c1
card "{ balance: 100, frozen: true }" as c2
card "E.left({ \n   type: 'AccountFrozen', \n   message: 'Cannot pay with a frozen account!' \n})" as c3

c1 --> c3 
c2 --> c3 : ""pay""
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('Either - Eitherを利用するやり方', () => {
    type Account = Readonly<{
      balance: number
      frozen: boolean
    }>
    type AccountFrozen = Readonly<{
      type: 'AccountFrozen'
      message: string
    }>
    type NotEnoughBalance = Readonly<{
      type: 'NotEnoughBalance'
      message: string
    }>

    const pay =
      (amount: number) =>
      (account: Account): E.Either<NotEnoughBalance | AccountFrozen, Account> =>
        account.frozen
          ? E.left({
              type: 'AccountFrozen',
              message: 'Cannot pay with a frozen account!'
            })
          : account.balance < amount
            ? E.left({
                type: 'NotEnoughBalance',
                message: `Cannot pay ${amount} with a balance of ${account.balance}!`
              })
            : E.right({
                ...account,
                balance: account.balance - amount
              })

    const account1: Account = {
      balance: 70,
      frozen: false
    }
    const account2: Account = {
      balance: 30,
      frozen: false
    }
    const account3: Account = {
      balance: 100,
      frozen: true
    }

    console.log(pipe(account1, pay(50)))
    console.log(E.right({ balance: 20, frozen: false }))
    console.log('-------------')

    console.log(pipe(account2, pay(50)))
    console.log(
      E.left({
        type: 'NotEnoughBalance',
        message: 'Cannot pay 50 with a balance of 30!'
      })
    )
    console.log('-------------')

    console.log(pipe(account3, pay(50)))
    console.log(
      E.left({
        type: 'AccountFrozen',
        message: 'Cannot pay with a frozen account!'
      })
    )
    console.log('-------------')
  })
})
```

実行結果
```ts
{ _tag: 'Right', right: { balance: 20, frozen: false } }
{ _tag: 'Right', right: { balance: 20, frozen: false } }
-------------
{
  _tag: 'Left',
  left: {
    type: 'NotEnoughBalance',
    message: 'Cannot pay 50 with a balance of 30!'
  }
}
{
  _tag: 'Left',
  left: {
    type: 'NotEnoughBalance',
    message: 'Cannot pay 50 with a balance of 30!'
  }
}
-------------
{
  _tag: 'Left',
  left: {
    type: 'AccountFrozen',
    message: 'Cannot pay with a frozen account!'
  }
}
{
  _tag: 'Left',
  left: {
    type: 'AccountFrozen',
    message: 'Cannot pay with a frozen account!'
  }
}
-------------
```

### エラーをthrowするやり方

```plantuml
node "account1_オブジェクト" as n1
card "balance" as n2 {
card 70
}
card "frozen" as n3 {
card false
}
n1 -- n2
n1 -- n3
```

```plantuml
card "50" as c1
card "{ balance: 70, frozen: false }" as c2
card "{ balance: 20, frozen: false }" as c3

c1 --> c3 
c2 --> c3 : ""pay""
```

<br>

```plantuml
node "account2_オブジェクト" as n1
card "balance" as n2 {
card 30
}
card "frozen" as n3 {
card false
}
n1 -- n2
n1 -- n3
```

```plantuml
card "50" as c1
card "{ balance: 30, frozen: false }" as c2
card "NotEnoughBalanceError" as c3
title 
<color:OrangeRed> 実行時エラーが発生する
end title

c1 --> c3 
c2 --> c3 : ""pay""
```

<br>

```plantuml
node "account3_オブジェクト" as n1
card "balance" as n2 {
card 100
}
card "frozen" as n3 {
card true
}
n1 -- n2
n1 -- n3
```

```plantuml
card "50" as c1
card "{ balance: 100, frozen: true }" as c2
card "AccountFrozenError" as c3
title 
<color:OrangeRed> 実行時エラーが発生する
end title

c1 --> c3 
c2 --> c3 : ""pay""
```

```ts
import { describe, it } from 'vitest'
import { pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('Either - エラーをthrowするやり方', () => {
    type Account = Readonly<{
      balance: number
      frozen: boolean
    }>

    class AccountFrozenError extends Error {
      type = 'AccountFrozen'
      public constructor(message?: string) {
        super(message)
      }
    }
    class NotEnoughBalanceError extends Error {
      type = 'NotEnoughBalance'
      public constructor(message?: string) {
        super(message)
      }
    }

    const pay = (amount: number) => (account: Account) => {
      if (account.frozen) {
        throw new AccountFrozenError('Cannot pay with a frozen account!')
      } else if (account.balance < amount) {
        throw new NotEnoughBalanceError(
          `Cannot pay ${amount} with a balance of ${account.balance}!`
        )
      } else {
        return {
          ...account,
          balance: account.balance - amount
        }
      }
    }

    const account1: Account = {
      balance: 70,
      frozen: false
    }
    const account2: Account = {
      balance: 30,
      frozen: false
    }
    const account3: Account = {
      balance: 100,
      frozen: true
    }

    console.log(pipe(account1, pay(50)))
    try {
      console.log(pipe(account2, pay(50)))
    } catch (e) {
      console.log(e.message)
    }
    try {
      console.log(pipe(account3, pay(50)))
    } catch (e) {
      console.log(e.message)
    }
  })
})
```

実行結果
```ts
{ balance: 20, frozen: false }
Cannot pay 50 with a balance of 30!
Cannot pay with a frozen account!
```

### E.fold(onLeft, onRight)

```plantuml
node "cart1_オブジェクト" as n1
card "items" as n2 {
card "ice cream"
card "waffle"
card "candy"
}
card "total" as n3 {
card 50
}
n1 -- n2
n1 -- n3
```

```plantuml
node "account1_オブジェクト" as n1
card "balance" as n2 {
card 70
}
card "frozen" as n3 {
card false
}
n1 -- n2
n1 -- n3
```

<br>

```plantuml
card "cart1" as c1
card "account1" as c2
card "{ balance: 20, frozen: false }" as c3

c1 --> c3 
c2 --> c3 : ""checkout""
```

-----------------------------------------------

```plantuml
node "cart1_オブジェクト" as n1
card "items" as n2 {
card "ice cream"
card "waffle"
card "candy"
}
card "total" as n3 {
card 50
}
n1 -- n2
n1 -- n3
```

```plantuml
node "account2_オブジェクト" as n1
card "balance" as n2 {
card 30
}
card "frozen" as n3 {
card false
}
n1 -- n2
n1 -- n3
```

<br>

```plantuml
card "cart1" as c1
card "account2" as c2
card "Cannot pay 50 with a balance of 30!" as c3

c1 --> c3 
c2 --> c3 : ""checkout""
```

-----------------------------------------------

```plantuml
node "cart1_オブジェクト" as n1
card "items" as n2 {
card "ice cream"
card "waffle"
card "candy"
}
card "total" as n3 {
card 50
}
n1 -- n2
n1 -- n3
```

```plantuml
node "account3_オブジェクト" as n1
card "balance" as n2 {
card 100
}
card "frozen" as n3 {
card true
}
n1 -- n2
n1 -- n3
```

<br>

```plantuml
card "cart1" as c1
card "account3" as c2
card "Cannot pay with a frozen account!" as c3

c1 --> c3 
c2 --> c3 : ""checkout""
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('Either - E.fold(onLeft, onRight)', () => {
    type Cart = Readonly<{
      items: string[]
      total: number
    }>
    type Account = Readonly<{
      balance: number
      frozen: boolean
    }>
    type AccountFrozen = Readonly<{
      type: 'AccountFrozen'
      message: string
    }>
    type NotEnoughBalance = Readonly<{
      type: 'NotEnoughBalance'
      message: string
    }>

    const pay =
      (amount: number) =>
      (account: Account): E.Either<NotEnoughBalance | AccountFrozen, Account> =>
        account.frozen
          ? E.left({
              type: 'AccountFrozen',
              message: 'Cannot pay with a frozen account!'
            })
          : account.balance < amount
            ? E.left({
                type: 'NotEnoughBalance',
                message: `Cannot pay ${amount} with a balance of ${account.balance}!`
              })
            : E.right({
                ...account,
                balance: account.balance - amount
              })

    const checkout_sample = (cart: Cart) => (account: Account) =>
      pipe(
        account,
        pay(cart.total),
        E.fold(
          (e) => 'Handle error...',
          (a) => 'handle success...'
        )
      )
    const checkout = (cart: Cart) => (account: Account) =>
      pipe(
        account,
        pay(cart.total),
        E.foldW(
          (e) => e.message,
          (a) => a
        )
      )

    const account1: Account = {
      balance: 70,
      frozen: false
    }
    const account2: Account = {
      balance: 30,
      frozen: false
    }
    const account3: Account = {
      balance: 100,
      frozen: true
    }

    const cart1: Cart = {
      items: ['ice cream', 'waffle', 'candy'],
      total: 50
    }

    console.log(checkout_sample(cart1)(account1))
    console.log(checkout(cart1)(account1))
    console.log('-----------')
    console.log(checkout_sample(cart1)(account2))
    console.log(checkout(cart1)(account2))
    console.log('-----------')
    console.log(checkout_sample(cart1)(account3))
    console.log(checkout(cart1)(account3))
    console.log('-----------')
  })
})
```

実行結果
```ts
handle success...
{ balance: 20, frozen: false }
-----------
Handle error...
Cannot pay 50 with a balance of 30!
-----------
Handle error...
Cannot pay with a frozen account!
-----------
```

### ts-adt - makeMatch

ts-adt の利用例

https://www.npmjs.com/package/ts-adt

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/lib/function'
import { makeMatch } from 'ts-adt/MakeADT'

describe('fp-ts Tutorial', () => {
  it('Either - ts-adt - makeMatch', () => {
    type Cart = Readonly<{
      items: string[]
      total: number
    }>
    type Account = Readonly<{
      balance: number
      frozen: boolean
    }>
    type AccountFrozen = Readonly<{
      type: 'AccountFrozen'
      message: string
    }>
    type NotEnoughBalance = Readonly<{
      type: 'NotEnoughBalance'
      message: string
    }>

    const matchError = makeMatch('type')

    const pay =
      (amount: number) =>
      (account: Account): E.Either<NotEnoughBalance | AccountFrozen, Account> =>
        account.frozen
          ? E.left({
              type: 'AccountFrozen',
              message: 'Cannot pay with a frozen account!'
            })
          : account.balance < amount
            ? E.left({
                type: 'NotEnoughBalance',
                message: `Cannot pay ${amount} with a balance of ${account.balance}!`
              })
            : E.right({
                ...account,
                balance: account.balance - amount
              })

    const checkout_sample = (cart: Cart) => (account: Account) =>
      pipe(
        account,
        pay(cart.total),
        E.fold(
          (e) => 'Handle error...',
          (a) => 'handle success...'
        )
      )
    const checkout = (cart: Cart) => (account: Account) =>
      pipe(
        account,
        pay(cart.total),
        E.foldW(
          matchError({
            AccountFrozen: (e) => e.message,
            NotEnoughBalance: (e) => e.message
          }),
          (a) => a
        )
      )

    const account1: Account = {
      balance: 70,
      frozen: false
    }
    const account2: Account = {
      balance: 30,
      frozen: false
    }
    const account3: Account = {
      balance: 100,
      frozen: true
    }

    const cart1: Cart = {
      items: ['ice cream', 'waffle', 'candy'],
      total: 50
    }

    console.log(checkout_sample(cart1)(account1))
    console.log(checkout(cart1)(account1))
    console.log('-----------')
    console.log(checkout_sample(cart1)(account2))
    console.log(checkout(cart1)(account2))
    console.log('-----------')
    console.log(checkout_sample(cart1)(account3))
    console.log(checkout(cart1)(account3))
    console.log('-----------')
  })
})
```

実行結果
```ts
handle success...
{ balance: 20, frozen: false }
-----------
Handle error...
Cannot pay 50 with a balance of 30!
-----------
Handle error...
Cannot pay with a frozen account!
-----------
```



## Chapter 3.1: Either tryCatch
https://www.youtube.com/watch?v=zVaWe5xWgI8&list=PLUMXrUa_EuePN94nJ2hAui5nWDj8RO3lH&index=7

### E.tryCatch

```plantuml
card "{"test_string":"test value","test_number":123} \n ## jsonText ##"" as c1
card "E.left(error)" as c2
card "E.right(\n{ test_string: 'test value', test_number: 123 }\n) \n ## jsonObject ##" as c3

c1 -[dashed]-> c2 
c1 --> c3 : ""JSON.parse(jsonText) \n ok""
```

<br>


```plantuml
card "test \n ## jsonText ##"" as c1
card "E.left(error)" as c2
card "E.right(result) \n ## jsonObject ##" as c3

c1 --> c2 #line:red;line.bold;text:red : ""JSON.parse(jsonText) \n ng""
c1 -[dashed]-> c3 
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'

describe('fp-ts Tutorial', () => {
  it('Either tryCatch - E.tryCatch', () => {
    const jsonParse1 = (jsonText: string): E.Either<Error, unknown> => {
      try {
        const result = JSON.parse(jsonText)
        return E.right(result)
      } catch (e) {
        const error = e instanceof Error ? e : new Error(String(e))
        return E.left(error)
      }
    }
    const jsonParse2 = (jsonText: string): E.Either<Error, unknown> =>
      E.tryCatch(
        () => JSON.parse(jsonText),
        (e) => (e instanceof Error ? e : new Error(String(e)))
      )
    const jsonParse3 = (jsonText: string): E.Either<Error, unknown> =>
      E.tryCatch(() => JSON.parse(jsonText), E.toError)

    const jsonData1 = JSON.stringify({
      test_string: 'test value',
      test_number: 123
    })

    const jsonData2 = 'test'

    console.log(jsonData1)
    console.log(jsonData2)
    console.log('----------------')
    console.log(jsonParse1(jsonData1))
    console.log(jsonParse2(jsonData1))
    console.log(jsonParse3(jsonData1))
    console.log('----------------')
    console.log(jsonParse1(jsonData2).left.message)
    console.log(jsonParse2(jsonData2).left.message)
    console.log(jsonParse3(jsonData2).left.message)
    console.log('----------------')
  })
})
```

実行結果
```ts
{"test_string":"test value","test_number":123}
test
----------------
{
  _tag: 'Right',
  right: { test_string: 'test value', test_number: 123 }
}
{
  _tag: 'Right',
  right: { test_string: 'test value', test_number: 123 }
}
{
  _tag: 'Right',
  right: { test_string: 'test value', test_number: 123 }
}
----------------
Unexpected token e in JSON at position 1
Unexpected token e in JSON at position 1
Unexpected token e in JSON at position 1
----------------
```

### E.tryCatchK - K means Kleisli

https://gcanti.github.io/fp-ts/guides/code-conventions.html#what-a-k-suffix-means-eg-fromeitherk-or-chaineitherk


```plantuml
card "{"test_string":"test value","test_number":123} \n ## jsonText ##"" as c1
card "E.left(error)" as c2
card "E.right(\n{ test_string: 'test value', test_number: 123 }\n) \n ## jsonObject ##" as c3

c1 -[dashed]-> c2 
c1 --> c3 : ""JSON.parse(jsonText) \n ok""
```

<br>


```plantuml
card "test \n ## jsonText ##"" as c1
card "E.left(error)" as c2
card "E.right(result) \n ## jsonObject ##" as c3

c1 --> c2 #line:red;line.bold;text:red : ""JSON.parse(jsonText) \n ng""
c1 -[dashed]-> c3 
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'

describe('fp-ts Tutorial', () => {
  it('Either tryCatch - E.tryCatchK', () => {
    const jsonParse3: (jsonText: string) => E.Either<Error, unknown> = (
      jsonText: string
    ): E.Either<Error, unknown> => E.tryCatch(() => JSON.parse(jsonText), E.toError)
    const jsonParseK4: (jsonText: string) => E.Either<Error, unknown> = E.tryCatchK(
      JSON.parse,
      E.toError
    )

    const jsonData1 = JSON.stringify({
      test_string: 'test value',
      test_number: 123
    })

    const jsonData2 = 'test'

    console.log(jsonData1)
    console.log(jsonData2)
    console.log('----------------')
    console.log(jsonParse3(jsonData1))
    console.log(jsonParseK4(jsonData1))
    console.log('----------------')
    console.log(jsonParse3(jsonData2).left.message)
    console.log(jsonParseK4(jsonData2).left.message)
    console.log('----------------')
  })
})
```

実行結果
```ts
{"test_string":"test value","test_number":123}
test
----------------
{
  _tag: 'Right',
  right: { test_string: 'test value', test_number: 123 }
}
{
  _tag: 'Right',
  right: { test_string: 'test value', test_number: 123 }
}
----------------
Unexpected token e in JSON at position 1
Unexpected token e in JSON at position 1
----------------
```

### E.tryCatchK - 2


```plantuml
card "{"test_string":"test value","test_number":123} \n ## jsonText ##"" as c1
card "E.left(\n{ type: 'JsonParseError', error: E.toError(e) }\n) \n ## JsonParseError ##" as c2
card "E.right(\n{ test_string: 'test value', test_number: 123 }\n) \n ## jsonObject ##" as c3

c1 -[dashed]-> c2 
c1 --> c3 : ""JSON.parse(jsonText) \n ok""
```

<br>


```plantuml
card "test \n ## jsonText ##"" as c1
card "E.left(\n{ type: 'JsonParseError', error: E.toError(e) }\n) \n ## JsonParseError ##" as c2
card "E.right(result) \n ## jsonObject ##" as c3

c1 --> c2 #line:red;line.bold;text:red : ""JSON.parse(jsonText) \n ng""
c1 -[dashed]-> c3 
```

実行はできるが赤い背景のところでエラーが出る
```diff_typescript
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'

describe('fp-ts Tutorial', () => {
  it('Either tryCatch - 2', () => {
    type JsonParseError = Readonly<{
      type: 'JsonParseError'
      error: Error
    }>

    const jsonParse5: (jsonText: string) => E.Either<JsonParseError, unknown> = (
      jsonText: string
    ): E.Either<Error, unknown> =>
-      E.tryCatch(
-        () => JSON.parse(jsonText),
-        (e) => ({
-          type: 'JsonParseError',
-          error: E.toError(e)
-        })
-      )
    const jsonParseK6: (jsonText: string) => E.Either<JsonParseError, unknown> = E.tryCatchK(
      JSON.parse,
      (e) => ({
        type: 'JsonParseError',
        error: E.toError(e)
      })
    )

    const jsonData1 = JSON.stringify({
      test_string: 'test value',
      test_number: 123
    })

    const jsonData2 = 'test'

    console.log(jsonData1)
    console.log(jsonData2)
    console.log('----------------')
    console.log(jsonParse5(jsonData1))
    console.log(jsonParseK6(jsonData1))
    console.log('----------------')
    console.log(jsonParse5(jsonData2).left.type)
    console.log(jsonParse5(jsonData2).left.error.message)
    console.log(jsonParseK6(jsonData2).left.type)
    console.log(jsonParseK6(jsonData2).left.error.message)
    console.log('----------------')
  })
})
```

実行結果
```ts
{"test_string":"test value","test_number":123}
test
----------------
{
  _tag: 'Right',
  right: { test_string: 'test value', test_number: 123 }
}
{
  _tag: 'Right',
  right: { test_string: 'test value', test_number: 123 }
}
----------------
JsonParseError
Unexpected token e in JSON at position 1
JsonParseError
Unexpected token e in JSON at position 1
----------------
```
※VSCodeなどでエラー表示されるが、実行は可能
※Type 'Either<{ type: string; error: Error; }, any>' is not assignable to type 'Either<Error, unknown>'.

## Chapter 3.2: Either map, mapLeft, bimap
https://www.youtube.com/watch?v=hBJ82TdCokY&list=PLUMXrUa_EuePN94nJ2hAui5nWDj8RO3lH&index=8

### J.stringify

```plantuml
card "{ balance: 100, success: true } \n ## jsonObject ##"" as c1
card "E.left( ... ) \n ## JsonStringifyError ##" as c2
card "E.right(\n'{"balance":100,"success":true}'\n) \n ## jsonText ##" as c3

c1 -[dashed]-> c2 
c1 --> c3 : ""J.stringify \n ok""
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import * as J from 'fp-ts/Json'
import { pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('Either map, mapLeft, bimap - J.stringify', () => {
    type JsonStringifyError = Readonly<{
      type: 'JsonStringifyError'
      error: Error
    }>
    type Response = Readonly<{
      body: string
      contentLength: number
    }>

    const createResponse1 = (payload: unknown): E.Either<JsonStringifyError, Response> =>
      pipe(payload, JSON.stringify)

    const createResponse2 = (payload: unknown): E.Either<JsonStringifyError, Response> =>
      pipe(payload, J.stringify)

    const jsonData = { balance: 100, success: true }

    console.log(createResponse1(jsonData))
    console.log(createResponse2(jsonData))
    console.log(E.right(jsonData))
  })
})
```

実行結果
```ts
{"balance":100,"success":true}
{ _tag: 'Right', right: '{"balance":100,"success":true}' }
{ _tag: 'Right', right: { balance: 100, success: true } }
```

### E.map(f)

```plantuml
card "{ balance: 100, success: true } \n ## jsonObject ##"" as c1
card "E.left( ... ) \n ## JsonStringifyError ##" as c2
card "E.right(\n body: '{"balance":100,"success":true}' \n contentLength: 30 \n) \n ## Response ##" as c3

c1 -[dashed]-> c2 
c1 --> c3 : ""createResponse \n ok""
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import * as J from 'fp-ts/Json'
import { pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('Either map, mapLeft, bimap - E.map(f)', () => {
    type JsonStringifyError = Readonly<{
      type: 'JsonStringifyError'
      error: Error
    }>
    type Response = Readonly<{
      body: string
      contentLength: number
    }>

    const debug_log = <T>(x: T) => {
      console.log(x)
      return x
    }
    const createResponse = (payload: unknown): E.Either<JsonStringifyError, Response> =>
      pipe(
        payload,
        debug_log,
        J.stringify,
        debug_log,
        E.map((s) => ({
          body: s,
          contentLength: s.length
        }))
      )

    const jsonData = { balance: 100, success: true }

    console.log(createResponse(jsonData))
    console.log('--------------')
    console.log(
      E.right({
        body: JSON.stringify(jsonData),
        contentLength: JSON.stringify(jsonData).length
      })
    )
  })
})
```

実行結果
```ts
{ balance: 100, success: true }
{ _tag: 'Right', right: '{"balance":100,"success":true}' }
{
  _tag: 'Right',
  right: { body: '{"balance":100,"success":true}', contentLength: 30 }
}
--------------
{
  _tag: 'Right',
  right: { body: '{"balance":100,"success":true}', contentLength: 30 }
}
```

### E.mapLeft(f)

```plantuml
card "{ balance: 100, success: true } \n ## jsonObject ##"" as c1
card "E.left( ... ) \n ## JsonStringifyError ##" as c2
card "E.right(\n body: '{"balance":100,"success":true}' \n contentLength: 30 \n) \n ## Response ##" as c3

c1 -[dashed]-> c2 
c1 --> c3 : ""createResponse \n ok""
```

<br>

```plantuml
card "<ref *1> { self: [Circular *1] } \n ## jsonObject ? ##"" as c1
card "E.left(\n type: 'JsonStringifyError' \n error: E.toError(TypeError) \n) \n ## JsonStringifyError ##" as c2
card "E.right( ... ) \n ## Response ##" as c3

c1 --> c2 #line:red;line.bold;text:red : ""createResponse \n ng""
c1 -[dashed]-> c3 
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import * as J from 'fp-ts/Json'
import { pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('Either map, mapLeft, bimap - E.mapLeft(f)', () => {
    type JsonStringifyError = Readonly<{
      type: 'JsonStringifyError'
      error: Error
    }>
    type Response = Readonly<{
      body: string
      contentLength: number
    }>

    const debug_log = <T>(x: T) => {
      console.log(x)
      return x
    }
    const createResponse = (payload: unknown): E.Either<JsonStringifyError, Response> =>
      pipe(
        payload,
        debug_log,
        J.stringify,
        // debug_log,
        E.map((s) => ({
          body: s,
          contentLength: s.length
        })),
        E.mapLeft((e) => ({
          type: 'JsonStringifyError',
          error: E.toError(e)
        }))
      )

    const jsonDataOk = { balance: 100, success: true }
    const jsonDataNg = {}
    jsonDataNg.self = jsonDataNg

    console.log(createResponse(jsonDataOk))
    console.log('--------------')
    console.log(
      E.right({
        body: JSON.stringify(jsonDataOk),
        contentLength: JSON.stringify(jsonDataOk).length
      })
    )
    console.log('--------------')
    console.log(createResponse(jsonDataNg).left.error.message)
    console.log('--------------')
    console.log(
      E.left({
        type: 'JsonStringifyError',
        error: E.toError(
          TypeError(
            "Converting circular structure to JSON\n    --> starting at object with constructor 'Object'\n    --- property 'self' closes the circle"
          )
        )
      }).left.error.message
    )
  })
})
```

実行結果
```ts
{ balance: 100, success: true }
{
  _tag: 'Right',
  right: { body: '{"balance":100,"success":true}', contentLength: 30 }
}
--------------
{
  _tag: 'Right',
  right: { body: '{"balance":100,"success":true}', contentLength: 30 }
}
--------------
<ref *1> { self: [Circular *1] }
Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'self' closes the circle
--------------
Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'self' closes the circle
```

### E.bimap(f)

```plantuml
card "{ balance: 100, success: true } \n ## jsonObject ##"" as c1
card "E.left( ... ) \n ## JsonStringifyError ##" as c2
card "E.right(\n body: '{"balance":100,"success":true}' \n contentLength: 30 \n) \n ## Response ##" as c3

c1 -[dashed]-> c2 
c1 --> c3 : ""createResponse \n ok""
```

<br>

```plantuml
card "<ref *1> { self: [Circular *1] } \n ## jsonObject ? ##"" as c1
card "E.left(\n type: 'JsonStringifyError' \n error: E.toError(TypeError) \n) \n ## JsonStringifyError ##" as c2
card "E.right( ... ) \n ## Response ##" as c3

c1 --> c2 #line:red;line.bold;text:red : ""createResponse \n ng""
c1 -[dashed]-> c3 
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import * as J from 'fp-ts/Json'
import { pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('Either map, mapLeft, bimap - E.bimap(f)', () => {
    type JsonStringifyError = Readonly<{
      type: 'JsonStringifyError'
      error: Error
    }>
    type Response = Readonly<{
      body: string
      contentLength: number
    }>

    const debug_log = <T>(x: T) => {
      console.log(x)
      return x
    }
    const createResponse = (payload: unknown): E.Either<JsonStringifyError, Response> =>
      pipe(
        payload,
        debug_log,
        J.stringify,
        // debug_log,
        E.bimap(
          (e) => ({
            type: 'JsonStringifyError',
            error: E.toError(e)
          }),
          (s) => ({
            body: s,
            contentLength: s.length
          })
        )
      )

    const jsonDataOk = { balance: 100, success: true }
    const jsonDataNg = {}
    jsonDataNg.self = jsonDataNg

    console.log(createResponse(jsonDataOk))
    console.log('--------------')
    console.log(
      E.right({
        body: JSON.stringify(jsonDataOk),
        contentLength: JSON.stringify(jsonDataOk).length
      })
    )
    console.log('--------------')
    console.log(createResponse(jsonDataNg).left.error.message)
    console.log('--------------')
    console.log(
      E.left({
        type: 'JsonStringifyError',
        error: E.toError(
          TypeError(
            "Converting circular structure to JSON\n    --> starting at object with constructor 'Object'\n    --- property 'self' closes the circle"
          )
        )
      }).left.error.message
    )
  })
})
```

実行結果
```ts
{ balance: 100, success: true }
{
  _tag: 'Right',
  right: { body: '{"balance":100,"success":true}', contentLength: 30 }
}
--------------
{
  _tag: 'Right',
  right: { body: '{"balance":100,"success":true}', contentLength: 30 }
}
--------------
<ref *1> { self: [Circular *1] }
Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'self' closes the circle
--------------
Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'self' closes the circle
```

## Chapter 3.3: Either flatMap (chain)
https://www.youtube.com/watch?v=J5jl6AnQAKk&list=PLUMXrUa_EuePN94nJ2hAui5nWDj8RO3lH&index=9

https://www.npmjs.com/package/base-64

### E.map(f) - E.flattenW

```plantuml
card "A" as c1
card "Either<E1, B>" as c2
card "Either<E2, C>" as c3

c1 -> c2 
c2 -> c3 
```

<br>

```plantuml
title "処理の型"

card "string" as c1
card "Either<Base64DecodeError, string>" as c2
card "Either<Base64DecodeError, Either<JsonParseError, unknown>>" as c3
card "Either<Base64DecodeError | JsonParseError, unknown>" as c4

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.map(jsonParse)"
c3 --> c4 : "E.flattenW"
```

<br>

```plantuml
title "実際の処理の値 okパターン"

card "eyJpZCI6MSwidXNlcm5hbWUiOiJqaW1teSJ9" as c1
card "E.right(\n'{"id":1,"username":"jimmy"}' \n) \n ## jsonText ##" as c2
card "E.right(\n E.right(\n  { id: 1, username: 'jimmy' } \n ) \n)  \n ## jsonObject ##" as c3
card "E.right(\n { id: 1, username: 'jimmy' } \n)  \n ## jsonObject ##" as c4

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.map(jsonParse)"
c3 --> c4 : "E.flattenW"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(Base64DecodeError)"

card "1" as c1
card "E.left(Base64DecodeError)" as c2
card "E.left(Base64DecodeError)" as c3
card "E.left(Base64DecodeError)" as c4

c1 --> c2 #line:red;line.bold;text:red : "base64Decode"
c2 --> c3 #line:red;line.bold;text:red : "E.map(jsonParse)"
c3 --> c4 #line:red;line.bold;text:red : "E.flattenW"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(JsonParseError)"

card " _ " as c1
card "E.right('')" as c2
card "E.right(\n E.left(JsonParseError) \n)" as c3
card "E.left(JsonParseError)" as c4

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.map(jsonParse)"
c3 --> c4 #line:red;line.bold;text:red : "E.flattenW"
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/lib/function'
import base64 from 'base-64'

describe('fp-ts Tutorial', () => {
  it('Either flatMap (chain) - E.map(f) - E.flattenW', () => {
    type User = Readonly<{
      id: number
      username: string
    }>
    type Base64DecodeError = Readonly<{
      type: 'Base64DecodeError'
      error: Error
    }>
    type JsonParseError = Readonly<{
      type: 'JsonParseError'
      error: Error
    }>

    const base64Decode = E.tryCatchK(
      base64.decode,
      (e): Base64DecodeError => ({
        type: 'Base64DecodeError',
        error: E.toError(e)
      })
    )

    const jsonParse: (jsonText: string) => E.Either<JsonParseError, unknown> = E.tryCatchK(
      JSON.parse,
      (e) => ({
        type: 'JsonParseError',
        error: E.toError(e)
      })
    )

    const debug_log = <T>(x: T) => {
      if (E.isLeft(x)) {
        if (E.isLeft(x.left)) {
          console.log(x.left.left.error.message)
        } else {
          console.log(x.left.error.message)
        }
      } else if (E.isRight(x)) {
        if (E.isLeft(x.right)) {
          console.log(x.right.left.error.message)
        } else {
          console.log(x)
        }
      } else {
        console.log(x)
      }
      return x
    }
    // const debug_log = <T>(x: T) => {
    //   console.log(x)
    //   return x
    // }

    const decodeUser = (encodedUser: string) =>
      pipe(encodedUser, debug_log, base64Decode, debug_log, E.map(jsonParse), debug_log, E.flattenW)

    const userOk: User = {
      id: 1,
      username: 'jimmy'
    }
    const encodedUserOk: string = base64.encode(JSON.stringify(userOk))

    const encodedUserNg1: string = ''
    const encodedUserNg2 = 1

    console.log(decodeUser(encodedUserOk))
    console.log('-----------------')
    console.log(decodeUser(encodedUserNg1).left.error.message)
    console.log('-----------------')
    console.log(decodeUser(encodedUserNg2).left.error.message)
  })
})
```

実行結果
```ts
eyJpZCI6MSwidXNlcm5hbWUiOiJqaW1teSJ9
{ _tag: 'Right', right: '{"id":1,"username":"jimmy"}' }
{
  _tag: 'Right',
  right: { _tag: 'Right', right: { id: 1, username: 'jimmy' } }
}
{ _tag: 'Right', right: { id: 1, username: 'jimmy' } }
-----------------

{ _tag: 'Right', right: '' }
Unexpected end of JSON input
Unexpected end of JSON input
-----------------
1
Invalid character: the string to be decoded is not correctly encoded.
Invalid character: the string to be decoded is not correctly encoded.
Invalid character: the string to be decoded is not correctly encoded.
```

### E.flatMap(f)

```plantuml
card "A" as c1
card "Either<E1, B>" as c2
card "Either<E2, C>" as c3

c1 -> c2 
c2 -> c3 
```

<br>

```plantuml
title "処理の型"

card "string" as c1
card "Either<Base64DecodeError, string>" as c2
card "Either<Base64DecodeError | JsonParseError, unknown>" as c3

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.flatMap(jsonParse)"
```

<br>

```plantuml
title "実際の処理の値 okパターン"

card "eyJpZCI6MSwidXNlcm5hbWUiOiJqaW1teSJ9" as c1
card "E.right(\n'{"id":1,"username":"jimmy"}' \n) \n ## jsonText ##" as c2
card "E.right(\n { id: 1, username: 'jimmy' } \n)  \n ## jsonObject ##" as c3

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.flatMap(jsonParse)"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(Base64DecodeError)"

card "1" as c1
card "E.left(Base64DecodeError)" as c2
card "E.left(Base64DecodeError)" as c3

c1 --> c2 #line:red;line.bold;text:red : "base64Decode"
c2 --> c3 #line:red;line.bold;text:red : "E.flatMap(jsonParse)"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(JsonParseError)"

card " _ " as c1
card "E.right('')" as c2
card "E.left(JsonParseError)" as c3

c1 --> c2 : "base64Decode"
c2 --> c3 #line:red;line.bold;text:red : "E.flatMap(jsonParse)"
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/lib/function'
import base64 from 'base-64'

describe('fp-ts Tutorial', () => {
  it('Either flatMap (chain) - E.flatMap(f)', () => {
    type User = Readonly<{
      id: number
      username: string
    }>
    type Base64DecodeError = Readonly<{
      type: 'Base64DecodeError'
      error: Error
    }>
    type JsonParseError = Readonly<{
      type: 'JsonParseError'
      error: Error
    }>

    const base64Decode = E.tryCatchK(
      base64.decode,
      (e): Base64DecodeError => ({
        type: 'Base64DecodeError',
        error: E.toError(e)
      })
    )

    const jsonParse: (jsonText: string) => E.Either<JsonParseError, unknown> = E.tryCatchK(
      JSON.parse,
      (e) => ({
        type: 'JsonParseError',
        error: E.toError(e)
      })
    )

    const debug_log = <T>(x: T) => {
      if (E.isLeft(x)) {
        if (E.isLeft(x.left)) {
          console.log(x.left.left.error.message)
        } else {
          console.log(x.left.error.message)
        }
      } else if (E.isRight(x)) {
        if (E.isLeft(x.right)) {
          console.log(x.right.left.error.message)
        } else {
          console.log(x)
        }
      } else {
        console.log(x)
      }
      return x
    }
    // const debug_log = <T>(x: T) => {
    //   console.log(x)
    //   return x
    // }

    const decodeUser = (encodedUser: string) =>
      pipe(encodedUser, debug_log, base64Decode, debug_log, E.flatMap(jsonParse))

    const userOk: User = {
      id: 1,
      username: 'jimmy'
    }
    const encodedUserOk: string = base64.encode(JSON.stringify(userOk))

    const encodedUserNg1: string = ''
    const encodedUserNg2 = 1

    console.log(decodeUser(encodedUserOk))
    console.log('-----------------')
    console.log(decodeUser(encodedUserNg1).left.error.message)
    console.log('-----------------')
    console.log(decodeUser(encodedUserNg2).left.error.message)
  })
})
```

実行結果
```ts
eyJpZCI6MSwidXNlcm5hbWUiOiJqaW1teSJ9
{ _tag: 'Right', right: '{"id":1,"username":"jimmy"}' }
{ _tag: 'Right', right: { id: 1, username: 'jimmy' } }
-----------------

{ _tag: 'Right', right: '' }
Unexpected end of JSON input
-----------------
1
Invalid character: the string to be decoded is not correctly encoded.
Invalid character: the string to be decoded is not correctly encoded.
```

### E.flatMap(f) - 2

```plantuml
card "A" as c1
card "Either<E1, B>" as c2
card "Either<E2, C>" as c3

c1 -> c2 
c2 -> c3 
```

<br>

```plantuml
title "処理の型"

card "string" as c1
card "Either<Base64DecodeError, string>" as c2
card "Either<Base64DecodeError | JsonParseError, unknown>" as c3
card "Either<Base64DecodeError | JsonParseError | InvalidUserObject, User>" as c4

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.flatMap(jsonParse)"
c3 --> c4 : "E.flatMap(decodeUserObjectFromUnknown)"
```

<br>

```plantuml
title "実際の処理の値 okパターン"

card "eyJpZCI6MSwidXNlcm5hbWUiOiJqaW1teSJ9" as c1
card "E.right(\n'{"id":1,"username":"jimmy"}' \n) \n ## jsonText ##" as c2
card "E.right(\n { id: 1, username: 'jimmy' } \n)  \n ## jsonObject ##" as c3
card "E.right(\n { id: 1, username: 'jimmy' } \n)  \n ## User ##" as c4

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.flatMap(jsonParse)"
c3 --> c4 : "E.flatMap(decodeUserObjectFromUnknown)"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(Base64DecodeError)"

card "1" as c1
card "E.left(Base64DecodeError)" as c2
card "E.left(Base64DecodeError)" as c3
card "E.left(Base64DecodeError)" as c4

c1 --> c2 #line:red;line.bold;text:red : "base64Decode"
c2 --> c3 #line:red;line.bold;text:red : "E.flatMap(jsonParse)"
c3 --> c4 #line:red;line.bold;text:red : "E.flatMap(decodeUserObjectFromUnknown)"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(JsonParseError)"

card " _ " as c1
card "E.right('')" as c2
card "E.left(JsonParseError)" as c3
card "E.left(JsonParseError)" as c4

c1 --> c2 : "base64Decode"
c2 --> c3 #line:red;line.bold;text:red : "E.flatMap(jsonParse)"
c3 --> c4 #line:red;line.bold;text:red : "E.flatMap(decodeUserObjectFromUnknown)"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(InvalidUserObject)"

card "eyJpZCI6MSwibmFtZSI6ImppbW15In0=" as c1
card "E.right(\n'{"id":1,"name":"jimmy"}' \n) \n ## jsonText ##" as c2
card "E.right(\n { id: 1, name: 'jimmy' } \n)  \n ## jsonObject ##" as c3
card "E.left(InvalidUserObject)" as c4

c1 --> c2 : "base64Decode"
c2 --> c3 : "E.flatMap(jsonParse)"
c3 --> c4 #line:red;line.bold;text:red : "E.flatMap(decodeUserObjectFromUnknown)"
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/lib/function'
import base64 from 'base-64'

describe('fp-ts Tutorial', () => {
  it('Either flatMap (chain) - E.flatMap(f) - 2', () => {
    type User = Readonly<{
      id: number
      username: string
    }>
    type Base64DecodeError = Readonly<{
      type: 'Base64DecodeError'
      error: Error
    }>
    type JsonParseError = Readonly<{
      type: 'JsonParseError'
      error: Error
    }>
    type InvalidUserObject = Readonly<{
      type: 'InvalidUserObject'
      error: Error
    }>

    const base64Decode = E.tryCatchK(
      base64.decode,
      (e): Base64DecodeError => ({
        type: 'Base64DecodeError',
        error: E.toError(e)
      })
    )

    const jsonParse: (jsonText: string) => E.Either<JsonParseError, unknown> = E.tryCatchK(
      JSON.parse,
      (e) => ({
        type: 'JsonParseError',
        error: E.toError(e)
      })
    )

    const isUser = (u: unknown): u is User =>
      u &&
      typeof u === 'object' &&
      'id' in u &&
      typeof u.id === 'number' &&
      'username' in u &&
      typeof u.username === 'string'
        ? true
        : false

    const decodeUserObjectFromUnknown: (u: unknown) => E.Either<InvalidUserObject, User> =
      E.tryCatchK(
        (u) => {
          if (isUser(u)) {
            return u
          }

          throw Error(`${JSON.stringify(u)} is not User`)
        },
        (e) => ({
          type: 'InvalidUserObject',
          error: E.toError(e)
        })
      )

    const debug_log = <T>(x: T) => {
      if (E.isLeft(x)) {
        if (E.isLeft(x.left)) {
          console.log(x.left.left.error.message)
        } else {
          console.log(x.left.error.message)
        }
      } else if (E.isRight(x)) {
        if (E.isLeft(x.right)) {
          console.log(x.right.left.error.message)
        } else {
          console.log(x)
        }
      } else {
        console.log(x)
      }
      return x
    }
    // const debug_log = <T>(x: T) => {
    //   console.log(x)
    //   return x
    // }

    const decodeUser = (encodedUser: string) =>
      pipe(
        encodedUser,
        debug_log,
        base64Decode,
        debug_log,
        E.flatMap(jsonParse),
        debug_log,
        E.flatMap(decodeUserObjectFromUnknown)
      )

    const userOk: User = {
      id: 1,
      username: 'jimmy'
    }
    const encodedUserOk: string = base64.encode(JSON.stringify(userOk))

    const encodedUserNg1: string = ''
    const encodedUserNg2 = 1

    const userNg = {
      id: 1,
      name: 'jimmy'
    }
    const encodedUserNg3: string = base64.encode(JSON.stringify(userNg))

    console.log(decodeUser(encodedUserOk))
    console.log('-----------------')
    console.log(decodeUser(encodedUserNg1).left.error.message)
    console.log('-----------------')
    console.log(decodeUser(encodedUserNg2).left.error.message)
    console.log('-----------------')
    console.log(decodeUser(encodedUserNg3).left.error.message)
  })
})
```

実行結果
```ts
eyJpZCI6MSwidXNlcm5hbWUiOiJqaW1teSJ9
{ _tag: 'Right', right: '{"id":1,"username":"jimmy"}' }
{ _tag: 'Right', right: { id: 1, username: 'jimmy' } }
{ _tag: 'Right', right: { id: 1, username: 'jimmy' } }
-----------------

{ _tag: 'Right', right: '' }
Unexpected end of JSON input
Unexpected end of JSON input
-----------------
1
Invalid character: the string to be decoded is not correctly encoded.
Invalid character: the string to be decoded is not correctly encoded.
Invalid character: the string to be decoded is not correctly encoded.
-----------------
eyJpZCI6MSwibmFtZSI6ImppbW15In0=
{ _tag: 'Right', right: '{"id":1,"name":"jimmy"}' }
{ _tag: 'Right', right: { id: 1, name: 'jimmy' } }
{"id":1,"name":"jimmy"} is not User
```

## Chapter 3.4: Either orElse error recovery
https://www.youtube.com/watch?v=319TlxJVR6o&list=PLUMXrUa_EuePN94nJ2hAui5nWDj8RO3lH&index=10

### E.orElse(onLeft) - 1

```plantuml
title "処理の型 - validateLoginName"

card "string" as c1
card "Either<'MalformedEmail' | 'NotAnEmail', Email>" as c2
card "Either<'MalformedEmail' | 'InvalidPhoneNumber', Email | PhoneNumber>" as c3

c1 --> c2 : "validateEmail"
c2 --> c3 : "E.orElseW((e) => \n (e === 'NotAnEmail' ? \n validatePhoneNumber(loginName) : \n E.left(e)))"
```

<br>

```plantuml
title "実際の処理の値 okパターン(Email)"

card "user@example.com" as c1
card "E.right(\n'{type: "Email", value:"user@example.com"}' \n)" as c2
card "E.right(\n'{type: "Email", value:"user@example.com"}' \n)" as c3

c1 --> c2 : "validateEmail"
c2 --> c3 : "E.orElseW((e) => \n (e === 'NotAnEmail' ? \n validatePhoneNumber(loginName) : \n E.left(e)))"
```

<br>

```plantuml
title "実際の処理の値 okパターン(PhoneNumber)"

card "555 1234567" as c1
card "E.left('NotAnEmail')" as c2
card "E.right(\n'{type: "PhoneNumber", value:"555 1234567"}' \n)" as c3

c1 --> c2 #line:red;line.bold;text:red : "validateEmail"
c2 --> c3 : "E.orElseW((e) => \n (e === 'NotAnEmail' ? \n validatePhoneNumber(loginName) : \n E.left(e)))"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(MalformedEmail)"

card "foo@bar" as c1
card "E.left('MalformedEmail')" as c2
card "E.left('MalformedEmail')" as c3

c1 --> c2 #line:red;line.bold;text:red : "validateEmail"
c2 --> c3 #line:red;line.bold;text:red : "E.orElseW((e) => \n (e === 'NotAnEmail' ? \n validatePhoneNumber(loginName) : \n E.left(e)))"
```

<br>

```plantuml
title "実際の処理の値 ngパターン(InvalidPhoneNumber)"

card "foo@bar" as c1
card "E.left('NotAnEmail')" as c2
card "E.left('InvalidPhoneNumber')" as c3

c1 --> c2 #line:red;line.bold;text:red : "validateEmail"
c2 --> c3 #line:red;line.bold;text:red : "E.orElseW((e) => \n (e === 'NotAnEmail' ? \n validatePhoneNumber(loginName) : \n E.left(e)))"
```

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { flow, pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('E.orElse(onLeft) - 1', () => {
    type Email = Readonly<{
      type: 'Email'
      value: string
    }>
    type PhoneNumber = Readonly<{
      type: 'PhoneNumber'
      value: string
    }>

    const emailRegex = /^\S+@\S+\.\S+$/
    const phoneNumberRegex = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s./0-9]*$/

    const debug_log = <T>(x: T) => {
      console.log(x)
      return x
    }
    const validateEmail = flow(
      E.fromPredicate(
        (mabeEmail: string) => emailRegex.test(mabeEmail),
        (invalidEmail) => (invalidEmail.includes('@') ? 'MalformedEmail' : 'NotAnEmail')
      ),
      E.map(
        (email): Email => ({
          type: 'Email',
          value: email
        })
      )
    )

    const validatePhoneNumber = flow(
      E.fromPredicate(
        (maybePhoneNumber: string) => phoneNumberRegex.test(maybePhoneNumber),
        () => 'InvalidPhoneNumber' as const
      ),
      E.map(
        (phoneNumber): PhoneNumber => ({
          type: 'PhoneNumber',
          value: phoneNumber
        })
      )
    )

    const validateLoginName = (loginName: string) =>
      pipe(
        loginName,
        debug_log,
        validateEmail,
        debug_log,
        E.orElseW((e) => (e === 'NotAnEmail' ? validatePhoneNumber(loginName) : E.left(e)))
      )

    console.log(validateLoginName('user@example.com'))
    console.log('------------')
    console.log(validateLoginName('555 1234567'))
    console.log('------------')
    console.log(validateLoginName('foo@bar'))
    console.log('------------')
    console.log(validateLoginName('foo123'))
  })
})
```

実行結果
```ts
user@example.com
{ _tag: 'Right', right: { type: 'Email', value: 'user@example.com' } }
{ _tag: 'Right', right: { type: 'Email', value: 'user@example.com' } }
------------
555 1234567
{ _tag: 'Left', left: 'NotAnEmail' }
{ _tag: 'Right', right: { type: 'PhoneNumber', value: '555 1234567' } }
------------
foo@bar
{ _tag: 'Left', left: 'MalformedEmail' }
{ _tag: 'Left', left: 'MalformedEmail' }
------------
foo123
{ _tag: 'Left', left: 'NotAnEmail' }
{ _tag: 'Left', left: 'InvalidPhoneNumber' }
```

### E.orElse(onLeft) - 2

エラーを文字列から方に変えたもの

```ts
import { describe, it } from 'vitest'
import * as E from 'fp-ts/Either'
import { flow, pipe } from 'fp-ts/lib/function'

describe('fp-ts Tutorial', () => {
  it('E.orElse(onLeft) - 2', () => {
    type Email = Readonly<{
      type: 'Email'
      value: string
    }>
    type PhoneNumber = Readonly<{
      type: 'PhoneNumber'
      value: string
    }>

    type MalformedEmail = Readonly<{
      type: 'MalformedEmail'
      error: Error
    }>
    type NotAnEmail = Readonly<{
      type: 'NotAnEmail'
      error: Error
    }>
    type InvalidPhoneNumber = Readonly<{
      type: 'InvalidPhoneNumber'
      error: Error
    }>

    const emailRegex = /^\S+@\S+\.\S+$/
    const phoneNumberRegex = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s./0-9]*$/

    const debug_log = <E, A>(x: E.Either<E, A>) => {
      if (E.isLeft(x)) {
        console.log(x.left.error.message)
      } else {
        console.log(x)
      }
      return x
    }
    const validateEmail = flow(
      E.fromPredicate(
        (mabeEmail: string) => emailRegex.test(mabeEmail),
        (invalidEmail): MalformedEmail | NotAnEmail =>
          invalidEmail.includes('@')
            ? { type: 'MalformedEmail', error: Error('Malformed email!') }
            : { type: 'NotAnEmail', error: Error('Not an email!') }
      ),
      E.map(
        (email): Email => ({
          type: 'Email',
          value: email
        })
      )
    )

    const validatePhoneNumber = flow(
      E.fromPredicate(
        (maybePhoneNumber: string) => phoneNumberRegex.test(maybePhoneNumber),
        (): InvalidPhoneNumber => ({
          type: 'InvalidPhoneNumber',
          error: Error('Invalid phone number!')
        })
      ),
      E.map(
        (phoneNumber): PhoneNumber => ({
          type: 'PhoneNumber',
          value: phoneNumber
        })
      )
    )

    const validateLoginName = (loginName: string) =>
      pipe(
        loginName,
        debug_log,
        validateEmail,
        debug_log,
        E.orElseW(
          (e): E.Either<InvalidPhoneNumber | MalformedEmail, PhoneNumber> =>
            e.type === 'NotAnEmail' ? validatePhoneNumber(loginName) : E.left(e)
        )
      )

    console.log(validateLoginName('user@example.com'))
    console.log('------------')
    console.log(validateLoginName('555 1234567'))
    console.log('------------')
    console.log(validateLoginName('foo@bar').left.error.message)
    console.log('------------')
    console.log(validateLoginName('foo123').left.error.message)
  })
})
```

実行結果
```ts
user@example.com
{ _tag: 'Right', right: { type: 'Email', value: 'user@example.com' } }
{ _tag: 'Right', right: { type: 'Email', value: 'user@example.com' } }
------------
555 1234567
Not an email!
{ _tag: 'Right', right: { type: 'PhoneNumber', value: '555 1234567' } }
------------
foo@bar
Malformed email!
Malformed email!
------------
foo123
Not an email!
Invalid phone number!
```

## Chapter 4: IO
https://www.youtube.com/watch?v=hqZaQRD1wCw&list=PLUMXrUa_EuePN94nJ2hAui5nWDj8RO3lH&index=11

具体的なソースコードなし