Поток `Попробуйте через-для понимания

Вызванный другим вопросом (который впоследствии был отредактирован), я хотел попробовать, насколько легко было бы связывать вызовы в Scala 2.10. Try построить (см. эту презентацию), используя для понимания.

Идея состоит в том, чтобы получить список токенов и сопоставить их с последовательностью шаблонов, а затем вернуть первую ошибку или успешно сопоставленный шаблон. Я пришел к следующей довольно неловкой версии, и мне интересно, можно ли сделать это проще и приятнее:

import util.Try

trait Token
case class Ident  (s: String) extends Token
case class Keyword(s: String) extends Token
case class Punct  (s: String) extends Token
case object NoToken extends Token
case class FunctionDef(id: Ident)

case class Expect[A](expectation: String)(pattern: PartialFunction[Token, A]) {
  def unapply(tup: (Try[_], Token)) = Some(tup._1.map { _ => 
     pattern.lift(tup._2).getOrElse(throw new Exception(expectation))
  })
}

Теперь построить ожидания для Keyword("void") :: Ident(id) :: Punct("(") :: Punct(")") :: tail

val hasVoid   = Expect("function def starts with void") { case Keyword("void") => }
val hasIdent  = Expect("expected name of the function") { case id: Ident       => id }
val hasOpen   = Expect("expected opening parenthesis" ) { case Punct("(")      => }
val hasClosed = Expect("expected closing parenthesis" ) { case Punct(")")      => }

Построить полный контрольный пример:

def test(tokens: List[Token]) = {
  val iter = tokens.iterator
  def next(p: Try[_]) = Some(p -> (if (iter.hasNext) iter.next else NoToken))
  def first() = next(Try())

  val sq = for {
    hasVoid  (vd) <- first()
    hasIdent (id) <- next(vd)
    hasOpen  (op) <- next(id)
    hasClosed(cl) <- next(op)
  } yield cl.flatMap(_ => id).map(FunctionDef(_))

  sq.head
}

Следующее проверяет метод испытания:

// the following fail with successive errors
test(Nil)
test(Keyword("hallo") :: Nil)
test(Keyword("void" ) :: Nil)
test(Keyword("void" ) :: Ident("name") :: Nil)
test(Keyword("void" ) :: Ident("name") :: Punct("(") :: Nil)
// this completes
test(Keyword("void" ) :: Ident("name") :: Punct("(") :: Punct(")") :: Nil)

Теперь особенно дополнительный flatMap а также map в yield кажется ужасным, а также необходимость звонить head на результат для понимания.

Есть идеи? Является Try очень плохо подходит для понимания? Не должен ни Either или же Try быть "фиксированным", чтобы учесть этот тип многопоточности (например, разрешить Try как прямой результат типа unapply)?

1 ответ

Решение

Хитрость заключается в том, чтобы не создавать Try экземпляры во внутренней структуре, но вместо этого позвольте этому генерировать исключения и создать один внешний Try,

Во-первых, давайте избавимся от Try[Unit]"S:

case class Expect(expectation: String)(pattern: PartialFunction[Token, Unit]) {
  def unapply(token: Token) = 
    pattern.isDefinedAt(token) || (throw new Exception(expectation))
}

case class Extract[A](expectation: String)(pattern: PartialFunction[Token, A]) {
  def unapply(token: Token) = Some(
    pattern.lift(token).getOrElse(throw new Exception(expectation))
  )
}

Тогда чеки становятся:

val hasVoid   = Expect ("function def starts with void") { case Keyword("void") => }
val getIdent  = Extract("expected name of the function") { case id: Ident       => id }
val hasOpen   = Expect ("expected opening parenthesis" ) { case Punct("(")      => }
val hasClosed = Expect ("expected closing parenthesis" ) { case Punct(")")      => }

И метод испытания:

def test(tokens: List[Token]) = Try {
  val iter = tokens.iterator
  def next() = Some(if (iter.hasNext) iter.next else NoToken)

  (for {
    hasVoid()    <- next()
    getIdent(id) <- next()
    hasOpen()    <- next()
    hasClosed()  <- next()
  } yield FunctionDef(id)).head  // can we get rid of the `head`?
}
Другие вопросы по тегам