Поток `Попробуйте через-для понимания
Вызванный другим вопросом (который впоследствии был отредактирован), я хотел попробовать, насколько легко было бы связывать вызовы в 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`?
}