メソッド屋のブログ

米マイクロソフト DevOps エバンジェリスト 牛尾の日記です。ソフトウェア開発の上手なやり方を追求するのがライフワーク。本ブログは、個人の意見であり、所属会社とは関係がありません。

Scalaプログラムのリファクタリング

Scalaで、「ふつうのHaskell」に出てくるプログラムをScalaで作ってみたりしているけど、
こんな感じのプログラムをつくってみた

scala> expandCharClass("abc[123].mpg")
res37: List[String] = List(abc1.mpg, abc2.mpg, abc3.mpg)
 

つまり、[]でかこっている部分を展開して文字列のリストを作るというイメージのコード。
バリバリ「オブジェクト脳」もしくは「手続き脳」の私が最初に書いた激ショボのコードは次の通り。
しかも、[が片方だけだと、例外が発生してしまうしょぼさ。さて、このしょぼいコードを
リファクタリングしてみる。

def expandCharClass(x:String):List[String] = x.indexOf('[') match {
   case -1 => List(x)
   case _  => expandChar(x)      
   }
}
def expandChar(x:String):List[String] =  {
   val start = x.indexOf('[')
   val end   = x.indexOf(']')
   val partString  = x.slice(start + 1 , end)
   val startString = x.slice(0, start)
   val endString = x.slice(end+1, x.length)
     partString.toList.map(y => startString + y + endString) 
}

特にexpandCharClassのパターンマッチも中途半端だし、
expandCharの関数でも、ダサい代入がある。このあたりを、Scalaの強力な正規表現とextractorを
使って書き直してみると下記の感じ

   val pattern = """(.*)\[(.*)\](.*)""".r

def expandCharClass(x:String):List[String] = pattern.unapplySeq(x) match {
     case Some(y) =>  expandChar(x)
     case None    =>  List()
}
def expandChar(x:String):List[String] = {
   val pattern(start, ext, end) = x
   ext.toList.map(y => start + y + end)
}

おお、かなりすっきり。特にexpandChar関数の中で、start, ext, end変数の束縛が
1行で済んでいるのが結構おしゃれですなぁ。

私も勉強中なので、もっとおしゃれにかける方法を知ってる人はコメント下さいね。

ちなみに、Scalaは、静的言語でかつ、遅延評価がデフォルトではない関数型言語
と、オブジェクト指向が融合したマルチパラダイムな言語です。こういう言語での
リファクタリングのパターンなんかができると、みんなもっとScalaらしいコードが
かきやすくなるかもですね。


20009年10月16日追記


Ha2さんがさらにおしゃれなコードにリファクタリングしてくれました。
ローカル関数を使うと、さらにイイ感じです。

def expandCharClass(x:String):List[String] = {

 val pattern = """(.*)\[(.*)\](.*)""".r

 def expandChar(x:String):List[String] = {
   val pattern(start, ext, end) = x
   ext.toList.map(y => start + y + end)
 }

 pattern.unapplySeq(x) match {
    case Some(y) =>  expandChar(x)
    case None    =>  List()
 }

}