Last active
January 30, 2021 12:36
-
-
Save yannick-cw/df673012c7bea0526e5472a2b9342ffc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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