Scala: Flatten the parseresult (~) from combinators parser into List?

I wrote some parser from combinatory library. I want a generic function that transform any size of nest ~ into a list. How to do this ?

Here is my example of parser I use (my real parser has a very long chain ~ so I want to avoid my current solution which is in comment below).

object CombinatorParser extends RegexParsers {

  lazy val a = "a"
  lazy val b = "b"
  lazy val c = "c"
  lazy val content = a ~ b ~ c // ^^ {case a~b => a::b::c::Nil work but I want something more general that work for any ~ length.
}

object CombinatorTesting {

  def main(args:Array[String]) {
    val testChar = "abc"
    val output = CombinatorParser.parseAll(CombinatorParser.content, testChar)
    println(output) // ((a~b)~c) but I want List(a,b,c)
  }
}

Answers


This is a good (and fairly simple) application for the kind of generic programming techniques exemplified in shapeless.

Given your definition,

object CombinatorParser extends RegexParsers {
  lazy val a = "a"
  lazy val b = "b"
  lazy val c = "c"
  lazy val content = a ~ b ~ c
}

We can recursively define a type class that will flatten it's results as follows,

import CombinatorParser._

First we define a trait which (abstractly) flattens an arbitrary match M to a List[String],

trait Flatten[M] extends (M => List[String]) {
  def apply(m : M) : List[String]
}

Then we provide type class instances for all the shapes of M that we're interested in: in this case, String, A ~ B and ParseResult[T] (where A, B and T are all types for which there are Flatten instances),

// Flatten instance for String
implicit def flattenString = new Flatten[String] {
  def apply(m : String) = List(m) 
}

// Flatten instance for `A ~ B`. Requires Flatten instances for `A` and `B`. 
implicit def flattenPattern[A, B]
  (implicit flattenA : Flatten[A], flattenB : Flatten[B]) =
    new Flatten[A ~ B] {
      def apply(m : A ~ B) = m match {
        case a ~ b => flattenA(a) ::: flattenB(b)
      } 
}

// Flatten instance for ParseResult[T]. Requires a Flatten instance for T.
implicit def flattenParseResult[T]
  (implicit flattenT : Flatten[T]) = new Flatten[ParseResult[T]] {
    def apply(p : ParseResult[T]) = (p map flattenT) getOrElse Nil 
}

Finally we can define a convenience function to simplify applying Flatten instances to parse results,

def flatten[P](p : P)(implicit flatten : Flatten[P]) = flatten(p)

And now we're ready to go,

val testChar = "abc"
val output = parseAll(content, testChar)
println(output)          // ((a~b)~c) but I want List(a, b, c)

val flattenedOutput = flatten(output)
println(flattenedOutput) // List(a, b, c)

If you prefer a solution without generic programming...

  def flatten(res: Any): List[String] = res match {
    case x ~ y => flatten(x) ::: flatten(y)
    case None => Nil
    case Some(x) => flatten(x)
    case x:String => List(x)
  }

  val testChar = "abc"
  val output = CombinatorParser.parseAll(CombinatorParser.content, testChar).getOrElse(None)
  println(flatten(output))

Need Your Help

React-Native Invariant Violation: Element type is invalid

javascript android ios reactjs react-native

When I going to run my react native app on my iPhone Expo this error displayed in red background area.

Go vs. return button in iOS keyboard for HTML input forms

ios forms input uiwebview keyboard

Managing the iOS keyboard for HTML <input> forms (used in UIWebView) is well known, i.e. <input type="tel"></input> for telephone numbers.