Haskellがわかる人のためのfp-ts

## はじめに

[fp-ts](https://gcanti.github.io/fp-ts)はTypescriptに関数型言語の一般的なパターンと抽象化を導入するためのライブラリです

バージョンは**2.10.5**です

公式のドキュメントは英語で、少し説明が少ないかな？と思ったので、日本語で最新バージョンの記事を書くことでfp-tsを触る人が増えることを期待します

> 注) 筆者はHaskell初心者です

> 2021/06/12 型制約付きの関数、モナド変換子について更新しました

## 基本

Typescriptにはアドホック多相がありません、なのでMaybeにfmapを使いたい、Eitherにfmapを使いたいとなったら、明示的に関数を選択する必要があります

後述しますがfp-tsでは`Maybe`は`Option`、`fmap`は`map`となっています

```ts
import * as E from "fp-ts/Either";
import * as O from "fp-ts/Option";

const e = E.right(1);

console.log(E.map((a: number): number => a + 1)(e));

// => { _tag: "Right", right: 2}

const value: number | null = 1;
const o = O.fromNullable(value);

console.log(O.map((a: number): number => a + 1)(o));

// => { _tag: "Some", value: 2}

```

## パターンマッチ?

`match`という関数が提供されてる

```ts
import * as O from 'fp-ts/Option';
import * as f from 'fp-ts/function';

const some = O.some(1)
const none = O.none
const match = O.match(
  function onNone() {
    return 0;
  },
  function onSome(a: number) {
    return a;
  }
);

console.log(match(some))
// => 1
console.log(match(none))
// => 0
```


## do構文

[公式の説明](https://gcanti.github.io/fp-ts/guides/do-notation.html)

`>>=`に対応する関数は`chain`、`a <- hoge`に対応するのは`bind('a', ()=>hoge)`、`return`に対応するのは`of`となっています

> `a -> Monad a`となる関数はいくつかあります、Optionの場合`fromNullable`があります

上記を用いてHaskellのdo構文をfp-tsで書き直すと以下のようになります

```hs
five :: Maybe Int
five =
  do
    a <- return 2
    b <- return 3
    return (a + b)
```

```ts
import * as O from "fp-ts/Option";
import {pipe} from "fp-ts/function";

five = pipe(
    O.Do,
    O.bind("a",()=>O.of(2)),
    O.bind("b",()=>O.of(3)),
    O.chain(({a, b})=>O.of(a + b))
    )
```

## データ型の宣言

[公式の説明](https://gcanti.github.io/fp-ts/guides/purescript.html#data)

Typescriptは構造的部分型なので`_tag`をつかって何の値なのか判断できるようにします

```ts
interface Bar {
  readonly _tag: 'Bar'
  readonly value: string
}

interface Baz {
  readonly _tag: 'Baz'
  readonly value: boolean
}

// type
type Foo = Bar | Baz

// constructors
const Bar = (value: string): Foo => ({ _tag: 'Bar', value })

const Baz = (value: boolean): Foo => ({ _tag: 'Baz', value })
```

## 多相的データ型の宣言

[公式の説明](https://gcanti.github.io/fp-ts/guides/purescript.html#polymorphic-data)

引数を取るようにしてあげます

```ts
export interface None {
  readonly _tag: 'None';
}

export interface Some<A> {
  readonly _tag: 'Some';
  readonly value: A;
}

const some = <A>(a: A): Some<A> => ({ _tag: 'Some', value: a });
const none:None = ({ _tag: 'None' });

export type Option<A> = None | Some<A>;
```

## 型クラスのインスタンス

[公式の説明](https://gcanti.github.io/fp-ts/guides/HKT.html)

### Eq型クラス

#### プリミティブな値

プリミティブな値のEq型クラスインスタンスは提供されてます

```ts
import * as s from "fp-ts/string"
import * as n from "fp-ts/number"

console.log(n.Eq.equals(1, 1))
// => true

console.log(s.Eq.equals("hello", "hello"))
// => true

```

#### オブジェクト
オブジェクトのEqは`struct`関数で作成します

```ts
import * as s from "fp-ts/string"
import * as n from "fp-ts/number"
import {struct, Eq} from "fp-ts/Eq"

type Person = {
  name: string,
  age: number,
}

const PersonEq: Eq<Person> = struct({
  name: s.Eq,
  age: n.Eq,
})

const john: Person = {
  name: "John",
  age: 23
}

const mike: Person = {
  name: "Mike",
  age: 21
}

const john2: Person = {
  name: "John",
  age: 23
}

console.log(PersonEq.equals(john, mike))
// => false

console.log(PersonEq.equals(john, john2))
// => true

```

#### 多相的データ型

多相的データ型の`getEq`関数を使って動的に作成する必要があります

以下ではEitherの`getEq`にLeftの値であるstringのEqインスタンスとRightの値であるnumberのEqインスタンスを渡してEitherのEqインスタンスを作っています

```ts
import * as E from 'fp-ts/Either';
import * as n from 'fp-ts/number';
import * as s from 'fp-ts/string';
import type { Eq } from 'fp-ts/Eq';

const EitherStrNumEq: Eq<E.Either<string, number>> = E.getEq(s.Eq, n.Eq);

console.log(EitherStrNumEq.equals(E.right(1), E.left('im left')));
// => false

console.log(EitherStrNumEq.equals(E.left('hello'), E.left('world')));
// => false

console.log(EitherStrNumEq.equals(E.right(1), E.right(1)));
// => true

```

### Functor型クラス

型コンストラクタの引数の数に合わせて`Functor1`、`Functor2`、`Functor3`、`Functor4`を使います

[#多相的データ型の宣言](#多相的データ型の宣言)で作成したOptionをFunctor型クラスのインスタンスにします

```ts
import type { Functor1 } from 'fp-ts/Functor';

...Optionの宣言は省略

export const URI = 'Option'

export type URI = typeof URI

declare module 'fp-ts/lib/HKT' {
  interface URItoKind<A> {
    readonly [URI]: Option<A>
  }
}

export const option: Functor1<URI> = {
  URI,
  map: (ma, f) => ma._tag === "None" ? ma : some(f(ma.value))
}
```

変数`URI`やモジュールの宣言で高カインド型をOptionに対応できるようにしています。詳しくは[「TypeScriptで高カインド型(Higher kinded types)」](https://qiita.com/kgtkr/items/281b634987220d1d1ed5)を参照してください

そして、Functorのインスタンスを作ります、今回は型引数が１つなので`Functor1`を使っています

１つ以上の引数を取る型コンストラクタをインスタンス化する場合は上記のように対応する`URItoKind`(URItoKind2、URItoKind3など)を拡張してあげる必要があります


## 型クラスの宣言

[公式の説明](https://gcanti.github.io/fp-ts/guides/HKT.html#how-to-type-functions-which-abstracts-over-type-classes)

型クラスの宣言では対応する`URIS`と`Kind`が必要になります

```ts
import type { URIS2, Kind2 } from 'fp-ts/HKT';

export interface Functor2<F extends URIS2> {
  readonly URI: F;
  readonly map: <E, A, B>(fa: Kind2<F, E, A>, f: (a: A) => B) => Kind2<F, E, B>;
}
```

`URIS2`は

```ts
type URIS2 = keyof URItoKind2<any, any>
```
と定義されているので`URItoKind2`を拡張してプロパティを増やすと自動で`URIS2`がとれる値が増えることになってます

`Kind2`も同様に

```ts
type Kind<URI extends URIS, A> = URI extends URIS ? URItoKind<A>[URI] : any
```
となっているので`URItoKind2`の拡張に応じて、`URIS2`に対応する型を返すようになります

## 型クラス制約付きの関数

[公式の説明](https://gcanti.github.io/fp-ts/guides/HKT.html#how-to-type-functions-which-abstracts-over-type-classes)

例えば関数をリフトする関数`lift`を作るとする、`lift`は`map`を利用するため、Functorのインスタンスが必要になる。

引数を１つ取る型に対しての実装は次のようになる

```ts
import type { Kind, URIS } from 'fp-ts/HKT';
import type { Functor1 } from 'fp-ts/lib/Functor';

export function lift<F extends URIS>(F: Functor1<F>): <A, B>(f: (a: A) => B) => (fa: Kind<F, A>) => Kind<F, B> {
  return (f) => (fa) => F.map(fa, f);
}
```

> TDB: 公式の説明では`HKT`を使っていますが、HKTの使い方については確認中です


アドホック多相がないため明示的にFunctorのインスタンスを渡している

これは次のように利用する

```ts
import * as O from "fp-ts/Option"
const optionLift = lift(O.Functor)

const f = (a: number): string => a.toString()

console.log(
  optionLift(f)(O.of(1))
)
// => { _tag: "Some", value: "1"}
```

これではEitherなどの引数を多く取る型には使えないので以下の拡張する

```ts
import type { Kind, Kind2, Kind3, Kind4, URIS, URIS2, URIS3, URIS4 } from 'fp-ts/HKT';
import type { Functor1, Functor2, Functor3, Functor4 } from 'fp-ts/lib/Functor';

function lift<F extends URIS>(F: Functor1<F>): <A, B>(f: (a: A) => B) => (fa: Kind<F, A>) => Kind<F, B>;
function lift<F extends URIS2>(F: Functor2<F>): <E, A, B>(f: (a: A) => B) => (fa: Kind2<F, E, A>) => Kind2<F, E, B>;
function lift<F extends URIS3>(F: Functor3<F>): <R, E, A, B>(f: (a: A) => B) => (fa: Kind3<F, R, E, A>) => Kind3<F, R, E, B>
function lift<F extends URIS4>(F: Functor4<F>): <S, R, E, A, B>(f: (a: A) => B) => (fa: Kind4<F, S, R, E, A>) => Kind4<F, S, R, E, B> {
  return (f) => (fa) => F.map(fa, f);
}

```

これでEitherに対しても`lift`がつかえるようになった

```ts
import * as O from "fp-ts/Option"
import * as E from "fp-ts/Either"
const optionLift = lift(O.Functor);
const eitherLift = lift(E.Functor);

console.log(optionLift((a: number): string => a.toString())(O.of(1)));
// => { _tag: "Some", value: "1"}
console.log(eitherLift((a: number): string => a.toString())(E.right(1)));
// => { _tag: "Right", right: "1"}
```

## モナド変換子

### `StateT`
例として`StateT`を使って`StateT s Option a`を作る

`StateT s Option a`専用に`map`などを作ってあげれば他のモナド同様に記述できる

```ts
import * as O from 'fp-ts/Option';
import * as ST from 'fp-ts/StateT';

const map = ST.map(O.Functor);
const of = ST.of(O.Pointed);
const evaluate = ST.evaluate(O.Functor);

console.log(
  evaluate({hello:'world'})(pipe(
    of<number,{hello: string}>(1),
    map((e)=>{
      return e.toString()
    }),
  ))
)
// => { _tag: 'Some', value: '1' }
```

`map`や`of`は生成用の関数が提供されているが、`bind`や`get`は`put`はない

なので少し工夫する必要がある

### `bind`

`bind`を作るためには`StateT s Option a`を`Chain`インスタンスにする必要がある

Chainインスタンスにした後で`fp-ts/Chain`から提供されるChainの制約付きの`bind`を作る関数`bind`にChainインスタンスを渡すと`bind`が作れる

```ts

import * as C from 'fp-ts/Chain';
import * as O from 'fp-ts/Option';
import * as ST from 'fp-ts/StateT';
import { pipe } from 'fp-ts/function';
import type { Applicative2 } from 'fp-ts/lib/Applicative';
import type { Chain2 } from 'fp-ts/lib/Chain';
import type { Functor2 } from 'fp-ts/lib/Functor';

const URI2 = 'StateOption';
type URI2 = typeof URI2;

type StateOption<S, A> = ST.StateT1<'Option', S, A>;

// 型クラスインスタンスを作るためURItoKindに登録する
declare module 'fp-ts/lib/HKT' {
  interface URItoKind2<E, A> {
    readonly [URI2]: StateOption<E, A>;
  }
}

// 型クラスのメソッドを作る
// 'fp-ts/StateT'から提供されてるメソッドにより簡略化できる
const map = ST.map(O.Functor);
const ap = ST.ap(O.Chain);
const of = ST.of(O.Pointed);
const chain = ST.chain(O.Chain);

// 各インスタンスを作る
// ST.mapによって作られる`map`とは引数のもらい方が違うため調整する
const Functor: Functor2<URI2> = {
  URI: URI2,
  map: <E, A, B>(ma: StateOption<E, A>, f: (a: A) => B): StateOption<E, B> => {
    return map(f)(ma);
  },
};

// map同様引数を調整する
const Applicative: Applicative2<URI2> = {
  ...Functor,
  of,
  ap: <E, A, B>(fab: StateOption<E, (a: A) => B>, fa: StateOption<E, A>): StateOption<E, B> => {
    return ap(fa)(fab);
  },
};

// map同様...
const Chain: Chain2<URI2> = {
  ...Applicative,
  chain: <E, A, B>(fa: StateOption<E, A>, f: (a: A) => StateOption<E, B>): StateOption<E, B> => {
    return chain(f)(fa);
  },
};

// Chainの制約付きのbindを作る関数`C.bind`を使ってbind関数を作る
const bind = C.bind(Chain);

```

## `get`と`put`

`fp-ts/State`の`get`と`put`を使って一旦`State s a`にしてから`State s Option a`に戻す

```ts
import * as O from 'fp-ts/Option';
import * as S from 'fp-ts/State';
import * as ST from 'fp-ts/StateT';

const fromState = ST.fromState(O.Pointed);

const get = <S>()=>fromState<S,S>(S.get<S>())
const put = <S>(s:S)=>fromState<S,void>(S.put<S>(s))
```

## 使用例

```ts

// 与えられた関数の処理を後続する関数に繋がないための関数
const chainFirst = C.chainFirst(Chain);

type S = { hello: string }
console.log(
  evaluate({hello:'world'})(pipe(
    of<number,S>(1),
    bind("before",()=>{
      return get()
    }),
    // `put`は`State s Option void`を返すため`chain`で繋ぐとそれまでの処理が捨てられるので、`chainFirst`を使う
    chainFirst(()=>{
      return put({hello:"fp-ts"})
    }),
    bind("after",()=>{
      return get()
    }),
  ))
)
/* => {
  _tag: 'Some',
  value: { before: { hello: 'world' }, after: { hello: 'fp-ts' } }
}
*/
```

## 名前の違うものたち

|型クラス|Haskell|fp-ts|コメント|
|-|-|-|-|
| Functor | fmap | map ||
| Applicative | <*> | ap |Apply型クラスのメソッドとして定義されている|
| Monad | >>= | chain | Chain型クラスのメソッドとして定義されている|
| Monad | return | of | Pointed型クラスのメソッドとして定義されていて、PointedをApplicativeが継承し、さらにMonadが継承している|
| Monad | a <- hoge | bind("a", ()=> hoge) ||
|| Maybe | Option |Scalaに影響を受けていると思われる|

> TBD

## 参考

https://gcanti.github.io/fp-ts/

https://qiita.com/kalzit/items/483e4d210c0c187787a8