やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

数学全然わからないけどElixirでMonadを作ってみる

前回までのあらすじ📖

「入門Haskellプログラミング」を読み進めながら、理解を深めるためにElixirを使ってHaskellFunctorApplicativeを再現してみました。

www.okb-shelf.work

www.okb-shelf.work

Functorは上手く再現出来ましたが、Applicativeはデフォルトで部分適応されるというHaskellの仕様がなければ、完全再現することが出来ず、カリー化した関数で近しいものを再現しました。

FunctorApplicativeを用いることでがコンテナ、コンテキスト内の値(例: リスト)に対して以下の操作をすることが出来ます。

  • 引数1つの関数をコンテナ、コンテキスト内の値に対して適応する(Functor : fmap)
  • コンテナ、コンテキスト内の部分適応された関数をコンテナ、コンテキスト内の値に対して適応する(Applicative : app)
    • 引数2つ以上の関数を適応することが出来る
  • コンテナ、コンテキスト外の値をコンテナ、コンテキスト内に格納する(Applicative : pure)


さて、これだけの操作がコンテナ、コンテキスト内に対して可能になりましたが、現状ではまだ出来ないことがあります。その問題を解決するためにMonadが必要なのです。

Monadとは🤔

HaskellではMonadFunctorApplicativeと同様にクラス(型)として定義されており、関数を定義しています。

よく言われるカタカナ表記の「モナド」とはHaskellにおいてはMonadクラス(型)のインスタンスに持つクラス(型)の総称です。試しにMaybeクラス(型)についての情報を見てみると、Monadインスタンスに持つ事が分かります。Maybeクラス(型)はモナドの1種です。

*Main> :info Maybe
type Maybe :: * -> *
data Maybe a = Nothing | Just a
        -- Defined in ‘GHC.Maybe’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Eq a => Eq (Maybe a) -- Defined in ‘GHC.Maybe’
instance Functor Maybe -- Defined in ‘GHC.Base’
:

qiita.com

またFunctorApplicativeスーパークラスであり、ApplicativeMonadスーパークラスです。関係性としてはFunctor < Applicative < Monadとなっています。

class Applicative m => Monad m where...
class Functor f => Applicative f where...
class Functor f where...

Monadが解決する問題

FuntorApplicativeでは対応出来ない問題は「関数を適応した結果がコンテナ、コンテキスト内の値となる場合」です。言葉で説明するのも理解するのも難しいので、まずはMonadの定義を見てみます。

class Applicative m => Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
  (>>) :: m a -> m b -> m b
  return :: a -> m a

Monadは3つの関数を定義していますが、今回は簡単のため>>=(bind)についてのみ説明をします。 bind関数の定義を見てみると以下のようになっています。

  • Monadクラス(型)の値(m a)を第1引数で受け取る
  • 第2引数では第1引数で受け取ったコンテナ、コンテキスト内の値を取り出して受け取り、新たなコンテナ、コンテキスト内の値を返す関数((a -> m b))を受け取る
  • 第2引数の関数の実行結果(m b)を返す


関数を適応した結果がコンテナ、コンテキスト内の値となる場合

(a -> m b)からも明らかですが、m a -> (a -> m b) -> m bという定義が上記の通りになっています。

コードサンプル

次にbindを用いた具体的なコードを見てみます。Haskellをあまり知らなくても理解出来るようにListを用います。

copyInt :: Int -> [Int]
copyInt n = [n, n]

copyIntは受け取った数値を2つListに格納して返すシンプルな関数です。

*Main> copyInt 1
[1,1]
*Main> copyInt 2
[2,2]

この関数を1から5の要素を持つListに適応させるためにbind関数を使用します。
(bindListから値を順に取り出して関数を適応させていきます)

applyBind = [1 .. 5] >>= copyInt
-- [1,1,2,2,3,3,4,4,5,5]

[[1,1], [2,2] ... [5,5]]という戻り値を想定していましたが、[1,1,2,2...5,5]という想定外の戻り値が得られました。自分もかなり驚いたのですが、HaskellではListに対してのbindは以下のように定義されています。

instance Monad [] where
    m >>= f  = concatMap f m
*Main> :info concatMap
concatMap :: Foldable t => (a -> [b]) -> t a -> [b]

wiki.haskell.org

Foldableクラス(型)はListインスタンスに持つので、concatMapは使用可能です。

*Main> concatMap copyInt [1 .. 5]
[1,1,2,2,3,3,4,4,5,5]

関数を適応した各要素にconcat関数を適応するのがconcatMapです。

*Main> concat [[1,2],[2,2],[3,3],[4,4],[5,5]]
[1,2,2,2,3,3,4,4,5,5]


戻り値の謎が解けたところで、applyBind = [1 .. 5] >>= copyIntについて考えてみます。bindの定義にあったように第1引数の値をコンテナ、コンテキスト内から取り出して、第2引数の関数へ受け渡し(今回はcopyInt)、その戻り値であるMonadクラス(型)(List)を受け取っています。

さらに戻り値がMonadクラス(型)であるため、bindを連続で呼び出すことも可能です。Monadbindの真の力は連結することで発揮されます。

*Main> [1, 2] >>= copyInt >>= copyInt
[1,1,1,1,2,2,2,2]
*Main> [1, 2] >>= copyInt >>= copyInt >>= copyInt
[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2]

繰り返しコンテナ、コンテキスト内に対して処理を適応出来るというのは非常にパワフルです。

いざElixirで作ってみる🧪

前回のApplicativeとは異なり、今回は言語の制約を受けなくて済んだのであっさりと実装出来ました。

defmodule Monad do
  def return(v), do: [v]
  def bind(lst, func) do
    Enum.map(lst, func) |> List.flatten()
  end
end

defmodule Func do
  def copyInt(n), do: [n, n]
end

実態はconcatMapの実装をElixirに書き換えただけです。

concatMap :: Foldable t => (a -> [b]) -> t a -> [b]

さっそく実行してみます。

[1,2,3,4,5]
|> Monad.bind(fn n -> Func.copyInt(n) end)
|> IO.inspect()

# [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]

同じように連結することも出来ます。

[1,2,3,4,5]
|> Monad.bind(fn n -> Func.copyInt(n) end)
|> Monad.bind(fn n -> Func.copyInt(n) end)
|> IO.inspect()

# [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]

無事に再現出来ました🎉

まとめ📖

  • MonadFunctorApplicativeでは解決できない問題を解決するために必要
    • 問題: 適応する関数がMonad(コンテナ、コンテキスト内の値)を返す場合に適応できない
  • bindm a -> (a -> m b) -> m bというように定義され、上記の問題を解決するための関数である
  • HaskellではListに対してbindFoldable t => (a -> [b]) -> t a -> [b]と定義している
  • bindMonadクラス(型)を返すため連結することが可能で非常にパワフルである

ようやく「入門Haskellプログラミング / UNIT5 コンテキストでの型の操作」にて取り扱われているFunctor, Applicative, Monadの章が終了しました。読み始める前はMonadについて全く分かっていなかった、分かったつもりになっていましたが、何とか誰かに説明出来るようになりました。

数学全然わからない自分でも読み進められる、「入門Haskellプログラミング」は非常におすすめです。少々、分厚いのが難です😓

【読書レビュー】「岩田さん 岩田聡はこんなことを話していた。」を読みました

どんな本なのか📚

タイトルにもある「岩田さん」とは、とある人物の実名です。
「岩田 聡」さんは皆さんがご存知の任天堂(Nintendo)の社長をされていた方で、我々がよく知る「Nintendo DS」や「Nintendo Wii」「カービィシリーズ」「スマッシュブラザース」など、数多くの名作をこの世に生み出しました。

ja.wikipedia.org

残念ながら、岩田さんは2015年に亡くなってしまいました。
しかし生前に岩田さんがお話しされていた数多くの言葉やエピソードを公開しないのは「もったいない!」という思いから「MOTHER2」のプロデューサーであり、生前、岩田さんと親密な関係であった糸井重里さんが、「ほぼ日刊イトイ新聞」と任天堂のウェブサイト「社長が訊く」という内容を元に、自身の体験と任天堂の元社長の宮本茂さんの言葉を加えて再構築された書籍です。

ずっと読もうと思っていた本で、偶然にも美品を安価で発見したので、衝動買いしました。

www.nintendo.co.jp

www.1101.com

各章の印象深い内容👓

なんと、リンク先のページにて書籍の半分の第3章まで無料で読めるそうです👀

www.1101.com

第1章: 岩田さんが社長になるまで

岩田さんが任天堂の社長になるまでの内容です。岩田さんは高校生の時に、ヒューレット・パッカード社のプログラム電卓「HP-67」を購入してスタートレックをテーマにゲームを開発し、そのゲームをヒューレット・パッカード社の代理店に送り「とんでもないプログラマーがいる!」と驚かせたそうです。高校時代で既にプログラマーとしてかなりハイレベルだったとは...すごい方です。

その後、HAL研究所プログラマーとして働いた後、任天堂の社長に就任されました。

https://img.aucfree.com/p569073288.1.jpg

これがHP-67。電卓でプログラムを書くってどんな感じなんだ...??

第2章: 岩田さんのリーダーシップ

岩田さんはよく「自分たちが得意なことはなにか」ということを意識されていました。
自分たちが得意なことを考えて優先順位を決めていくことが非常に重要だと考えていたそうです。これはプロダクト開発においても同じことが言えます。適切に優先順位を定めていかないとプロダクトは破綻します。

「自分たちが得意なことはなにか」ということを基軸に優先順位を定めるというのは基本中の基本ですが、最も重要なことです。

またプロジェクトでは「自分がやっておきましょうか?」という様な当事者意識があるメンバーが多くいる場合に成功すると岩田さんは仰っています。当事者意識を持ってもらうためにも、岩田さんは「自分が出来ないことが出来る人を尊敬する」ということを大切にされていたそうです。

www.nintendo.co.jp

この章で最も痺れたのは「面談では相手が答えやすいことから聞く」という事です。まずは相手をほぐして話しやすい状態にして、思ったことを話してもらえるようにしていたとのことです。

第3章: 岩田さんの個性

岩田さんは気になったことや疑問があると「なぜなのか」ということをひたすら追求する方だったそうで、会話中に突然、黙り数秒経った後に「さっきの事ですけど...」と話し始めることがあったそうです。それだけ1つの事に没頭出来るのは素直に羨ましいです。

また、岩田さんは「プログラマーはNOとは言っていけない」という持論を持っていらっしゃいました。これは「無理をしてでも絶対やれ!」という意味合いではなく、頭ごなしに「出来ない」と言うのではなく、実現できる方法を考えることが重要だということです。
実現できる方法があったとしても優先順位を決める必要があります。「〇〇という風にやれば出来るけど、他の開発が遅れる」という具体的な指針を出せるのがプロだと岩田さんはおっしゃっています。

www.1101.com

これについてはまさにその通りですね。出来る方法を考えるというのが我々の一番の仕事です。

第4章: 岩田さんが信じる人

岩田さんは任天堂の元社長である宮本茂さんのことを非常に尊敬しており「宮本ウォッチャー」と自称していたそうです。そんな宮本さんの持論である「アイディアとは複数の問題を一気に解決すること」という考えを大切にされていました。

またコンピューターに精通していた岩田さんは「コンピューターに出来ることはコンピューターにやらせる」という事を重要に考えており、多くのメンバーの仕事を仕組み化したり、制度化したりとやるべき仕事に専任してもらえるようにしていたそうです。

第5章: 岩田さんの目指すゲーム

Wiiのプロジェクトを進める中で、電源に繋げて、テレビに出力しなければいけないゲーム機をどのようにして日常に取り込んでもらえるかということを非常に重視されてそうです。その1つのアイディアとしてWiiの場合は「コントローラー」という単語を使わずに「リモコン」という単語から「Wiiリモコン」という名称が誕生したそうです。

リモコンという普段使いされるものの名前を付けたのは岩田さんで「日常使いしてもらうには...」という疑問に対する合理的な判断の結果だそうです。

またゲーマーというのはコアゲーマーとライトゲーマーの2つに大きく分類されるのですが、コアゲーマーも元を辿れば、みなライトゲーマーでした。なので新規のゲーマーを取り入れ続けることがゲームという業界で生き抜くためには必要になります。そのために脳トレやフィットネス、動物との触れ合いをゲームという体験に仕上げてきたそうです。

www.nintendo.co.jp

第6章: 岩田さんを語る

宮田さんと糸井さんが岩田さんについて語っています。
岩田さんはとにかく怒らない人だったそうで、人の話を楽しそうに聞き、頭ごなしに否定をしなかったそうです。 常に視座が低く、弟役に徹していたそうで、岩田さんが多くの方に人格者と言われる理由が分かりました。

総評✋

岩田さんがどのように考えて、何を重要にしていたのかという事が余す事なく記載されていました。僕はプログラマーなので岩田さんをプログラマーとして尊敬していますし、プロジェクトリーダー、経営者としても素晴らしい方だったと思っています。

しかし、特別に難しい事はなく、シンプルなことを岩田さんは大切にされていました。ただ、このシンプルなことを大切にするのがとても難しいんですよね。岩田さんに少しでも近づけるように頑張っていこうと思います🙇‍♂️

数学全然わからないけどElixirでApplicativeを作ってみる

前回のあらすじ📖

www.okb-shelf.work

「入門Haskellプログラミング」という書籍を読み続けながら、理解を深めるために自分でFunctorを実装してみました。今回は、Functorに引き続き、Applicativeに挑戦してみます。
先に結論ですが、ElixirApplicativeを作るのは言語の制約上、かなり難しいです。マクロという機能を使えば、似たものには出来ますが、HaskellApplicativeのパワフルさを完全再現することは出来ませんでした。

マクロについてはこちらをどうぞ。

www.okb-shelf.work

簡単におさらい / Functorとは🤔

前回、作ってみたFunctorfmapはコンテナもしくはコンテキスト内の値に対して、関数を適応することが可能でした。Haskellfmapは第1引数に適応させる関数((a -> b))を受け取り、第2引数で受け取った値(f a)に対して、関数を適応した値(f b)を返します。

HaskellでのFunctorfmapの定義

fmap :: (a -> b) -> f a -> f b

最も身近なfmapがリストに対して定義されていました。それはmap関数です。fmapmap関数の定義を見比べてみれば明らかでした。

--  Functor []
fmap :: (a -> b) -> [a] -> [b]

-- Data.List map
map :: (a -> b) -> [a] -> [b]

Functorの制約⛓

しかしFunctorはある制約を抱えています。それは適応する関数の引数が1つの場合にしか対応出来ないという点です。例を見てみましょう。

受け取った数値に対して、m分だけ加算をする単純なaddNという関数を定義したので、この関数をfmapを使って、1から10の値を持つリストに適応してみます。ただし、この関数は引数を2つ受け取る必要があることに注目です。

addN :: Int -> Int -> Int
addN n m = n + m

applyAddN :: [Int]
applyAddN = fmap addN [1 .. 10]

すると以下のようなエラーが発生するためコンパイルをすることが出来ません。

• Couldn't match type ‘Int -> Int’ with ‘Int’
  Expected type: [Int]
    Actual type: [Int -> Int]
• In the expression: fmap addN [1 .. 10]
  In an equation for ‘applyAddN’:
      applyAddN = fmap addN [1 .. 10]

fmapの第1引数の定義が(a -> b)となっているので当然ですが、引数が1つの関数以外は受け付けません。引数が1つの関数として定義してあげれば、このエラーは発生しなくなります。

add10 :: Int -> Int
add10 n = n + 10

applyAdd10 :: [Int]
applyAdd10 = fmap add10 [1 .. 10]
-- [11,12,13,14,15,16,17,18,19,20]

この制約はHaskellだけのものではありません。ElixirEnum.map関数でも、同じ制約がなされています。JavaScriptは適応する関数の引数が可変で1, 2, 3の場合があるようですが、任意の値を渡すことが出来ないという視点では同様の制約があると判断出来ます。

map(t(), (element() -> any())) :: list()
applyAdd10 = [1,2,3,4,5,6,7,8,9,10] |> Enum.map(fn n -> n + 10 end)
const applyAdd10 = [1,2,3,4,5,6,7,8,9,10].map(n => n + 10);

hexdocs.pm

developer.mozilla.org

Applicativeの登場

このFunctorの制約をクリアすることが出来るのがApplicativeというクラス(型)です。Applicativeはコンテナ、コンテキスト内(モナドインスタンス)で部分適応された関数を適応させることが出来ます。

hackage.haskell.org

部分適応について知りたい方は過去の記事を読んでみてください。 www.okb-shelf.work

Applicativeには<*>(appと読むそう)という関数が定義されています。この関数の型の定義は以下のようになっています。

class Functor f => Applicative f where
  (<*>) :: f (a -> b) -> f a -> f b

Functorfmapにかなり似ていますね。2つを比べてみます。

fmap :: (a -> b) -> [a] -> [b]
(<*>) :: f (a -> b) -> f a -> f b

異なるのが、第1引数です。fmapはコンテナ、コンテキスト外(モナドではない)の関数((a -> b))を引数に受けとりますが、appはコンテナ、コンテキスト内(モナドインスタンス)にある関数(f (a -> b))を引数に受け取ります。

コンテナ、コンテキスト内にある関数を引数に受け取れるというのは、コンテナ、コンテキスト内で関数を部分適応した場合に、その関数をそのまま受け取って、要素に対して適応が出来るということです。

いざApplicative

言葉で説明、理解するのは難しいので例を見ていきます。適応させる関数は先ほど挫折した、addN関数です。この関数は2つの引数を受け取ります。

addN :: Int -> Int -> Int
addN n m = n + m

以下が先ほどエラーとなった記述です。

applyAddN :: [Int]
applyAddN = fmap addN [1 .. 10]

ここにapp(<*>)を付け加えます。

applyAddN :: [Int]
applyAddN = fmap addN [1 .. 10] <*> (pure 10)

これでエラーは発生せず、思った通りの値が返ってくるのですが、何やら見慣れない状態になってしまいました。
しかし順に値を追っていけば、そう難しいものではありません。まずfmap addN [1 .. 10]が返す値について考えてみます。fmapの定義fmap :: (a -> b) -> f a -> f bより、返ってくる値はコンテナ、コンテキスト内に存在するbの値です。

Haskellではデフォルトで全ての関数が部分適応されるので、addNに対して、第1引数だけを受け渡して部分適応された関数がコンテナ、コンテキスト内でbとして返っています。

fmap addN [1 .. 10]という呼び出しでエラーになっていないことからも明確です。

*Main> applyAddN = fmap addN [1 .. 10]
*Main> :t applyAddN
applyAddN :: (Num a, Enum a) => [a -> a]

現在、コンテナ、コンテキスト内に部分適応された関数が返っている状態です。もうお分かりですね。コンテナ、コンテキスト内の関数を引数に受け取り、処理を適応するのはapp(<*>)の役目です。

applyAddN :: [Int]
applyAddN = fmap addN [1 .. 10] <*> (pure 10)

先程の定義通りになりました。最後にpureという構文について簡単に説明しておきます。Applicativeにはappの他にpureが定義されています。

class Functor f => Applicative f where
  pure :: a -> f a

定義を見てわかるように、受け取った値をApplicativeのクラス(型)にするというだけのシンプルな関数です。要するにコンテナ、コンテキスト外の値をコンテナ、コンテキスト内に引きずり込むための関数です。addNの第2引数に渡したい10はそのままだとInt型の値なので、appの戻り値エラーになってしまいます。

applyAddN :: [Int]
applyAddN = fmap addN [1 .. 10] <*> 10

• No instance for (Num [Int]) arising from the literal ‘10’
• In the second argument of ‘(<*>)’, namely ‘10’
  In the expression: fmap addN [1 .. 10] <*> 10
  In an equation for ‘applyAddN’:
      applyAddN = fmap addN [1 .. 10] <*> 10

apppureという2つの関数を定義することでApplicativeは2つ以上の引数を持つ関数への処理の適応を可能とします。

いざElixirで作ってみる🧪

...。

最初に記述した通り、Elixirでは言語の制約上、HaskellでのApplicativeと同等のものを作るのは非常に難しいです。なぜならHaskellはデフォルトで関数を部分適応するからです。HaskellApplicativeが強力なのは、この仕様があるからこそです。デフォルトで部分適応がされない以上、自分で部分適応がされるようにカリー化した関数を定義するか、マクロを作成する他、ありません。

今回は簡単のため、カリー化した関数を用いました。引数2つの状態のままの関数では再現が出来ませんでした。

defmodule Applicative do
  def app(func, v), do: func.(v)
  def pure(v), do: [v] # 一例としてListに対しての定義
end

defmodule Func do
  def addN(n), do: fn m -> n + m end
end

Applicativeapppureに関してはHaskellに定義されていた引数に近しい状態に出来ました。適応させる関数は先程も登場したaddNですが、上記にもあるようにカリー化することでしか再現することが出来なかったので、元々、引数2つで定義していたものをカリー化して引数1つの関数を返すようにしています。

addN :: Int -> Int -> Int
addN n m = n + m
def addN(n), do: fn m -> n + m end

実行してみます。

Enum.to_list(1..10)
|> Enum.map(fn n -> Func.addN(n) end)
|> Enum.map(fn func -> Applicative.app(func, 10) end)
|> IO.inspect() # [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

重要なのは、Enum.mapの中で、Func.addN(n)を呼び出した後の戻り値です。以下のように出力されます。この時点で、カリー化した関数が返り部分適応されていることが分かります。

Enum.to_list(1..10)
|> Enum.map(fn n -> Func.addN(n) end)
|> IO.inspect()

# [#Function<0.17272158/1 in Func.addN/1>, #Function<0.17272158/1 in Func.addN/1>,
#  :
#  :
#  #Function<0.17272158/1 in Func.addN/1>, #Function<0.17272158/1 in Func.addN/1>]

そして、appによって部分適応された関数を適応するだけです。
pureについては簡単のため受け取った値を要素に含めたリスト返すようにしました。実行結果は自明だと思うのです、割愛します。

まとめ📖

ApplicativeFunctorの制約をクリアするために非常に便利なクラス(型)です。
Functorfmapでは引数が1つの関数のみしか適応することが出来ませんが、Applicativeappは、コンテナ、コンテキスト内にある関数に対しても処理を適応することが出来るので、Functorをより強力にしたクラス(型)と言えます。

しかし、ApplicativeHaskellの言語仕様である全ての関数がデフォルトで部分適応されるからこそ、強力です。他言語でApplicativeを再現しようとしても、デフォルトと部分適応されない以上は同等の力を引き出すことは難しく、個別にカリー化した関数などを定義する必要があります。

非常に面白いですね。次は大本命のMonadです。

【JavaScriptで解説】部分適用って何?カリー化との違いは?

部分適用について🤔

部分適用とは一言でいえば、「関数の引数を一部だけ固定して、新しい関数を作るための仕組みの総称」です。

よく混同されるカリー化とは別の言葉で、カリー化よりも広い意味を扱います。カリー化は部分適用を行う方法の1つです。その他にも部分適用を行う方法はいくつかあります。

部分適用を行う2つの方法✒️

カリー化

例として以下の関数を使用します。この関数は2つの引数を受け取り、受け取った2つの値を足し合わせます。

const addN = (n, m) => n + m; 
console.log(addN(1, 1)); // 2
console.log(addN(1, 10)); // 11

このような複数の引数を受け取る関数を、関数から関数を返すことで1つずつ引数を受け取るように分割することが出来ます。
この手法をカリー化と呼びます。

const addN = (n) => (m) => n + m;
// [Function: addN]

引数を1つ与えて実行してみて、返ってくる値を確認してみます。

const addN = (n) => (m) => n + m;
const add1 = addN(1);
console.log(add1); // [Function (anonymous)]

関数として値が返ってきていることが分かります。この時点でadd1nとして定義された引数の受け取りが完了して(m) => n + mという関数を値として返しています。

このadd1は関数なので、さらに引数を渡して実行することが出来ます。

const addN = (n) => (m) => n + m;
const add1 = addN(1);
const result = add1(10);
console.log(result); // 11


複数の引数を受け取る関数を、関数から関数を返すことで1つずつ引数を受け取るように分割すること

これがカリー化です。渡す値を変えれば様々なバリエーションの関数を定義することが出来ます。

const addN = (n) => (m) => n + m;
const add10 = addN(10);
const add100 = addN(100);
const add1000 = addN(1000);

console.log(add10(1)); // 11
console.log(add100(1)); // 101
console.log(add1000(1)); // 1001

また引数の数には上限はないので、分割をしまくれば、いくらでもカリー化をすることも出来ます。

const add5times = (a) => (b) => (c) => (d) => (e) => a + b + c + d + e;
const result = add5times(1)(1)(1)(1)(1);
console.log(result); // 5


定数を用いる(ラッパー関数を定義する)

もう1つの方法として関数をラップした別の関数を定義することでも部分適用をすることが出来ます。例として2つの引数を持つaddN関数をラップして、10を固定で足し合わせる関数を作成してみます。

const addN = (n, m) => n + m;
const add10 = (m) => addN(10, m);
console.log(add10(1)); // 11

引数の一部を固定化することで、新たな関数を定義することが出来ました。これも同じように部分適用がされていると判断出来ます。

このラッパー関数はカリー化と同じようにいくらでも作ることが出来ます。

const addN = (n, m) => n + m;
const add10 = (m) => addN(10, m);
const add100 = (m) => addN(100, m);
const add1000 = (m) => addN(1000, m);

console.log(add10(1)); // 11
console.log(add100(1)); // 101
console.log(add1000(1)); // 1001

以上です。

おまけ / 第一級関数について

実は今回紹介したカリー化が出来る言語と出来ない言語があります。その違いが第一級関数をサポートしているかどうかということです。第一級関数とは簡単に言えば、「関数を値として扱うことが出来るかどうか」という点です。

先ほど紹介した例では、関数を変数に代入していました。これは関数を値として扱うことが可能でなければ出来ないことです。

const addN = (n, m) => n + m;

また、関数を引数に渡したり、関数から関数を返すというのも関数を値として扱っているからです。

[1,2,3].map((n) => n + 10); // 関数を引数に渡している
const addN = (n, m) => n + m; // 関数から関数を返している

すなわちJavaScript第一級関数をサポートしていると言えます。

プロダクトにおいて営業と開発は対等であるという意識の重要さ

お客様の声🎤

プロダクトが売れ始めて、ある程度、成長を続けていくとお客様から様々な声が上がってきます。

www.okb-shelf.work

中でも最も多いのは新機能の開発要望です。小さな変更からプロダクトのコンセプトを覆してしまうような大きな変更まで大小様々です。そんな声を我々、開発者の元まで運んでくれるのが営業の仕事の1つです。

営業を通して自分のような開発者は「こういう機能が欲しい」という話を初めて耳にすることが出来ます。しかし、ここで問題が発生することがあります。

日にちを約束してくる🗓

「こういう機能が欲しい」という声はプロダクトをより良くするため、既存の問題を解決するためには無くてはならないものです。また、さらに多くのお客様に使って頂くためにも非常に参考になるものです。

しかしながら、目先の利益のために、機能の開発を約束してしまうという問題が頻発しました。

「分かりました。A機能を来月までに作ります!」 」

...

というようにお客さんに機能の提供と期限を約束してきてしまうのです。

www.sonicgarden.jp

発生する問題

突発的に機能の追加を半強制的にやらなくてはいけなくなります。しかも謎の期限付きです。
例とした来月までというのは見積もりをとった期限ではないので、機能によっては無茶苦茶な期限で設定されている可能性もあります。開発リソースが確保出来ない問題もあります。

さらに問題であるのは、その追加を検討している機能が十分に議論されていないという点です。

1つの機能を追加することで、プロダクトのコンセプトを大きく変えてしまう可能性があります。
また、既存の他のお客様にとっては使い勝手が悪くなる、他に待ち望んでいた機能の開発が遅れてしまうという問題も起きてくるのです。

じゃあ、言い返せばいいじゃん🤔

と思うじゃないですか...。

これが言い返せない、もしくは言い返しても意味がないことが多いのです。
我々、開発者の思いは営業さんに代弁してもらう必要があります。「A機能を来月までに作ります!」と言った手前、「やっぱり出来ません!」とは言いにくいというのは想像に容易いです。

開発者が直接伝える場合も、自分たちの都合を理解してもらえることは稀です。

別のケースとして営業さんが開発者の思いに反発することもあります。
営業さんとしては自分の責務を果たすために、お客様にプロダクトを売ってきたのに、「なんでキャンセルするの!?」と思うでしょう。

つまり、お互いに立場があり、責務が異なるので思いが衝突、もしくはどちらかに傾いてしまうのは自然なことなのです。

だからこそ対等であるという意識が重要👍

そもそも今回の問題は機能の約束をしていなければ、発生していませんでした。
なぜ機能の開発を約束してしまうのかは様々な理由がありますが、一例として営業と開発が対等ではないという意識が理由としてあげられます。

営業が約束してきたものを開発が作らないといけないというのは「営業 > 開発」という関係になっていますし、営業が約束してきたものを開発が作らないというのは「営業 < 開発」という関係です。

お互いに対等である「営業 = 開発」という状態であれば、営業はその場で約束するのではなく「一旦、開発チームに相談してみます」などと言えるはずです。また開発は「こういう声が上がってきているから検討しよう。見積もってみよう」という動きがとれます。

お互いを憎むのではなく、お互い出来ない仕事をしているんだというリスペクトを持つことが非常に重要なのです。