Skip to content

Commit ef16fc7

Browse files
committed
make package ready
1 parent 4dddaab commit ef16fc7

File tree

3 files changed

+91
-11
lines changed

3 files changed

+91
-11
lines changed

README.md

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,80 @@
1-
# hxl
1+
# Hxl
2+
Hxl is a pure applicative batching library for Scala.
3+
4+
Hxl is based on the ideas presented in [Haxl](https://simonmar.github.io/bib/papers/haxl-icfp14.pdf), but diverges in in a few ways.
5+
Notably, hxl does not use side effects, but is instead based on free applicatives.
6+
7+
Hxl is very small (only a couple hundred lines of code) and only depends on cats.
8+
9+
Hxl is written in tagless final, which allows for molding the library to your needs.
10+
11+
## Installation
12+
Hxl is available on Maven Central for Scala 2.13 and 3.2.
13+
```scala
14+
libraryDependencies += "com.github.casehubdk" %% "hxl" % "0.1.0"
15+
```
16+
17+
## Usage
18+
There are two primitive structures in hxl: `DataSource[F, K, V]` and `DSKey[K, V]`.
19+
A `DataSource[F, K, V]` abstracts over a function `NonEmptyList[K] => F[Map[K, V]]`.
20+
A `DataSource` is uniquely (as in Scala universal equals) identified by its `DSKey`.
21+
```scala
22+
import cats._
23+
import cats.implicits._
24+
25+
final case class MyDSKey(id: String)
26+
case object MyDSKey extends DSKey[MyDSKey, String]
27+
28+
val database = Map(
29+
"foo" -> "bar",
30+
"baz" -> "qux"
31+
)
32+
33+
def dataSource[F[_]: Applicative] = DataSource.from[F, MyDSKey, String](MyDSKey) { keys =>
34+
keys.toList.flatMap(key => database.get(key.id).toList.tupleLeft(key)).toMap.pure[F]
35+
}
36+
37+
val fa: Hxl[Id, Option[String]] = Hxl(MyDSKey("foo"), dataSource[Id])
38+
val fb: Hxl[Id, Option[String]] = Hxl(MyDSKey("baz"), dataSource[Id])
39+
(fa, fb).mapN(_.mkString + " " + _.mkString) // "bar qux"
40+
```
41+
42+
Hxl is an applicative, but sometimes you need a monad.
43+
Hxl is like `Validated` from `cats`, in that it can escape it's applicative nature via a method `andThen`.
44+
However, if you need Hxl to become a monad (like `Either` to `Validated`), you can use request a monadic view of your effect:
45+
```scala
46+
val fa: Hxl[F, String] = ???
47+
48+
val m: HxlM[F, String] = fa.monadic.flatMap{ x =>
49+
???
50+
}
51+
52+
val back: Hxl[F, String] = m.hxl
53+
```
54+
55+
## Advanced usage
56+
Since Hxl is written in tagless final, you can add various behaviors to your data sources.
57+
For instance, you can add (pure) caching.
58+
```scala
59+
import cats.data._
60+
import cats.implicits._
61+
62+
final case class MyDSKey(id: String)
63+
case object MyDSKey extends DSKey[MyDSKey, String]
64+
65+
val database = Map(
66+
"foo" -> "bar",
67+
"baz" -> "qux"
68+
)
69+
70+
type Cache = Map[String, String]
71+
type Effect[A] = State[Cache, A]
72+
73+
def dataSource: DataSource[Effect, MyDSKey, String] = DataSource.from(MyDSKey) { keys =>
74+
State[Cache, Map[MyDSKey, String]] { cache =>
75+
val (misses, hits) = keys.toList.partitionEither(k => cache.get(k.id).tupleLeft(k).toRight(k))
76+
val fetched = misses.flatMap(key => database.get(key.id).toList.map(key -> _)).toMap
77+
(cache ++ fetched.map{ case (k, v) => k.id -> v }, hits.toMap ++ fetched)
78+
}
79+
}
80+
```

hxl/src/main/scala/hxl/Hxl.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package hxl
1818

1919
import cats._
2020
import cats.implicits._
21-
import cats.arrow.FunctionK
2221

2322
/*
2423
* Hxl is a value that that represents a computation that may be batched.
@@ -71,7 +70,7 @@ object Hxl {
7170
}
7271

7372
def runSequential[F[_]: Monad, A](node: Hxl[F, A]): F[A] = {
74-
implicit val ev = Parallel.identity[F]
73+
implicit val ev: Parallel[F] = Parallel.identity[F]
7574
runPar[F, A](node)
7675
}
7776

@@ -93,10 +92,12 @@ object Hxl {
9392
new Parallel[G] {
9493
type F[A] = Hxl[P.F, A]
9594

96-
override def sequential: F ~> G =
97-
FunctionK.liftFunction(fa => fa.mapK(P.sequential)(P.monad))
98-
override def parallel: G ~> F =
99-
FunctionK.liftFunction(fa => fa.mapK(P.parallel)(P.applicative))
95+
override def sequential: F ~> G = new (F ~> G) {
96+
def apply[A](fa: F[A]): G[A] = fa.mapK(P.sequential)(P.monad)
97+
}
98+
override def parallel: G ~> F = new (G ~> F) {
99+
def apply[A](fa: G[A]): F[A] = fa.mapK(P.parallel)(P.applicative)
100+
}
100101

101102
override def applicative: Applicative[F] = applicativeForHxl[P.F](P.applicative)
102103

hxl/src/main/scala/hxl/Requests.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ object Requests {
6666

6767
def read[F[_], A](req: Requests[F, A], state: DSMap): Eval[A] = Eval.defer {
6868
req match {
69-
case Pure(a) => Eval.now(a)
70-
case ap: Ap[F, a, b] => (read(ap.left, state), read(ap.right, state)).mapN(_(_))
71-
case l: Lift[F, k, a] => Eval.now(state.get(l.source.key, l.key))
69+
case Pure(a) => Eval.now(a)
70+
case ap: Ap[F, a, b] => (read(ap.left, state), read(ap.right, state)).mapN(_(_))
71+
case l: Lift[F, k, a] @unchecked => Eval.now(state.get(l.source.key, l.key))
7272
}
7373
}
7474

@@ -84,8 +84,8 @@ object Requests {
8484
val m2: F[List[(DSKey[_, _], KeyedData[_, _])]] = m.toList.parTraverse { case (ds: Key[k, v], keys) =>
8585
type K = k
8686
val nest: F[Map[ds.ds.K2, v]] = keys.toList.toNel match {
87-
case None => F.pure(Map.empty[ds.ds.K2, v])
8887
case Some(k2: NonEmptyList[K] @unchecked) => ds.ds.batch(k2)
88+
case _ => F.pure(Map.empty[ds.ds.K2, v])
8989
}
9090

9191
nest.map { m =>

0 commit comments

Comments
 (0)