4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

関数型プログラミングのすゝめ

4
Posted at

TL;DR

  • コードがシンプルになる
  • テストが楽チン
  • 副作用がない
  • モダンJSは関数型推し
  • mapfilterreduceさえ覚えとけば大体イケる

きっかけ

React書いてて改めて気になったので。。

体系的なコト

関数型プログラミングは宣言型プログラミングの一種らしいです。

  • 命令型プログラミング
    • 「どうやって」(how)に着目する
    • 昔のC言語みたいな
  • 宣言型プログラミング
    • 「何をするか」(what)に着目する
    • 最近だとScalaとか

ex) 書いてみる

  • 命令型プログラミング
/** 単語を全てハイフンで繋ぐ */
const str = "I have a pen"
let convStr = ""

for (let i = 0; i < str.length; i++) {
    if (str[i] === " ") convStr += "-"
    else convStr += str[i] 
}
console.log(convStr) // I-have-a-pen
  • 宣言型プログラミング
const str = "I have a pen"
const strConnectHyphen = (str) => str.replace(/ /g, "-")

const convStr = strConnectHyphen(str)
console.log(convStr) // I-have-a-pen

考察

  • 命令型プログラミング
    • コメントがないと一見して何をしているか分からない
  • 宣言型プログラミング
    • 関数名から何をしてるかすぐに分かる(きちんと命名していれば)

なるほど。。
「やること」(what)を宣言的に記述し、
「どうやって」(how)の部分を抽象化することでコードが見やすくなるわけですね。

じゃあ関数型って?

諸説あるようですが、天下のオライリー様によると、、

  • データは全てイミュータブル(変更不可)である
  • 純粋関数である

むむ、、なんのこっちゃ。

ex) イミュータブルなデータ

  • ミュータブルな例
    • 元のオブジェクトを変更してしまう(破壊的メソッド
const theme_green = {
    title: "darkgreen",
    color: "#006400",
    opacity: 0.5
}
const changeOpacity = (color, opacity) => {
    color.opacity = opacity
    return color
}
console.log(changeOpacity(theme_green, 0.8).opacity) // 0.8
console.log(theme_green.opacity) // 0.8 <- 変わっちゃった
  • イミュータブルな例
    • 元のオブジェクトは変わらない(非破壊的メソッド
const theme_green = {
    title: "darkgreen",
    color: "#006400",
    opacity: 0.5
}
const changeOpacity = (color, opacity) => ({...color, opacity})

console.log(changeOpacity(theme_green, 0.8).opacity) // 0.8
console.log(theme_green.opacity) // 0.5 <- 変わらない(イミュータブル)

純粋関数

  • 少なくとも一つの引数をとり、値もしくは関数を戻り値として返す
  • 引数は、イミュータブルである
  • 副作用(関数外への影響)が発生しない
  • 引数以外の値を参照しない
  • 同じ引数ならば、同じ値が返ってくる

ex) 純粋ではない関数

前述の例です。引数colorが変更されているので純粋関数ではない

const theme_green = {
    title: "darkgreen",
    color: "#006400",
    opacity: 0.5
}
const changeOpacity = (color, opacity) => {
    color.opacity = opacity
    return color
}

ex) 純粋な関数

同じく前述の例。新たなオブジェクトを生成し返しているので、副作用なし

const theme_green = {
    title: "darkgreen",
    color: "#006400",
    opacity: 0.5
}
const changeOpacity = (color, opacity) => ({...color, opacity})

考察

  • 要はスコープは狭く、結合度を下げろってことですね
  • 引数さえコントロールすればいいので、テストが簡単そう

具体的にはどうしろと?

思うと、ES6以降のJSって、関数型で書けって言っているような構文ばかりですよね。
個人的によく使うケースをいくつか。。

mapfilterreduceを使い倒す

/** raw data */
const theme_colors = [
    {
        title: "darkgreen",
        color: "#006400",
        opacity: 0.5
    },
    {
        title: "tomato",
        color: "#ff6347",
        opacity: 0.7
    },
    {
        title: "navy",
        color: "#000080",
        opacity: 0.5
    },    
]
配列から任意の要素を抽出する/削除する
/** opacity=0.5の要素を抽出 */
const selected = theme_colors.filter(theme => theme.opacity == 0.5)
オブジェクトの配列から任意のプロパティだけ抽出する
/** カラーコードだけを抽出 */
const selected = theme_colors.map(theme => theme.color)
/** 新しいプロパティにして抽出 */
const selected = theme_colors.map(theme => ({code: theme.color}))
配列の任意の要素を変更する
/** 特定のタイトルを変更する */
const editThemeTitle = (before, after, arr) =>
    arr.map(item => (item.title == before ? {...item, title: after} : item))
const changed = editThemeTitle("navy", "darkblue", theme_colors)
メソッドチェーンで繋ぐ
/** opacity=0.5の要素のタイトルだけ抽出 */
const selected = theme_colors
    .filter(theme => theme.opacity == 0.5)
    .map(theme => theme.title)
オブジェクト->配列に変換する
const theme_colors = {
    "darkgreen": "#006400",
    "tomato": "#ff6347",
    "navy": "#000080"
}
const arr = Object.keys(theme_colors).map(key => ({
    title: key,
    color: theme_colors[key]
}))
配列->単一の値に変換する
/** 最大値を求める */
const numbers = [21, 18, 42, 40, 64, 63, 34]
const maxNumber = numbers.reduce((max, number) => (number > max ? number: max), 0)
配列->オブジェクトに変換する
/** titleをkey, color,opacityをプロパティにもつオブジェクトにする */
const obj_colors = theme_colors.reduce((colors, {title, color, opacity}) => {
    colors[title] = {color, opacity}
    return colors
}, {})

高階関数を使う

関数を引数に取るか、戻り値として関数を返す、もしくはその両方」を満たす関数
mapfilterreduceは全て高階関数(関数を引数に取るパターン)

戻り値として関数を返すパターン
const userLogs = userName => message => console.log(`${userName} -> ${message}`)
/** ロギングを開始する */
const log = userLogs("John Doe")
log("logIn")

/** 非同期処理の完了を通知する */
fetchData
    .then(_ => log("success to load data.")) // John Doe -> success to load data.
    .catch(_ => log("failed to load data.")) // John Doe -> failed to load data.

再帰関数を使う

関数の中から自分自身を再帰的に呼び出す関数

const countDown = (value, fn) => {
    fn(value)
    return value > 0 ? countDown(value-1, fn) : value   
}
countDown(10, value => console.log(value))

まとめ

なんかES6構文の解説みたくなっちゃいました。。
関数型だからこう書く、ということではなく、読みやすいコードを書くことが大事なので
臨機応変にやればいいかなと思います。
多分、純粋関数を制約にしてしまったら、難しい局面とか出てきそう。。
機会があれば、React実践編みたいなのもご紹介できればと思います。

参考文献

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?