LoginSignup
0
0

More than 5 years have passed since last update.

高階関数にゅうもん1(Scalaで)

Posted at

高階関数にゅうもん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))
}  
0
0
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
0
0