Skip to content

Instantly share code, notes, and snippets.

@afsalthaj
Last active May 4, 2021 19:00
Show Gist options
  • Save afsalthaj/809650b4c6e895084110269a3ecb8702 to your computer and use it in GitHub Desktop.
Save afsalthaj/809650b4c6e895084110269a3ecb8702 to your computer and use it in GitHub Desktop.
package com..s3paths
import cats.free.Cofree
import com.s3paths.nonfree.Component.Version
import com.s3paths.nonfree.Component.RunTime
import com.s3paths.nonfree.Component.Adhoc
import cats.Traverse
import cats.Eval
// Well we need name and pos
import cats.Id
import cats.~>
import cats.implicits._
// Thats the abstraction that was hidden
import atto._, Atto._
import cats.Applicative
object nonfree {
sealed trait Component
object Component {
def keyValueParser[A](n: String, v: Parser[A]): Parser[(String, A)] =
string(n) <~ string("=") flatMap (n => v.map(v => (n, v)))
case class Version(n: String, v: Int) extends Component
object Version {
def parser(name: String): Parser[Version] =
keyValueParser(name, int).map({ case (a, b) => Version(a, b) })
}
case class RunTime(n: String, v: Long) extends Component
object RunTime {
def parser(name: String): Parser[RunTime] =
keyValueParser(name, long).map({ case (a, b) => RunTime(a, b) })
}
case class Adhoc(n: String, v: String) extends Component
object Adhoc {
val anyCharExceptSlash: Parser[String] =
many(satisfy(t => t != '/' && !(t.isSpaceChar))).map(_.mkString)
def parser(name: String): Parser[Adhoc] =
keyValueParser(name, anyCharExceptSlash).map({ case (a, b) => Adhoc(a, b) })
}
}
// oversimplified, coz a path can itself a metadata, and tomorrow it may not be s3. But forget about all that
case class Path(current: Component, next: Option[Path])
val path = Path(Component.Version("minor_version", 10), Some(Path(Component.RunTime("run_time", 11), None)))
}
object free extends App {
case class PathA[A](current: nonfree.Component, next: Option[A])
object PathA {
implicit val traversePathA: Traverse[PathA] = new Traverse[PathA] {
override def foldLeft[A, B](fa: PathA[A], b: B)(f: (B, A) => B): B =
fa.next match {
case Some(value) => f(b, value)
case None => b
}
override def foldRight[A, B](fa: PathA[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
fa.next match {
case Some(value) => f(value, lb)
case None => lb
}
override def traverse[G[_]: Applicative, A, B](fa: PathA[A])(f: A => G[B]): G[PathA[B]] = {
val ogb = fa.next.map(f)
ogb match {
case None => Applicative[G].pure(PathA(fa.current, None))
case Some(value) => Applicative[G].map(value)(a => PathA(fa.current, Some(a)))
}
}
}
}
val inclusion: Eval ~> cats.Id = new (Eval ~> cats.Id) {
override def apply[A](fa: Eval[A]): Id[A] = fa.value
}
val parsers: List[Parser[nonfree.Component]] =
List(Version.parser("minor_version"), RunTime.parser("run_time"))
def parser: Parser[Cofree[PathA, Int]] =
for {
loc <- pos
current <- parsers.combineAll // Monoid of parser is `orElse`
_ <- string("/")
next <- opt(parser)
} yield Cofree(loc, cats.Eval.now(PathA(current, next)))
type Structure = List[(Int, String)]
def structure(parsed: Cofree[PathA, Int]): Structure = {
def combine(a: (Int, String), b: Option[Structure]): Structure =
Applicative[Option]
.ap2[(Int, String), Structure, Structure](Some(_ :: _))(Some(a), b)
.getOrElse(Nil)
Cofree.cataM[PathA, Id, Int, Structure](parsed)(
(a, fb) =>
fb.current match {
case Version(n, _) => combine((a, n), fb.next)
case RunTime(n, _) => combine((a, n), fb.next)
case Adhoc(n, _) => combine((a, n), fb.next)
}
)(inclusion)
}
val path1 = "minor_version=1/run_time=222222/relationship_id=3/abc.parquet"
val path2 = "run_time=222222/minor_version=1/relationship_id=4/abc.parquet"
val parsed1 =
parser
.parse(path1)
.done
.option
val parsed2 =
parser
.parse(path2)
.done
.option
val isSameStructure =
Applicative[Option].map2(
parsed1,
parsed2
)((a, b) => structure(a) == structure(b))
println(parsed1.map(structure))
println(parsed2.map(structure))
println(isSameStructure)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment