Upgrade to Pro — share decks privately, control downloads, hide ads and more …

カリー化はナンの役に立つのか

fsubal
February 08, 2019

 カリー化はナンの役に立つのか

昔ニコナレにアップロードしていたピクシブ社内勉強会資料です。

fsubal

February 08, 2019
Tweet

More Decks by fsubal

Other Decks in Programming

Transcript

  1. 2 誰 • pixivFACTORY フロントエンド • React と Redux と

    SVG • 将来の夢は Single SVG Application です @f_subal
  2. • n 引数関数を、n 回適用可能な 1 引数の連なりに変形すること • 例 ◦ (a:

    number, b: number) => Math.max(a, b) が ◦ (a: number) => (b: number) => Math.max(a, b) になる ◦ a を渡すと、「b を渡すと number が返る関数」が返る 9 カリー化とは
  3.   // before   function getMax(a: number, b: number) { return Math.max(a,

    b) }   // after (curried)   function getMax(a: number) { return function (b: number) { return Math.max(a, b) } } 11
  4.   // before   function isLarger(a: number, b: number) { return a

    > b }   // with before   const largerThanThrees = inputs.filter(function (n) { return isLarger(n, 3) }) 11
  5.   // after (curried)   function isLarger(a: number) { return function (b:

    number) { return a < b } } // with after   const largerThanThrees = inputs.filter(isLarger(3)) 11
  6.   // after (curried)   function isLargerThan(a: number) { return function (b:

    number) { return a < b } } // with after   const largerThanThrees = inputs.filter(isLargerThan(3)) 11
  7.   // after (curried)   function isLargerThan(a: number) { return function (b:

    number) { return a < b } } // with after   const largerThanThrees = inputs.filter(isLargerThan(3)) 11
  8. • .map .filter など、JS は高階関数 API を多用する • すると、高階関数にビジネスロジックとして意味のあるパターンが現れる ◦

    .filter(isPremiumUser) とか ◦ .filter(isOwnedBy(user)) とか • こういう関数を切り出す際、カリー化しておくと便利 ◦ .map や .filter にドメインロジックが表現される ◦ 厳密に 1 引数ずつにしなくても「関数をかえす関数」にするだけでも便利 20 高階関数 の building block
  9. • 多言語出身の人は .filter(isPremiumUser) より .selectPremiumUser() と書きたがる • A. 諦めろ ◦

    経験上、クラスインスタンスにマッピングするメリットが、デメリットを上回ることはほぼ ない ◦ フロントエンドの殆どは「ユーザー入力を JSON に相互変換する処理」に過ぎない( 燃える発言 ) ◦ プレーンな関数に表現力を与える方向で議論したほうが有益( TypeScript はそれが 得意 ) 21 Q. オブジェクト指向すれば?
  10. 22

  11. 23

  12. • 関数の引数の一部だけ、予めある値を束縛しておくこと ◦ 例: (a, b) => a + b

    があるとき、b=1 を部分適用すると increment 関数になる • カリー化された関数に対し、予め 1回(あるいは数回)呼んでおくことでも実現できる ◦ f が a => b => a + b のとき、f(1) すると b => b + 1 になる ◦ isOwnedBy(user) に currentUser を適用すると isMine(item) が作れる • カリー化と部分適用はよく混同される( 別の概念です ) 25 部分適用とは
  13. 29

  14.   // without currying // 1 inch === 25.4 mm   function

    px2mm(px: number, dpi: number) { return px / dpi * 25.4 }   const dpi = 1181 / 100 * 25.4   const mm = px2mm(30, dpi) 11
  15. 例題 2) 実はいま 1181 px で 表示されてるのはたまたまで、 100mm の原稿が window

    の resize のたびに いろんな大きさ (px) になると仮定してください 33
  16. 例題 2) 実はいま 1181 px で 表示されてるのはたまたまで、 100mm の原稿が window

    の resize のたびに いろんな大きさ (px) になると仮定してください 34
  17. 11

  18. • (px, dpi) => mm という関数のうち、dpi が変わる場合がある ◦ dpi は「この

    mm がこの px で表示されている」という事実から決定される • dpi は window をリサイズしない限り変わらない • 普通に扱う場合は、dpi を固定した状態で、px → mm 換算が行えると嬉しい 36 つまり…
  19. • (px, dpi) => mm という関数を dpi => px =>

    mm という形式で表すとどうなるか ◦ makePx2Mm に dpi を部分適用すると px2mm ができる • window.resize のたびに makePx2Mm(dpi) を実行すると… ◦ 「dpi をある値で固定したときの px → mm 換算関数」ができる!! 37 そこで部分適用ですよ
  20.   // curried! // 1 inch === 25.4 mm   function makePx2mm(dpi:

    number) { return function (px: number) { return px / dpi * 25.4 } } 11
  21.   // in React Component class   public componentDidMount() { document.addEventListener('resize', ()

    => { this.setState({ currentDpi: ... }) }) } get px2mm() { return makePx2mm(this.state.currentDpi) } 11
  22.   // with Hooks (>= v16.8.0)   const [width, height] = useWindowSize()

      const currentDpi = calcDpi(...)   const px2mm = makePx2mm(currentDpi) 11
  23. • ある時点において px ➔ mm はこういう式だが、別の時点では違う式になる • 外部要因( ここでは dpi

    )を注入して関数を組み立てるような設計にしたい ◦ dpi は更に window size に依存する 43 時間経過によって変動する式
  24. • 時間経過によって変化する対象( UI )をクラス / 関数で表す • そこに内部状態( React の

    state )を閉じ込める • カリー化された関数に、内部状態を適用して式を組み立てる • あとはそれを子に引き回す( React なら Context API と合わせても良い ) ◦ 子は単に this.props.px2mm() すればよい(親の dpi は知らなくて良い) • 時間経過によって変わる関数式を使い回せて便利 44 Stateful な対象と合わせる
  25. • その通り ◦ メソッドが1個しかないクラスに対する DI みたいなものだと思って • むしろ自分は DI をクラスで部分適用するものと見ている

    ◦ new Service(arg).call(hoge) はカリー化の OOP 的解釈 ◦ 関数だと呼出が 2 段以上続くケースも作りやすい ( service(arg)(hoge)(...) ) 46 これって DI みたいなもの?
  26. • Q. 高階関数はともかくさ〜、if 文が ↓ みたいになるのイケてなくない? ◦ if ( isOwnedBy(user)(item)

    ) { • A. まぁそうですね ( uncurry しなければ ) • いま ECMAScript に提案中の pipeline 演算子がきたら ↓ みたいに書けるので辛抱してく ださい ◦ if ( item |> isOwnedBy(user) ) { 47 余談: Pipeline Operator