ScalaとRubyで関数合成してみる

N予備校Scala応用編を受講したのでRubyと比較して色々やってみたくなった。

まえおき

ScalaRubyと似てる気がする

Rubyは関数(というか処理をまとめたProcかMethodのインスタンス)も他のオブジェクトと同等だし、Scalaも同様で関数の返り値や引数を関数にしたりできる。

関数合成してみる

例えば文字列 "true"と”false"をパースする関数を書くためにtruePaser、falsePaserそれぞれを定義して関数合成したいとする。 Rubyなら

 class ParseResult
  attr_accessor :value
end

class Success < ParseResult
  def initialize(value)
    @value = value
  end
end

class Failure < ParseResult
  def initialize; end
end


def true_parser(input)
  if input == "true"
    Success.new(true)
  else
    Failure.new
  end
end

def false_parser(input)
  if input == "false"
    Success.new(false)
  else
    Failure.new
  end
end

def select(left, right)
  Proc.new do |input|
    if (success = left.call(input)).class == Success
      success
    else
      right.call(input)
    end
  end
end

true_parser_method = method(:true_parser)
false_parser_method = method(:false_parser)

puts select(true_parser_method, false_parser_method).call('true').value         # true
puts select(true_parser_method, false_parser_method).call('false').value          # false
puts select(true_parser_method, false_parser_method).call('hogehoge').value # (何も出力されない)
 

合成は問題なくできるが結局selectが何をするProcを返すのかわかりにくい。

ここで型があるScalaだと

 object Main extends App {
  sealed trait ParseResult[+T] {
    def value :Option[T]
  }

  case class Success[+T](in_value: T) extends ParseResult[T] {
    def value: Option[T] = Option(in_value)
  }

  case object Failure extends ParseResult[Nothing] {
    def value: Option[Nothing] = None
  }

  type Parser[+T] = String => ParseResult[T]

  def trueParser: Parser[Boolean] = input =>
    if (input == "true") {
      Success(true)
    } else {
      Failure
    }

  def falseParser: Parser[Boolean] = input =>
    if (input == "false") {
      Success(false)
    } else {
      Failure
    }

  def select[T, U >: T](left: => Parser[T], right: => Parser[U]): Parser[U] = input => {
    left(input) match {
      case success@Success(_) => success
      case Failure => right(input)
    }
  }

  println(select(trueParser, falseParser)("true").value) // Success(true)
  println(select(trueParser, falseParser)("false").value) // Success(false)
  println(select(trueParser, falseParser)("hogehoge").value) // None
}
 

型があると、関数合成をする関数を定義する時点で引数に取りたい関数のパラメーターを表現でき、合成関数の返り値に何を引数として渡したら良いかわかりやすい。 後Rust同様Option型が便利。
対して、Rubyは型がないことで明らかに記述量へるし多分僕の書いた以上に研ぎ澄ますこともできると思う。あとobject Main extends Appみたいなのがいらない。

まとめ

関数型言語として関数の集まりを書いていくなら型があったほうが扱いやすいのでは?という気持ち。Lispは型ないけど。 入出力に対してルールがある程度形式張って表現されていたほうが関数を組み合わせやすい気がする。