はじめに
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 で勉強させてもらいましたが、海外サイトは良質な記事が多いので、英語頑張りたいです。