途中で失敗したら強制的に例外を吐きたい
use std::io;
use std::fs::File;
use std::io::Read;
fn main() {
println!("Hello, world!");
println!("{:?}", foo());
}
fn foo() -> Result<String, io::Error> {
println!("-- 1");
let mut f = File::open("username.txt")?;
println!("-- 2");
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
これはRustのコードで、ファイルが存在しなければ -- 2
は出力されずに、そこで作業が終了し強制的に Err(Error { repr: Os { code: 2, message: "No such file or directory" } })
が返るコードです。
今回はこれの早期リターンの部分をScalaで書いてみたいと思います。
scalamacros によってマクロを作る
sbt-example や sbt-example-newstyle を参考にして、一つマクロを組んでみます。
package examples
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object Macros {
def guardimpl[T](c: Context)(defn: c.Expr[Option[T]]) = {
import c.universe._
c.Expr[T](
q""" if ($defn.isEmpty) { return None } else { $defn.get } """)
}
def guard[T](defn: Option[T]): T = macro guardimpl[T]
}
Rustでは元々?
が try!
マクロでした。しかし、Scalaでは try
が予約されていて使えなかったので、guard
にしました。
使い方はこんな感じです。
package foo
import examples.Macros._
object Hello extends App {
val user = User("tarou")
println(user.yourName("tarou"))
println(user.yourName("jirou"))
}
case class User(name: String) {
def yourName(name: String): Option[String] = {
val ret: String = guard[String] {
if (this.name == name) Some(this.name) else None
}
println(s"your name is ${ret}")
Some(ret)
}
}
結果は
your name is tarou
Some(tarou)
None
となります。jirou
の場合は名前が間違っているため名前を呼ばれることはありません。
guardは、成功するとOption型をアンラップして中身を返すため、値をそのまま使う事ができます。
macro内でreturnを使っているため、defn
が None だった場合は、早期リターンでNoneが返ります。macroを改造してEitherや単純にExceptionを返してあげても面白いかもしれません。