Help us understand the problem. What is going on with this article?

Swiftで高階関数(Higher Order Function)を活用してスッキリ&カッコ良いコードを書く

はじめに

Swiftでプログラミングをしていても、UI周り部分の実装がメインとなり、GenericsやHigher Order Functionをあまり活用できていないように思います。
他の言語扱う中でも、必ずと言っていいほど、GenericsやHigher Order Functionは出てきますし、どうせなら、カッコ良いコード書きたいので、
Swift言語を使って復習していきます。

名前リストを挨拶リストに変換

let nameList: [String] = ["Yamada", "Tanaka", "Nakano"]

名前リストから、 敬称挨拶をつけたリストを作り出すには、 map を使ってシンプルに書くと、

let morningMrList = nameList.map { "Mr. \($0) Good morning!" }

のようになります。map を使っているので、十分スッキリ書けていると思います。

ただ、敬称を Ms. に変更したい、 挨拶を、Good evening! に変更したい場合に

let morningMsList = nameList.map { "Ms. \($0) Good morning!" }
let eveningMrList = nameList.map { "Mr. \($0) Good evening!" }

ベタ書きため再利用性がない事に気づきます。

この問題を解決するために、最初に考えられるのは、map処理の中で 敬称挨拶 を分離する。

let morningMrList2 = nameList.map { "Mr. \($0)" }.map {"\($0) Good morning!"}

処理が別れたので再利用性が高くなりそうです。

次に、名前リスト毎に文字列加工処理(敬称+挨拶)をmapでベタ書きするのは再利用できないので、
名前リストと文字列加工処理をパラメータとして受け取る関数を用意します。

関数をパラメータとして受け取る高階関数となります。

func _map<String>(_ list: [String], f: (String) -> String) -> [String] {
    return list.map { f($0) }
}

// 名前リストを挨拶リストに変換
func _mrMorningF (_ list: [String]) -> [String] {
    return _map(list) { "Mr. \($0) Good morning!" }
}
let morningMrList3 = _mrMorningF(nameList)

ただ、このコードはベタ書きであり、スッキリしません。

以下では戻り値が関数の高階関数を使って改善していきます。

戻り値が関数の高階関数を使って改善

  • 敬称、挨拶の文字列追加を分離
  • 文字列加工の関数化

この2つが実現出来れば、名前リストから挨拶リストを生成するのに、毎回ベタ書きしなくてよくなります。

上のmapでは、戻りの型が配列でしたが、戻り値を関数に書き換えます。戻り値の型に束縛されないのでGenericsで書くことができます。

func map<A, B>(_ f: @escaping (A) -> B) -> ([A]) -> [B] {
    return { $0.map(f) }
}

戻り値は、引数に任意の型の配列(A)を受け取り、任意の型の配列(B)を返す関数となります。
Genericsを使っているので、汎用的な関数を用意出来たと思います。

アロー( -> ) が連続すると、とたんに可読性が落ちますが、代わり玄人感はすごく増しますね。

このmapを使うと以下のような、敬称文字列追加、挨拶文字列追加を定義できます。

// 名前に、敬称のMrを追加する関数
let mrF: ([String]) -> [String] = map{ "Mr. \($0)" }

// 名前に、挨拶を追加する関数
let morningF: ([String]) -> [String] = map{ "\($0) Good morning!" }

nameList, nameList2のように別のリストでも、引数を変えるだけで済みます。

let morningMrList4 = morningF(mrF(nameList))
let morningMrList5 = morningF(mrF(name2List))

他の挨拶の関数も同様に書けます。

let msF: ([String]) -> [String] = map{ "Ms. \($0)" }
let afternoonF: ([String]) -> [String] = map{ "\($0) Good afternoon!" }
let eveningF: ([String]) -> [String] = map{ "\($0) Good evening!" }

これらを組み合わせれば名前から挨拶リストを作り出せるので、再利用可能です。

関数ネストを改善したい

関数がネストしているので少し見にくい気がします。

morningF(mrF(nameList))

これを解決するには、関数を合成する関数を用意すれば良いでしょう。本格的な高階関数ですね。

2つの関数を引数を受け取り、これらの関数を合成して関数を返却しています。

func compose<A, B, C>(_ f: @escaping((A) -> B), _ g: @escaping((B) -> C)) -> (A) -> C {
    return { a in g(f(a)) }
}

型に縛られない関数なので、Genericsで書くことが出来ます。

これを使うと、

let msMorningF = compose(msF, morningF)

使う側も関数がネストしていないので、スッキリ読みやすいです!

let morningMsList2 = msMorningF(name2List)

まとめ

毎回、同じ処理を書く場合は、高階関数(特に、戻り値を関数)にすることを検討すると再利用性が高いコードにすることが
出来るケースがあることが理解できました。

Swiftの高階関数の記事は、 map flatMap reduceなどの使い方を説明に閉じた記事が多いですが、
自身で高階関数を用意して使うことが本当の活用な気がします。

そして、Generics +高階関数の組み合わせは非常に強力ですね。

この記事にあたり、Point Free で勉強させてもらいましたが、海外サイトは良質な記事が多いので、英語頑張りたいです。

masa7351
Freelance Software Developer.
https://medium.com/@masa7351
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした