tags:

views:

201

answers:

3

How would you implement class that parses some input via regex and transforms founded string to some other type? My approach is:

class ARegex[T](regex:Regex, reform:Option[String => T]){
  def findFirst(input:String):Option[T] = {
    (regex.findFirstIn(input), reform) match{
      case (None, _) => None
      case (Some(s), None) => Some(s) // this won't compile because of type mismatch
      case (Some(s), Some(fun)) => Some(fun(s))
    }
  }
}

class BRegex[T](regex:Regex, reform:Option[String => T]) {
  def findFirst(input:String) = {  //returns Option[Any] - erasure
    (regex.findFirstIn(input), reform) match{
      case (None, _) => None
      case (Some(s), None) => Some(s)
      case (Some(s), Some(fun)) => Some(fun(s))
    }
  }
}
+2  A: 

Well, the problem is the type mismatch, because you are returning either a String or a T, which, of course, are unified at Any. You can't say you are going to return Option[T] and then return Option[String].

Other than that, a simplified version of that code is this:

class ARegex[T](regex: Regex, reform: Option[String => T]) {
  def findFirst(input: String): Option[Any] =
    regex findFirstIn input map { s => reform map (_(s)) getOrElse s }
}

You could return an Option[Either[String, T]], though. The code would look like this:

class ARegex[T](regex: Regex, reform: Option[String => T]) {
  def findFirst(input: String): Option[Either[String, T]] =
    regex findFirstIn input map { s => reform map (_(s)) toRight s }
}
Daniel
+1  A: 

Why is reform Option[String => T] instead of just String => T? If you don't pass in a mechanism for creating an instance of your desired type, there's no mechanism for the runtime system to actually create the appropriate object. If you really need to pass in an Option[String => T] then your second case should simply return None.

Also, flatMap is your friend, and will give you the correct behavior (i.e. if reform is None, the method returns None.

class RegexExtractor[T](regex: Regex, reform: Option[String => T]) {
  def findFirst(input: String): Option[T] = reform.flatMap(f => regex.findFirstIn(input).map(f))
}
Kris Nuttycombe
I don't think correct behavior is for `findFirst` to return `None` if if `reform` is `None`. He's looking for a way to omit the `reform` function and have `findFirst` skip the transformation under those circumstances.
Ken Bloom
+4  A: 

We can solve this problem by eliminating the Option part of the reform's type, and using a different mechanism to indicate that we don't want to change the match in any way. This mechanism is to use identity as a default parameter or pass identity when you don't want the type to change.

class ARegex[T](regex:Regex, reform:String => T = identity[String](_)){
  def findFirst(input:String):Option[T] = {
    regex.findFirstIn(input) match{
      case None => None
      case Some(s) => Some(reform(s))
    }
  }
}

new ARegex("something".r).findFirst("something else") //returns Option[String]
new ARegex("3".r, {x=>x.toInt}).findFirst("number 3") //returns Option[Int]
Ken Bloom
Oh, I like this! It didn't occur to me to use identity as a default parameter.
Daniel
You could rewrite the body of `findFirst` to `regex findFirstIn input map reform`, though. It would really show off Scala elegance.
Daniel
@Daniel, that probably is the most idiomatic way to write it. I didn't do it that way (actually, I did but reverted it... check the edit history) because I felt that it might obscure the important part of my answer if I deviated too much from the original.
Ken Bloom