Skip to content

Instantly share code, notes, and snippets.

@yannick-cw
Last active January 30, 2021 12:36
Show Gist options
  • Save yannick-cw/df673012c7bea0526e5472a2b9342ffc to your computer and use it in GitHub Desktop.
Save yannick-cw/df673012c7bea0526e5472a2b9342ffc to your computer and use it in GitHub Desktop.
import cats.{Applicative, Eval, Functor, Traverse}
import cats.implicits.{toFunctorOps, toTraverseOps}
import com.sksamuel.elastic4s.requests.searches.queries.term.TermQuery
import com.sksamuel.elastic4s.requests.searches.queries.{BoolQuery, Query}
import io.circe.Decoder.Result
import io.circe.{Decoder, Json}
import orderindexer.api.graphql.QueryLang.{Root, fromDslToEsAlgebra, fromJsonCoalgebra}
object ReCurse {
type Algebra[F[_], A] = F[A] => A
type Coalgebra[F[_], A] = A => F[A]
def cata[F[_]: Functor, S, B](algebra: Algebra[F, B])(unpack: Coalgebra[F, S]): S => B = {
new (S => B) { self =>
def recurse: F[S] => F[B] = _.map(self)
def apply(init: S): B = algebra(recurse(unpack(init)))
}
}
def ana[F[_]: Functor, S, B](coalgebra: Coalgebra[F, B])(pack: Algebra[F, S]): B => S = {
new (B => S) { self =>
def recurse: F[B] => F[S] = _.fmap(self)
def apply(init: B): S = pack(recurse(coalgebra(init)))
}
}
// first unfold something, then fold it to a result
def hylo[F[_]: Functor, A](algebra: Algebra[F, A], coalgebra: Coalgebra[F, A]): A => A =
new (A => A) { self =>
def apply(init: A): A = {
(algebra compose coalgebra)(init)
}
}
}
object A extends App {
import ReCurse._
type ListF[A, B] = Option[(A, B)]
implicit def fun[A]: Functor[ListF[A, *]] = Functor[Option].compose[(A, *)]
val packCompute: Algebra[ListF[Int, *], Int] = {
case Some((e, b)) => e + b
case None => 0
}
def unpackList[A]: Coalgebra[ListF[A, *], List[A]] = {
case Nil => None
case h :: t => Some((h, t))
}
def packList[A]: Algebra[ListF[A, *], List[A]] = {
case Some((e, b)) => e :: b
case None => Nil
}
val unpackCompute: Coalgebra[ListF[Int, *], Int] = i => if (i < 10) Some((i, i + 1)) else None
val fold: Algebra[List, Int] = cata(packCompute)(unpackList)
val unfold: Coalgebra[List, Int] = ana(unpackCompute)(packList)
val unfoldAndFold = hylo(fold, unfold)
println(fold(List(1, 2, 3)))
println(unfold(0))
println(unfoldAndFold(2))
}
object QueryLang {
import higherkindness.droste._
import io.circe.generic.semiauto._
sealed trait QueryDSL[A]
case class Match(target: String, term: String)
object Match { implicit val dec: Decoder[Match] = deriveDecoder }
case class MatchQuery[A](_match: Match) extends QueryDSL[A]
object MatchQuery { def dec[A]: Decoder[QueryDSL[A]] = deriveDecoder[MatchQuery[A]].map(identity) }
case class OrQuery[A](queries: List[A]) extends QueryDSL[A]
object OrQuery {
def dec[A: Decoder]: Decoder[QueryDSL[A]] =
Decoder.decodeHCursor.emap(c =>
c.downField("or")
.focus
.toRight("Did not find down field `or`")
.flatMap(_.as[List[A]].map(OrQuery(_)).left.map(_.message))
)
}
case class AndQuery[A](queries: List[A]) extends QueryDSL[A]
object AndQuery {
def dec[A: Decoder]: Decoder[QueryDSL[A]] =
Decoder.decodeHCursor.emap(c =>
c.downField("and")
.focus
.toRight("Did not find down field `and`")
.flatMap(_.as[List[A]].map(AndQuery(_)).left.map(_.message))
)
}
case class Root(query: Json)
object Root { implicit def dec: Decoder[Root] = deriveDecoder }
def fromJsonCoalgebra(): CoalgebraM[Result, QueryDSL, Json] = CoalgebraM { json =>
AndQuery.dec[Json].or(OrQuery.dec[Json]).or(MatchQuery.dec[Json]).decodeJson(json)
}
def fromDslToEsAlgebra(): AlgebraM[Result, QueryDSL, Query] = AlgebraM {
case OrQuery(queries) => Right(BoolQuery(should = queries))
case AndQuery(queries) => Right(BoolQuery(must = queries))
case MatchQuery(Match(target, term)) => Right(TermQuery(target, term))
}
implicit def traverse: Traverse[QueryDSL] = new Traverse[QueryDSL] {
override def traverse[G[_], A, B](fa: QueryDSL[A])(f: A => G[B])(
implicit ap: Applicative[G]
): G[QueryDSL[B]] =
fa match {
case OrQuery(q) => q.traverse(f).map(OrQuery(_))
case AndQuery(q) => q.traverse(f).map(AndQuery(_))
case m: MatchQuery[A] => ap.pure(m.asInstanceOf[QueryDSL[B]])
}
override def foldLeft[A, B](fa: QueryDSL[A], b: B)(f: (B, A) => B): B =
fa match {
case OrQuery(q) => q.foldLeft(b)(f)
case AndQuery(q) => q.foldLeft(b)(f)
case _ => b
}
override def foldRight[A, B](fa: QueryDSL[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
foldLeft(fa, lb)((b, a) => f(a, b))
}
new Functor[QueryDSL] {
override def map[A, B](fa: QueryDSL[A])(f: A => B): QueryDSL[B] = fa match {
case OrQuery(q) => OrQuery(q.map(f))
case AndQuery(q) => AndQuery(q.map(f))
case m: MatchQuery[A] => m.asInstanceOf[QueryDSL[B]]
}
}
}
object RunQuery extends App {
import io.circe.parser.parse
val queryJson1 = parse("""
|{
| "query": {
| "and": [
| {
| "_match": {
| "target": "name",
| "term": "Frank"
| }
| },
| {
| "_match": {
| "target": "age",
| "term": "22"
| }
| }
| ]
| }
|}
|""".stripMargin).getOrElse(???)
val queryJson2 = parse("""
|{
| "query": {
| "or": [
| {
| "_match": {
| "target": "name",
| "term": "Frank"
| }
| },
| {
| "_match": {
| "target": "age",
| "term": "22"
| }
| }
| ]
| }
|}
|""".stripMargin).getOrElse(???)
val queryJson3 = parse("""
|{
| "query": {
| "_match": {
| "target": "name",
| "term": "Frank"
| }
| }
|}
|""".stripMargin).getOrElse(???)
import higherkindness.droste.scheme
for {
root <- queryJson1.as[Root]
root2 <- queryJson2.as[Root]
root3 <- queryJson3.as[Root]
res <- scheme.hyloM(fromDslToEsAlgebra(), fromJsonCoalgebra()).apply(root.query)
_ = println(res)
res2 <- scheme.hyloM(fromDslToEsAlgebra(), fromJsonCoalgebra()).apply(root2.query)
_ = println(res2)
res3 <- scheme.hyloM(fromDslToEsAlgebra(), fromJsonCoalgebra()).apply(root3.query)
_ = println(res3)
} yield ()
}
// https://github.com/softwaremill/recursion-training
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment