Skip to content

Commit 4cdab91

Browse files
authored
Merge pull request #5 from casehubdk/3-hxlm-constructors
3 hxlm constructors
2 parents c149d0f + 0572575 commit 4cdab91

File tree

5 files changed

+109
-52
lines changed

5 files changed

+109
-52
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
strategy:
2929
matrix:
3030
os: [ubuntu-latest]
31-
scala: [2.13.10, 3.2.2, 3.3.0]
31+
scala: [2.13.12, 3.2.2, 3.3.0]
3232
java: [temurin@8]
3333
runs-on: ${{ matrix.os }}
3434
steps:
@@ -101,7 +101,7 @@ jobs:
101101
strategy:
102102
matrix:
103103
os: [ubuntu-latest]
104-
scala: [2.13.10]
104+
scala: [2.13.12]
105105
java: [temurin@8]
106106
runs-on: ${{ matrix.os }}
107107
steps:
@@ -138,12 +138,12 @@ jobs:
138138
~/Library/Caches/Coursier/v1
139139
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
140140

141-
- name: Download target directories (2.13.10)
141+
- name: Download target directories (2.13.12)
142142
uses: actions/download-artifact@v3
143143
with:
144-
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.10
144+
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.12
145145

146-
- name: Inflate target directories (2.13.10)
146+
- name: Inflate target directories (2.13.12)
147147
run: |
148148
tar xf targets.tar
149149
rm targets.tar

build.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
val scala213Version = "2.13.10"
1+
val scala213Version = "2.13.12"
22
val scala32Version = "3.2.2"
33

44
ThisBuild / scalaVersion := scala213Version
55
ThisBuild / crossScalaVersions := Seq(scala213Version, scala32Version, "3.3.0")
66
ThisBuild / organization := "io.github.casehubdk"
77
ThisBuild / organizationName := "CaseHubDK"
88

9-
ThisBuild / tlBaseVersion := "0.1"
9+
ThisBuild / tlBaseVersion := "0.2"
1010
ThisBuild / tlSonatypeUseLegacyHost := false
1111

1212
ThisBuild / tlCiMimaBinaryIssueCheck := false

modules/core/src/main/scala/hxl/Hxl.scala

Lines changed: 100 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
package hxl
1818

1919
import cats._
20+
import cats.arrow._
2021
import cats.implicits._
2122

2223
/*
2324
* Hxl is a value that that represents a computation that may be batched.
2425
* Hxl forms an applicative, and only an applicative.
25-
* `andThen` exists as an alternative to `flatMap` (since that wouldn't be lawful), much like `Validated`.
26+
* `andThen` exists as an alternative to `flatMap`, much like `Validated`.
2627
*/
2728
sealed trait Hxl[F[_], A] {
2829
def andThen[B](f: A => Hxl[F, B])(implicit F: Functor[F]): Hxl[F, B]
@@ -88,6 +89,8 @@ object Hxl {
8889
runPar(node)
8990
}
9091

92+
def unit[F[_]]: Hxl[F, Unit] = Done(())
93+
9194
def embedF[F[_], A](fa: F[Hxl[F, A]]): Hxl[F, A] = LiftF(fa)
9295

9396
def liftF[F[_]: Functor, A](fa: F[A]): Hxl[F, A] = embedF(fa.map(Done(_)))
@@ -101,38 +104,19 @@ object Hxl {
101104
apply[F, K, V](k, source)
102105
.flatMapF(F.fromOption(_, new RuntimeException(show"Key $k not found")))
103106

104-
implicit def parallelForHxl[F[_]](implicit P: Parallel[F]): Parallel[Hxl[F, *]] = {
105-
type G[A] = Hxl[F, A]
106-
new Parallel[G] {
107-
type F[A] = Hxl[P.F, A]
108-
109-
override def sequential: F ~> G = new (F ~> G) {
110-
def apply[A](fa: F[A]): G[A] = fa.mapK(P.sequential)(P.monad)
111-
}
112-
override def parallel: G ~> F = new (G ~> F) {
113-
def apply[A](fa: G[A]): F[A] = fa.mapK(P.parallel)(P.applicative)
114-
}
115-
116-
override def applicative: Applicative[F] = applicativeForHxl[P.F](P.applicative)
117-
118-
override def monad: Monad[G] = {
119-
implicit val m = P.monad
120-
new Monad[G] {
121-
override def flatMap[A, B](fa: G[A])(f: A => G[B]): G[B] = fa.monadic.flatMap(f(_).monadic).hxl
122-
override def tailRecM[A, B](a: A)(f: A => G[Either[A, B]]): G[B] = a.tailRecM(f(_).monadic).hxl
123-
override def pure[A](x: A): G[A] = Done(x)
124-
}
125-
}
126-
}
127-
}
128-
129-
implicit def applicativeForHxl[F[_]: Applicative]: Applicative[Hxl[F, *]] = {
130-
type G[A] = Hxl[F, A]
131-
new Applicative[G] {
132-
def pure[A](x: A): G[A] = Done(x)
133-
def ap[A, B](ff: G[A => B])(fa: G[A]): G[B] =
107+
// Almost the same signature as parallel, except we don't have a monad, but a functor instead
108+
// This is because of the free monad structure of Hxl, we can defer Monad evidence until we need to run
109+
def applicativeInstance[F[_]: Functor, G[_]: Applicative](
110+
fg: F ~> G,
111+
gf: G ~> F
112+
): Applicative[Hxl[F, *]] = {
113+
implicit def self: Applicative[Hxl[F, *]] = applicativeInstance[F, G](fg, gf)
114+
type H[A] = Hxl[F, A]
115+
new Applicative[H] {
116+
def pure[A](x: A): H[A] = Done(x)
117+
def ap[A, B](ff: H[A => B])(fa: H[A]): H[B] =
134118
(ff, fa) match {
135-
case (LiftF(fa), LiftF(fb)) => LiftF((fa, fb).mapN(_ <*> _))
119+
case (LiftF(fa), LiftF(fb)) => LiftF(gf((fg(fa), fg(fb)).mapN(_ <*> _)))
136120
case (LiftF(fa), h) => LiftF(fa.map(_ <*> h))
137121
case (h, LiftF(fa)) => LiftF(fa.map(h <*> _))
138122
case (Done(f), Done(a)) => Done(f(a))
@@ -144,20 +128,46 @@ object Hxl {
144128
}
145129
}
146130
}
131+
132+
implicit def applicativeForHxl[F[_]: Applicative]: Applicative[Hxl[F, *]] =
133+
applicativeInstance[F, F](FunctionK.id[F], FunctionK.id[F])
147134
}
148135

149136
/*
150137
* A monadic view of Hxl.
151138
* The equivalent counterpart for `Hxl` as `Either` is to `Validated`.
152-
* Is effectively the identity monad transformer.
153139
*/
154140
final case class HxlM[F[_], A](hxl: Hxl[F, A]) {
155141
def mapK[G[_]: Functor](fk: F ~> G): HxlM[G, A] = HxlM(hxl.mapK(fk))
156142

157143
def flatMapF[B](f: A => F[B])(implicit F: Functor[F]): HxlM[F, B] = HxlM(hxl.flatMapF(f))
144+
145+
def foldMap[G[_]](fk: Hxl.Compiler[F, G])(implicit G: Monad[G]): G[A] = hxl.foldMap(fk)
146+
147+
def applicative: Hxl[F, A] = hxl
158148
}
159149

160150
object HxlM {
151+
def unit[F[_]]: HxlM[F, Unit] = HxlM(Hxl.unit[F])
152+
153+
def liftF[F[_]: Functor, A](fa: F[A]): HxlM[F, A] = HxlM(Hxl.liftF(fa))
154+
155+
def pure[F[_], A](a: A): HxlM[F, A] = HxlM(Hxl.pure(a))
156+
157+
def apply[F[_], K, V](k: K, source: DataSource[F, K, V]): HxlM[F, Option[V]] =
158+
HxlM(Hxl(k, source))
159+
160+
def force[F[_]: ApplicativeThrow, K: Show, V](k: K, source: DataSource[F, K, V]): HxlM[F, V] =
161+
HxlM(Hxl.force(k, source))
162+
163+
def monadicK[F[_]]: Hxl[F, *] ~> HxlM[F, *] = new (Hxl[F, *] ~> HxlM[F, *]) {
164+
def apply[A](fa: Hxl[F, A]): HxlM[F, A] = fa.monadic
165+
}
166+
167+
def applicativeK[F[_]]: HxlM[F, *] ~> Hxl[F, *] = new (HxlM[F, *] ~> Hxl[F, *]) {
168+
def apply[A](fa: HxlM[F, A]): Hxl[F, A] = fa.applicative
169+
}
170+
161171
// Monad for HxlM
162172
// HxlM can implement any covariant typeclass (but not contravariant ones since `F ~> HxlM` but not `HxlM ~> F`).
163173
implicit def monadForHxlM[F[_]: Monad]: Monad[HxlM[F, *]] = {
@@ -174,9 +184,41 @@ object HxlM {
174184
}
175185
}
176186
}
187+
}
188+
189+
object instances {
190+
191+
/** A parallel instance for Hxl a bit of a footgun since (P: Parallel[F, Hxl]).monad: Monad[Hxl[F, *]], which can have very unfortunate
192+
* consequences if you're not careful.
193+
*
194+
* import hxl.instances.parallel._
195+
*/
196+
object parallel {
197+
implicit def parallelForHxl[F[_]](implicit P: Parallel[F]): Parallel[Hxl[F, *]] = {
198+
implicit def m: Monad[F] = P.monad
199+
implicit def a: Applicative[P.F] = P.applicative
200+
type F0[A] = F[A]
201+
new Parallel[Hxl[F, *]] {
202+
type F[A] = Hxl[F0, A]
203+
override def sequential: F ~> F = FunctionK.id[F]
204+
override def parallel: F ~> F = FunctionK.id[F]
205+
override def applicative: Applicative[F] = Hxl.applicativeInstance[F0, P.F](P.parallel, P.sequential)
206+
override def monad: Monad[Hxl[F0, *]] = {
207+
implicit val m = P.monad
208+
new Monad[Hxl[F0, *]] {
209+
override def flatMap[A, B](fa: Hxl[F0, A])(f: A => Hxl[F0, B]): Hxl[F0, B] =
210+
fa.andThen(f)
211+
override def tailRecM[A, B](a: A)(f: A => Hxl[F0, Either[A, B]]): Hxl[F0, B] =
212+
a.tailRecM(f(_).monadic).hxl
213+
override def pure[A](x: A): Hxl[F0, A] = Hxl.Done(x)
214+
}
215+
}
216+
}
217+
}
218+
}
177219

178220
/*
179-
* A parallel instance for HxlM is dangerously ambiguous.
221+
* A parallel instance for HxlM is ambiguous.
180222
* Consider the difference between parallel composition of the Batch axis and the lifted effect axis
181223
* Which one of the following do you want:
182224
* Hxl[F, A] | F[A]
@@ -187,15 +229,29 @@ object HxlM {
187229
*
188230
* With Hxl (applicative) then the Hxl axis is Batch, and ap / parAp controls the effect axis
189231
* With HxlM (monad) then the Hxl axis is Seq and the effect axis is ambigious
190-
* If you need a parallel instance for Hxl consider implementing one ad-hoc, here is an example:
191-
* ```scala
192-
* implicit def parallelForHxlM[G[_]: Monad]: Parallel[HxlM[G, *]] = new Parallel[HxlM[G, *]] {
193-
* type F[A] = Hxl[G, A]
194-
* override def sequential: F ~> HxlM[G, *] = FunctionK.liftFunction(HxlM(_))
195-
* override def parallel: HxlM[G, *] ~> F = FunctionK.liftFunction(_.hxl)
196-
* override def applicative: Applicative[F] = Hxl.applicativeForHxl[G]
197-
* override def monad: Monad[HxlM[G, *]] = monadForHxlM[G]
198-
* }
199-
* ```
232+
* Pick one by importing the appropriate instance:
233+
* import hxl.instances.hxlm.parallel._
234+
* // or
235+
* import hxl.instances.hxlm.sequential._
200236
*/
237+
object hxlm {
238+
object parallel {
239+
implicit def parallelHxlMForParallelEffect[G[_]](implicit P: Parallel[G]): Parallel[HxlM[G, *]] = {
240+
implicit def applicativePF: Applicative[P.F] = P.applicative
241+
implicit def monadF: Monad[G] = P.monad
242+
new Parallel[HxlM[G, *]] {
243+
type F[A] = Hxl[G, A]
244+
override def sequential: F ~> HxlM[G, *] = HxlM.monadicK[G]
245+
override def parallel: HxlM[G, *] ~> F = HxlM.applicativeK[G]
246+
override def applicative: Applicative[F] = Hxl.applicativeInstance[G, P.F](P.parallel, P.sequential)
247+
override def monad: Monad[HxlM[G, *]] = HxlM.monadForHxlM[G]
248+
}
249+
}
250+
}
251+
252+
object sequential {
253+
implicit def parallelHxlMForParallelEffect[G[_]: Monad]: Parallel[HxlM[G, *]] =
254+
parallel.parallelHxlMForParallelEffect[G](Parallel.identity[G])
255+
}
256+
}
201257
}

modules/core/src/test/scala/hxl/HxlEvaluationTest.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class HxlEvaluationTest extends FunSuite {
107107
F.raiseError[Map[String, String]](NonEmptyChain.one("error"))
108108
}
109109

110+
import hxl.instances.parallel._
110111
test("parallel composition in runner") {
111112
type Effect[A] = EitherNec[String, A]
112113
val fa = Hxl("foo", failingDataSource[Effect]("foo"))

project/metals.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
// This file enables sbt-bloop to create bloop config files.
44

5-
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.6")
5+
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.8")

0 commit comments

Comments
 (0)