関数型入門(Functor編)

Kanta Inoue
nextbeat-engineering
7 min readJun 21, 2023

こんにちは!

株式会社ネクストビートでKIDSNAシッターというアプリを開発している井上と申します。

今回は、関数型プログラミングについて自己学習した内容のアウトプットとして記事を書いていきます。自己学習は会社の先輩にお薦めをいただいたすごいHaskellたのしく学ぼう!を使って進めました。この記事では、関数型プログラミングの Functor について書いています。関数型プログラミングの基本的な概念に触れてみたい人などにお読みいただければ幸いです。

1. はじめに

いきなり Functor の話になってもわからないと思うので、この章では Scala における Functor とは何の型に当たるのか見ていきます。

まず、Scala における Functor として振る舞えるものの代表例として Option, Seq, Either などが該当します。

Option などが Functor に該当する理由を端的に述べると map が実装されているからです。この map 関数は、関数型プログラミングの Functor の概念を元に実装されています。

map 関数は、関数を渡すと、ラップしている値に関数を適用してくれる便利な関数です。Scala などの関数型言語を普段書いている方は、よく使っている関数だと思います。

次章以降では Functor の概念を元に実装されていることで何が嬉しいのかを説明していきます。

2. Functor

Functor の旨みは、型の文脈(振る舞い)を維持したまま関数を適用してくれる仕組みです。

言葉ではわかりにくいと思うので、まずは Functor の定義を見ていき、その後に、Functor の旨みを説明していきます。

以下は、実際の Haskell の Functor 型クラスの定義 の一部です。

Haskell

Scala のコードは Haskell のコードを元に私が実装してみたものです。

Scala

上記の定義からは以下のことが読み取れます。

  1. Functor は 1つの型引数を取る
  2. fmap は A => B の関数と F[A] の値を取り、F[B] の値を返す(関数 A => B を取り、関数 F[A] => F[B]に変換して返す)

1つずつ見ていきます。

まず、「1. Functor は 1つの型引数を取る」を確認していきましょう。

Haskell

Haskell では、型クラス(Functor)の次に来る f が Functor 型クラスのインスタンスである型コンストラクタを表現しています。fmap のシグネチャを見ると f a ,f b とあります。この a ,b は 型コンストラクタが取る型引数です。f a b cになっていれば3つの型引数を取る型コンストラクタだということです。

このことから、Functor を実装した型コンストラクタは、1つの型引数を取る型コンストラクタだということが読み取れます。(厳密には残り1つの型引数を取る型コンストラクタだがここでは気にしない)

Scala

Scala でも同様に、何からしら1つの型引数を取る、型コンストラクタ F[_] を受け取っていますね。Scala の場合 F[_, _, _] となっている場合は、3つの型引数を取る型コンストラクタだということです。例えば、Either[_, _] は2つの型引数を取る型コンストラクタです。

_ でもアルファベットでもOKです

次に 「2. fmap は A => B の関数と F[A] の値を取り、F[B] の値を返す(関数 A => B を取り、関数 F[A] => F[B]に変換して返す)」を見ていきます。

Haskell、Scala ともにシグネチャから、Functor の値に対して何らかの関数を適用して、適用した値を Functor でラップして返す関数だということが読み取れます。

Haskell

  1. (a -> b) : A型からB型になる関数を取る
  2. f a : A 型の値をラップしている Functor を取る
  3. f b : B 型の値をラップしている Functor を返す

Scala

  1. g: A => B : A型からB型になる関数を取る
  2. func: F[A] : A 型の値をラップしている Functor を取る
  3. F[B] : B 型の値をラップしている Functor を返す

上記のことから Functor のことが何となくわかったかと思います。

ここから、Functor の旨みを説明していきます。冒頭でも述べたように、Functor は型の文脈(振る舞い)を維持したまま関数を適用してくれる仕組みです。「文脈」は定義する型によって決まります。

ここで例として「1つの値を持っているかもしれない」という文脈を持つ Haskell の Maybe、その Maybe に似た型の Scala の Option を見ていきます。

まず、Haskell の Maybe から見ていきます。

Maybe は Nothing、Just の値コンストラクタ(具象型)を持ちます。Just は1つの値を持ち、Nothing は値を持ちません。

この「1つの値を持っているかもしれない」という文脈を維持したまま関数を適用できる強みは、さまざまなユースケースで生きてきます。以下のコードを見て分かる通り、fmap は Maybe 型が Just であろうと Nothing であろうと動作します。

Just の場合:値に関数を適用して Just でラップして返す

Nothing の場合:Nothing を返す

次に Scala の Option をみていきます。

Scala では独自の Functor を実装してみました。

こちらに関しては、型クラス、型クラスのインスタンスを意識して実装しているため少し複雑になっていますが、上記の Maybe とやっていることは同じです。文脈を維持したまま、処理を行うことができます。

Functor がない場合は以下のように、処理の度に文脈を意識した関数の定義が必要になります。それを Functor を継承することで簡単に文脈を維持しながら関数を適用できます。

3. おわりに

今回は Functor の良さを簡単に説明しましたが、Functor 則といった Functor を実装する時の決まり事などもあります。

また、Functor の他にも Applicative, Monad など Functor の強化版のような型クラスも存在します。私自身もまだまだ浅い理解ですが、関数型プログラミングではその型が持つ性質を理解することで、冗長なメソッドが一気にスッキリとした読みやすいコードになることも珍しくありません。これを機に関数型プログラミングに少しでも興味を持っていただければ幸いです。

今回は、最後までお読みいただきありがとうござました。

4. 参考文献

5. 告知

We are hiring!

本記事をご覧いただき、ネクストビートの技術や組織についてもっと話を聞いてみたいと思われた方、カジュアルにお話しませんか?

・今後のキャリアについて悩んでいる
・記事だけでなく、より詳しい内容について知りたい
・実際に働いている人の声を聴いてみたい

など、まだ転職を決められていない方でも、ネクストビートに少しでもご興味をお持ちいただけましたら、ぜひカジュアルにお話しましょう!

🔽申し込みはこちら
https://hrmos.co/pages/nextbeat/jobs/1000008

また、ネクストビートについてはこちらもご覧ください。

🔽エントランスブック
https://note.nextbeat.co.jp/n/nd6f64ba9b8dc

--

--