Effectful Regex
Otra posible solución al problema del post anterior consiste en recorrer varias veces la misma línea del documento Markdown, sustituyendo un link cada vez, hasta encontrarlos todos.
La ventaja principal de esta solución es que se puede implementar totalmente dentro del stream, sin salirnos del contexto de la IO:
final class MarkdownTransformationUseCase:
// -8<-- código omitido para abreviar
private[this] def transform(line: Line) =
Stream
.unfoldChunkEval[IO, Option[String], Line](Some(line.value)) {
case Some(text) =>
(text match
case linkPattern(text, url) => Some(Link(LinkText(text), LinkUrl(url)))
case _ => Option.empty
).fold(IO.delay(Some((Chunk(Line(text)), Option.empty)))) { link =>
for
reference <- footnotesRepository.save(link)
transformedText = linkPattern.replaceFirstIn(
text,
s"${link.text} [^${reference.value}]",
)
yield Some((Chunk(Line(transformedText)), Some(transformedText)))
}
case None => IO.none
}
.lastOr(Line.empty)
Aunque el coste computacional de esta solución es mayor que el de la anterior, en el mejor de los casos, la solución sigue teniendo una complejidad lineal.
Expresiones Regulares#
Aunque en toda la kata usé solamente una expresión regular, hice bastante para conseguir que fuera lo más expresiva posible:
object MarkdownTransformationUseCase:
private val linkPattern =
raw"(?<!!)\[(?<text>[^\^].+?)]\((?<url>.+?)\)".r.unanchored
El negative look around al principio de la expresión asegura que no haga match con una imagen. En Markdown los links y las imágenes comparten un formato similar. Por ejemplo:

[And this is a link](http://example.org)
Otro detalle de la expresión regular es que utiliza named capturing groups para extraer los grupos. En Scala es muy cómodo reutilizar los mismos nombres dentro del pattern-matching:
case linkPattern(text, url) => (LinkText(text), LinkUrl(url))
Finalmente#
Lo que parecía un problema simple terminó dando mucho de sí y me mantuvo pensando durante días en nuevas formas de mejorar mi solución. Lo he disfrutado mucho y agradezco a Lean Mind que hayan compartido la kata.