ScalaとRubyで関数合成してみる
N予備校でScala応用編を受講したのでRubyと比較して色々やってみたくなった。
まえおき
ScalaはRubyと似てる気がする
これは思想が似てる(オブジェクト思考と関数型の融合)だからなのかもしれないけどscalaとrubyって似てる気がするんだよな
— とるめん (@qwyngg) March 30, 2020
DSLかけたりとかシンプルな構文だったりとか
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は型ないけど。 入出力に対してルールがある程度形式張って表現されていたほうが関数を組み合わせやすい気がする。