高階関数にゅうもん1(Scalaで)
目的
- 高階関数をつかえば、重複するコードを削減できる!
- コップ本の第09章をそのまま。ちょっとだけ問題形式にしてみて何かの時ようにメモを残す。。
環境
- scala: 2.11.8
お題
- ファイル名のリストの中から、何らかの基準に合致するファイルを探したい。
Exerciseの準備
- ちょっと無理やり感がありますが許してください・・・
// ファイルをたくさんつくるので、適当にディレクトリ作ってください!
$ mkdir scala_hof && cd scala_hof // 英: higher-order function = 高階関数
$ touch {prd,stg}-{web,db}-2017{01,02,03}{01,02,03}.{log,error}
Step1: いくつか重複したコードを書いてみます。
以下のExerciseで作る関数は、このobjectの中に書いてください。
object FileMatcher {
private def filesHere = new File(".").listFiles // カレントディレクトリのファイル一覧がとれる
def echoHoge = println("hoge") // こんな感じで書く
}
【Exercise1-1】 カレントディレクトリの中から .error
で終わるファイル一覧を取得したい。シグネチャは以下のとおり。
def filesEnding(query: String): Array[File]
- Answer1-1
def filesEnding(query: String): Array[File] =
for (file <- filesHere; if file.getName.endsWith(query))
yield file
【Exercise1-2】 カレントディレクトリの中から web
を含むファイル一覧を取得したい。シグネチャは以下のとおり。
def filesContaining(query: String): Array[File]
- Answer1-2
def filesContaining(query: String): Array[File] =
for (file <- filesHere; if file.getName.contains(query))
yield file
【Exercise1-3】 カレントディレクトリの中から prd x 201703 x log
的なファイルを正規表現でマッチさせてファイル一覧を取得したい(正規表現は """prd.+201703\d{2}.log"""
)。シグネチャは以下のとおり。
def filesRegex(query: String): Array[File]
- Answer1-3
def filesRegex(query: String): Array[File] =
for (file <- filesHere; if file.getName.matches(query))
yield file
【ここまで】
import java.io.File
object FileMatcher {
private def filesHere = new File(".").listFiles
def filesEnding(query: String): Array[File] =
for (file <- filesHere; if file.getName.endsWith(query))
yield file
def filesContaining(query: String): Array[File] =
for (file <- filesHere; if file.getName.contains(query))
yield file
def filesRegex(query: String): Array[File] =
for (file <- filesHere; if file.getName.matches(query))
yield file
}
- REPLで確認
$ scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60).
Type in expressions for evaluation. Or try :help.
scala> import java.io.File
import java.io.File
scala> object FileMatcher {
| private def filesHere = new File(".").listFiles
|
| def filesEnding(query: String): Array[File] =
| for (file <- filesHere; if file.getName.endsWith(query))
| yield file
|
| def filesContaining(query: String): Array[File] =
| for (file <- filesHere; if file.getName.contains(query))
| yield file
|
| def filesRegex(query: String): Array[File] =
| for (file <- filesHere; if file.getName.matches(query))
| yield file
| }
defined object FileMatcher
scala> FileMatcher.filesEnding("error")
res0: Array[java.io.File] = Array(./prd-db-20170101.error, ./prd-db-20170102.error, ./prd-db-20170103.error, ./prd-db-20170201.error, ./prd-db-20170202.error, ./prd-db-20170203.error, ./prd-db-20170301.error, ./prd-db-20170302.error, ./prd-db-20170303.error, ./prd-web-20170101.error, ./prd-web-20170102.error, ./prd-web-20170103.error, ./prd-web-20170201.error, ./prd-web-20170202.error, ./prd-web-20170203.error, ./prd-web-20170301.error, ./prd-web-20170302.error, ./prd-web-20170303.error, ./stg-db-20170101.error, ./stg-db-20170102.error, ./stg-db-20170103.error, ./stg-db-20170201.error, ./stg-db-20170202.error, ./stg-db-20170203.error, ./stg-db-20170301.error, ./stg-db-20170302.error, ./stg-db-20170303.error, ./stg-web-20170101.error, ./stg-web-20170102.error, ./stg-web-20170103.error, ....
scala> FileMatcher.filesContaining("web")
res1: Array[java.io.File] = Array(./prd-web-20170101.error, ./prd-web-20170101.log, ./prd-web-20170102.error, ./prd-web-20170102.log, ./prd-web-20170103.error, ./prd-web-20170103.log, ./prd-web-20170201.error, ./prd-web-20170201.log, ./prd-web-20170202.error, ./prd-web-20170202.log, ./prd-web-20170203.error, ./prd-web-20170203.log, ./prd-web-20170301.error, ./prd-web-20170301.log, ./prd-web-20170302.error, ./prd-web-20170302.log, ./prd-web-20170303.error, ./prd-web-20170303.log, ./stg-web-20170101.error, ./stg-web-20170101.log, ./stg-web-20170102.error, ./stg-web-20170102.log, ./stg-web-20170103.error, ./stg-web-20170103.log, ./stg-web-20170201.error, ./stg-web-20170201.log, ./stg-web-20170202.error, ./stg-web-20170202.log, ./stg-web-20170203.error, ./stg-web-20170203.log, ./stg-web-201...
scala> FileMatcher.filesRegex("""prd.+201703\d{2}.log""")
res2: Array[java.io.File] = Array(./prd-db-20170301.log, ./prd-db-20170302.log, ./prd-db-20170303.log, ./prd-web-20170301.log, ./prd-web-20170302.log, ./prd-web-20170303.log)
Step2: 共通ヘルパー関数をつくりたい!
と、ここまで書いてみると、どうも重複していることに気づく(はず)。こんな感じの共通したヘルパー関数をつくりたいな。
// 動かないよ!
def filesMatching(query: String, method) =
for (file <- filesHere; if file.getName.method(query))
yield file
um....値としてメソッド名が渡せないなら、メソッドを呼び出してくれる関数値を渡せばいい!
【Exercise2-1】 (String, String) => Boolean
型の関数を引数にとるような共通ヘルパー関数をつくりたい。シグネチャは以下のとおり。
def filesMatching(query: String, matcher: (String, String) => Boolean)
- Answer2-1
def filesMatching(query: String, matcher: (String, String) => Boolean) =
for (file <- filesHere; if matcher(file.getName, query))
yield file
引数に渡した matcher
は、 String型の引数を2つとって、Boolean型を返す関数
を表している。
【Exercise2-2】 Exercise2-1で作った filesMatching
を使って、Exercise1-1でつくった filesEnding
を書き直したい。
- Answer2-2
def filesEnding(query: String): Array[File] =
filesMatching(query, (fileName: String, query: String) => fileName.endsWith(query): Boolean)
(fileName: String, query: String) => fileName.endsWith(query): Boolean
の部分は、
fileNameと、queryの2つを引数にとって、fileName.endsWith(query)の結果を返す関数
ということになる。
ただ、冗長な感じ。そもそも query
って、 filesMatching
の中でmatcher関数に渡されてるだけじゃん。。
【Exercise2-3】 String => Boolean
型のメソッドを引数にとるように共通ヘルパー関数を書き直して、filesEnding
を書き直したい。シグネチャは以下のとおり。
def filesMatching(query: String, matcher: String => Boolean)
- Answer2-3
def filesMatching(query: String, matcher: String => Boolean) =
for (file <- filesHere; if matcher(file.getName))
yield file
def filesEnding(query: String): Array[File] =
filesMatching(query, fileName: String => fileName.endsWith(query): Boolean)
filesEnding
は、プレースホルダー構文を使って、型推論をつかえばさらに簡略化できる。
def filesEnding(query: String) =
filesMatching(query, _.endsWith(query))
ここまでのを全部使ったコードは以下!
おつかれさまでした。
import java.io.File
object FileMatcher {
private def filesHere = new File(".").listFiles
private def filesMatching(query: String, matcher: String => Boolean) =
for (file <- filesHere; if matcher(file.getName))
yield file
def filesEnding(query: String) =
filesMatching(query, _.endsWith(query))
def filesContaining(query: String) =
filesMatching(query, _.contains(query))
def filesRegex(query: String) =
filesMatching(query, _.matches(query))
}