├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── zio └── src │ ├── main │ └── scala │ │ ├── zio │ │ └── expose │ │ │ └── package.scala │ │ └── com │ │ └── github │ │ └── mvv │ │ └── sager │ │ └── zio │ │ ├── console │ │ └── package.scala │ │ ├── clock │ │ └── package.scala │ │ ├── blocking │ │ └── package.scala │ │ ├── system │ │ └── package.scala │ │ ├── random │ │ └── package.scala │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── github │ └── mvv │ └── sager │ └── zio │ └── test │ └── ZioSpec.scala ├── .scalafmt.conf ├── core └── src │ ├── test │ ├── scala3 │ │ └── com │ │ │ └── github │ │ │ └── mvv │ │ │ └── sager │ │ │ └── test │ │ │ ├── FindSpec.scala │ │ │ ├── RecordSpec.scala │ │ │ └── FoundSpec.scala │ └── scala2 │ │ └── com │ │ └── github │ │ └── mvv │ │ └── sager │ │ └── test │ │ └── RecordSpec.scala │ └── main │ ├── scala2 │ └── com │ │ └── github │ │ └── mvv │ │ └── sager │ │ ├── impl │ │ ├── SagerBlackBoxMacros.scala │ │ ├── SagerMacroUtils.scala │ │ └── SagerWhiteBoxMacros.scala │ │ └── Record.scala │ └── scala3 │ └── com │ └── github │ └── mvv │ └── sager │ ├── impl │ └── SagerMacros.scala │ └── Record.scala ├── .github └── workflows │ └── ci.yml ├── README.md └── zio-interop-cats └── src └── main └── scala └── com └── github └── mvv └── sager └── zio └── interop └── catz └── package.scala /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.swp 3 | *~ 4 | *.log 5 | target 6 | /project/metals.sbt 7 | /project/project 8 | -------------------------------------------------------------------------------- /zio/src/main/scala/zio/expose/package.scala: -------------------------------------------------------------------------------- 1 | package zio 2 | 3 | package object expose { 4 | def hadFatalError(): Boolean = 5 | zio.internal.FiberContext.fatal.get() 6 | } 7 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 2 | addSbtPlugin("io.crashbox" % "sbt-gpg" % "0.2.1") 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.12") 4 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.4.3 2 | maxColumn = 120 3 | align = most 4 | align.openParenDefnSite = true 5 | align.openParenCallSite = true 6 | newlines.implicitParamListModifierPrefer = before 7 | danglingParentheses.defnSite = false 8 | danglingParentheses.callSite = false 9 | includeCurlyBraceInSelectChains = false 10 | rewrite.rules = [SortImports, SortModifiers, RedundantBraces] 11 | rewrite.redundantBraces.generalExpressions = false 12 | literals.hexDigits = Upper 13 | runner.dialect = scala213source3 14 | fileOverride { 15 | "glob:**/scala3/**" { 16 | runner.dialect = scala3 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /zio/src/test/scala/com/github/mvv/sager/zio/test/ZioSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.zio.test 2 | 3 | import com.github.mvv.sager.zio.FoundService 4 | import com.github.mvv.sager.zio.blocking.Blocking 5 | import com.github.mvv.sager.zio.clock.Clock 6 | import com.github.mvv.sager.zio.console.Console 7 | import org.scalatest.funspec.AnyFunSpec 8 | import org.scalatest.matchers.must.Matchers._ 9 | 10 | class ZioSpec extends AnyFunSpec { 11 | import ZioSpec._ 12 | 13 | describe("ZIO service") { 14 | it("found in environment") { 15 | noException must be thrownBy implicitly[FoundService[Clock.Service, Environment]] 16 | } 17 | } 18 | } 19 | 20 | object ZioSpec { 21 | type Environment = Blocking with Clock with Console 22 | } 23 | -------------------------------------------------------------------------------- /core/src/test/scala3/com/github/mvv/sager/test/FindSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.test 2 | 3 | import com.github.mvv.sager.{Field, Record} 4 | import com.github.mvv.sager.Record.Find 5 | import org.scalatest.funspec.AnyFunSpec 6 | import org.scalatest.matchers.must.Matchers.* 7 | 8 | class FindSpec extends AnyFunSpec: 9 | describe("Find") { 10 | it("derive for fields (present)") { 11 | """ 12 | val find = summon[Find[Char, Nothing, Field[String, Int] & Field[Char, Float]]] 13 | summon[find.Rest =:= Field[String, Int]] 14 | """ must compile 15 | } 16 | 17 | it("derive for fields (absent)") { 18 | """ 19 | val find = summon[Find[Int, Nothing, Field[String, Int] & Field[Char, Float]]] 20 | summon[find.Rest =:= (Field[String, Int] & Field[Char, Float])] 21 | """ must compile 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | env: 8 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 9 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 10 | GPG_KEY: ${{ secrets.GPG_KEY }} 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v1 18 | - name: Setup Scala 19 | uses: olafurpg/setup-scala@v12 20 | with: 21 | java-version: "adopt@1.8" 22 | - name: Check source code formatting 23 | shell: bash 24 | run: sbt +scalafmtCheck +test:scalafmtCheck scalafmtSbtCheck 25 | - name: Build and Test 26 | shell: bash 27 | run: sbt +test 28 | - name: Publish 29 | if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' 30 | shell: bash 31 | run: | 32 | echo $GPG_KEY | base64 -d | gpg --import 33 | sbt +publish sonatypeBundleReleaseIfNotSnapshot 34 | -------------------------------------------------------------------------------- /zio/src/main/scala/com/github/mvv/sager/zio/console/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.zio 2 | 3 | import java.io.IOException 4 | 5 | import _root_.zio.{console => zioConsole, ULayer, URLayer, ZIO, ZLayer} 6 | 7 | package object console { 8 | type Console = Haz[zioConsole.Console.Service] 9 | type ConsoleEnv[A <: zioConsole.Console.Service] = Env[zioConsole.Console.Service, A] 10 | val Console: zioConsole.Console.type = zioConsole.Console 11 | 12 | object SagerConsole { 13 | val any: URLayer[Console, Console] = ZLayer.requires[Console] 14 | val live: ULayer[Console] = ZLayer.succeedHaz(Console.Service.live) 15 | } 16 | 17 | def putStr(line: => String): ZIO[Console, IOException, Unit] = 18 | ZIO.accessM(_.value.putStr(line)) 19 | def putStrErr(line: => String): ZIO[Console, IOException, Unit] = 20 | ZIO.accessM(_.value.putStrErr(line)) 21 | def putStrLn(line: => String): ZIO[Console, IOException, Unit] = 22 | ZIO.accessM(_.value.putStrLn(line)) 23 | def putStrLnErr(line: => String): ZIO[Console, IOException, Unit] = 24 | ZIO.accessM(_.value.putStrLnErr(line)) 25 | val getStrLn: ZIO[Console, IOException, String] = 26 | ZIO.accessM(_.value.getStrLn) 27 | } 28 | -------------------------------------------------------------------------------- /zio/src/main/scala/com/github/mvv/sager/zio/clock/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.zio 2 | 3 | import java.time.{DateTimeException, OffsetDateTime} 4 | import java.util.concurrent.TimeUnit 5 | 6 | import _root_.zio.{clock => zioClock, ULayer, URIO, URLayer, ZIO, ZLayer} 7 | import _root_.zio.duration.Duration 8 | 9 | package object clock { 10 | type Clock = Haz[zioClock.Clock.Service] 11 | type ClockEnv[A <: zioClock.Clock.Service] = Env[zioClock.Clock.Service, A] 12 | val Clock: zioClock.Clock.type = zioClock.Clock 13 | 14 | object SagerClock { 15 | val any: URLayer[Clock, Clock] = ZLayer.requires[Clock] 16 | val live: ULayer[Clock] = ZLayer.succeedHaz(Clock.Service.live) 17 | } 18 | 19 | def currentTime(unit: => TimeUnit): URIO[Clock, Long] = 20 | URIO.accessM(_.value.currentTime(unit)) 21 | val currentDateTime: ZIO[Clock, DateTimeException, OffsetDateTime] = 22 | ZIO.accessM(_.value.currentDateTime) 23 | val instant: URIO[Clock, java.time.Instant] = 24 | URIO.accessM(_.value.instant) 25 | val nanoTime: URIO[Clock, Long] = 26 | URIO.accessM(_.value.nanoTime) 27 | def sleep(duration: => Duration): URIO[Clock, Unit] = 28 | URIO.accessM(_.value.sleep(duration)) 29 | } 30 | -------------------------------------------------------------------------------- /core/src/test/scala3/com/github/mvv/sager/test/RecordSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.test 2 | 3 | import com.github.mvv.sager.{Field, Record} 4 | import org.scalatest.funspec.AnyFunSpec 5 | import org.scalatest.matchers.must.Matchers.* 6 | 7 | class RecordSpec extends AnyFunSpec: 8 | describe("Record") { 9 | it("add non-existent field") { 10 | val r = Field[Int]("a").add[Long](true).add[String](0.0) 11 | (r.get[Int]: String) mustEqual "a" 12 | (r.get[Long]: Boolean) mustEqual true 13 | (r.get[String]: Double) mustEqual 0.0 14 | } 15 | 16 | it("override existing field") { 17 | val r = Field[Int]("a") 18 | val r1 = r.add[Int](true) 19 | (r1.get[Int]: Boolean) mustEqual true 20 | val r2 = r.add[Long](true).add[Int](0.0f) 21 | (r2: Field[Long, Boolean] with Field[Int, Float]) mustEqual r2 22 | (r2.get[Long]: Boolean) mustEqual true 23 | (r2.get[Int]: Float) mustEqual 0.0f 24 | } 25 | 26 | it("updating field") { 27 | Field[Int]("a").add[Long](true).update[Int]((_: String) + "b").get[Int] mustEqual "ab" 28 | } 29 | 30 | it("updating field via map") { 31 | Field[Int]("a").add[Long](true).field[Int].map(_ + "b").get[Int] mustEqual "ab" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/test/scala2/com/github/mvv/sager/test/RecordSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.test 2 | 3 | import com.github.mvv.sager.{Field, Record} 4 | import org.scalatest.funspec.AnyFunSpec 5 | import org.scalatest.matchers.must.Matchers._ 6 | 7 | class RecordSpec extends AnyFunSpec { 8 | describe("Record") { 9 | it("add non-existent field") { 10 | val r = Field[Int]("a").add[Long](true).add[String](0.0) 11 | (r.get[Int]: String) mustEqual "a" 12 | (r.get[Long]: Boolean) mustEqual true 13 | (r.get[String]: Double) mustEqual 0.0 14 | } 15 | 16 | it("override existing field") { 17 | val r = Field[Int]("a") 18 | val r1 = r.add[Int](true) 19 | (r1.get[Int]: Boolean) mustEqual true 20 | val r2 = r.add[Long](true).add[Int](0.0f) 21 | (r2: Field[Long, Boolean] with Field[Int, Float]) mustEqual r2 22 | (r2.get[Long]: Boolean) mustEqual true 23 | (r2.get[Int]: Float) mustEqual 0.0f 24 | } 25 | 26 | it("updating field") { 27 | Field[Int]("a").add[Long](true).update[Int]((_: String) + "b").get[Int] mustEqual "ab" 28 | } 29 | 30 | it("updating field via map") { 31 | Field[Int]("a").add[Long](true).field[Int].map(_ + "b").get[Int] mustEqual "ab" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/test/scala3/com/github/mvv/sager/test/FoundSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.test 2 | 3 | import com.github.mvv.sager.{Field, Record} 4 | import com.github.mvv.sager.Record.{Absent, Found, FoundSome} 5 | import org.scalatest.funspec.AnyFunSpec 6 | import org.scalatest.matchers.must.Matchers.* 7 | 8 | class FoundSpec extends AnyFunSpec: 9 | describe("Found") { 10 | it("derive for fields") { 11 | """ 12 | val found = summon[FoundSome[Char, Field[String, Int] & Field[Char, Float]]] 13 | summon[found.Value =:= Float] 14 | summon[found.Rest =:= Field[String, Int]] 15 | """ must compile 16 | } 17 | 18 | it("derive for fields + absent") { 19 | """ 20 | def f[R <: Record](using Absent[Char, R]) = 21 | val found = summon[FoundSome[Char, Field[String, Int] & Field[Char, Float] & R]] 22 | summon[found.Value =:= Float] 23 | summon[found.Rest =:= (Field[String, Int] & R)] 24 | """ must compile 25 | } 26 | 27 | it("derive for fields + present") { 28 | """ 29 | def f[R <: Record](using w: FoundSome[Char, R]) = 30 | val found = summon[FoundSome[Char, Field[String, Int] & R]] 31 | summon[found.Value =:= w.Value] 32 | summon[found.Rest =:= (w.Rest & Field[String, Int])] 33 | """ must compile 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /zio/src/main/scala/com/github/mvv/sager/zio/blocking/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.zio 2 | 3 | import java.io.IOException 4 | import _root_.zio.{blocking => zioBlocking, RIO, UIO, ULayer, URIO, URLayer, ZIO, ZLayer} 5 | import _root_.zio.internal.Executor 6 | 7 | package object blocking { 8 | type Blocking = Haz[zioBlocking.Blocking.Service] 9 | type BlockingEnv[A <: zioBlocking.Blocking.Service] = Env[zioBlocking.Blocking.Service, A] 10 | val Blocking: zioBlocking.Blocking.type = zioBlocking.Blocking 11 | 12 | object SagerBlocking { 13 | val any: URLayer[Blocking, Blocking] = ZLayer.requires[Blocking] 14 | val live: ULayer[Blocking] = ZLayer.succeedHaz(Blocking.Service.live) 15 | } 16 | 17 | def blocking[R <: Blocking, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] = 18 | ZIO.accessM[R](_.value.blocking(zio)) 19 | def blockingExecutor: URIO[Blocking, Executor] = 20 | URIO.access(_.value.blockingExecutor) 21 | def effectBlocking[A](effect: => A): RIO[Blocking, A] = 22 | RIO.accessM(_.value.effectBlocking(effect)) 23 | def effectBlockingCancelable[A](effect: => A)(cancel: UIO[Unit]): RIO[Blocking, A] = 24 | RIO.accessM(_.value.effectBlockingCancelable(effect)(cancel)) 25 | def effectBlockingInterrupt[A](effect: => A): RIO[Blocking, A] = 26 | RIO.accessM(_.value.effectBlockingInterrupt(effect)) 27 | def effectBlockingIO[A](effect: => A): ZIO[Blocking, IOException, A] = 28 | effectBlocking(effect).refineToOrDie[IOException] 29 | } 30 | -------------------------------------------------------------------------------- /zio/src/main/scala/com/github/mvv/sager/zio/system/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.zio 2 | 3 | import _root_.zio.{system => zioSystem, RIO, ULayer, URIO, URLayer, ZIO, ZLayer} 4 | 5 | package object system { 6 | type System = Haz[zioSystem.System.Service] 7 | type SystemEnv[A <: zioSystem.System.Service] = Env[zioSystem.System.Service, A] 8 | val System: zioSystem.System.type = zioSystem.System 9 | 10 | object SagerSystem { 11 | val any: URLayer[System, System] = ZLayer.requires[System] 12 | val live: ULayer[System] = ZLayer.succeedHaz(System.Service.live) 13 | } 14 | 15 | def env(variable: => String): ZIO[System, SecurityException, Option[String]] = 16 | ZIO.accessM(_.value.env(variable)) 17 | def envOrElse(variable: String, alt: => String): ZIO[System, SecurityException, String] = 18 | ZIO.accessM(_.value.envOrElse(variable, alt)) 19 | def envOrOption(variable: String, alt: => Option[String]): ZIO[System, SecurityException, Option[String]] = 20 | ZIO.accessM(_.value.envOrOption(variable, alt)) 21 | val envs: ZIO[System, SecurityException, Map[String, String]] = 22 | ZIO.accessM(_.value.envs) 23 | val properties: RIO[System, Map[String, String]] = 24 | RIO.accessM(_.value.properties) 25 | def property(prop: => String): RIO[System, Option[String]] = 26 | RIO.accessM(_.value.property(prop)) 27 | def propertyOrElse(prop: String, alt: => String): RIO[System, String] = 28 | RIO.accessM(_.value.propertyOrElse(prop, alt)) 29 | def propertyOrOption(prop: String, alt: => Option[String]): RIO[System, Option[String]] = 30 | RIO.accessM(_.value.propertyOrOption(prop, alt)) 31 | val lineSeparator: URIO[System, String] = 32 | URIO.accessM(_.value.lineSeparator) 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/scala2/com/github/mvv/sager/impl/SagerBlackBoxMacros.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.impl 2 | 3 | import com.github.mvv.sager.Record 4 | import com.github.mvv.typine.impl.TypineMacros 5 | 6 | import scala.reflect.macros.blackbox 7 | 8 | class SagerBlackBoxMacros(val c: blackbox.Context) extends SagerMacroUtils { 9 | import c.universe._ 10 | 11 | def notFound[L: WeakTypeTag, R <: Record: WeakTypeTag]: Tree = { 12 | val labelType = weakTypeTag[L].tpe 13 | val recordType = weakTypeTag[R].tpe 14 | 15 | val (fields, rest) = deconstructRecordType(recordType) 16 | val (diffType, unknownType) = 17 | fields.foldLeft((Option.empty[Either[Tree, Type]], rest.map(Right(_): Either[Tree, Type]))) { 18 | case ((diffType, unknownType), (fieldLabelType, fieldValueType)) => 19 | val fieldType = createFieldType(fieldLabelType, fieldValueType) 20 | if (TypineMacros.searchUnequal(c)(labelType, fieldLabelType) != EmptyTree) { 21 | (withType(diffType, fieldType), unknownType) 22 | } else { 23 | (diffType, withType(unknownType, fieldType)) 24 | } 25 | } 26 | diffType.map(toType) match { 27 | case Some(diffType) => 28 | val absentInDiff = 29 | q"_root_.com.github.mvv.sager.Record.NotFound.unsafeMake[$labelType, $diffType]" 30 | unknownType.map(toType) match { 31 | case Some(unknownType) => 32 | q""" 33 | _root_.com.github.mvv.sager.Record.NotFound.make[$labelType, $diffType, $unknownType]( 34 | $absentInDiff, 35 | implicitly[_root_.com.github.mvv.sager.Record.NotFound[$labelType, $unknownType]]) 36 | """ 37 | case None => 38 | absentInDiff 39 | } 40 | case None => 41 | EmptyTree 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/scala2/com/github/mvv/sager/impl/SagerMacroUtils.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.impl 2 | 3 | import com.github.mvv.sager.{Field, Record} 4 | 5 | import scala.reflect.macros.blackbox 6 | 7 | trait SagerMacroUtils { 8 | val c: blackbox.Context 9 | import c.universe._ 10 | 11 | protected val baseRecordType = c.typeOf[Record] 12 | protected val baseFieldType = c.typeOf[Field[Any, Any]] 13 | 14 | protected def createFieldType(labelType: Type, valueType: Type): Type = { 15 | val TypeRef(fieldPre, fieldSym, _) = baseFieldType 16 | c.internal.typeRef(fieldPre, fieldSym, List(labelType, valueType)) 17 | } 18 | 19 | protected def withType(acc: Option[Either[Tree, Type]], tpe: Type): Option[Either[Tree, Type]] = 20 | acc match { 21 | case None => Some(Right(tpe)) 22 | case Some(Right(existingType)) => Some(Left(tq"$existingType with $tpe")) 23 | case Some(Left(existingType)) => Some(Left(tq"$existingType with $tpe")) 24 | } 25 | 26 | protected def toType(acc: Either[Tree, Type]): Type = 27 | acc match { 28 | case Right(tpe) => tpe 29 | case Left(tree) => c.typecheck(q"((???): $tree)").tpe 30 | } 31 | 32 | private def flattenRefinedTypes(tpe: c.Type): Seq[Type] = 33 | tpe match { 34 | case RefinedType(parents, _) => parents.flatMap(parent => flattenRefinedTypes(parent.dealias)) 35 | case _ => Seq(tpe) 36 | } 37 | 38 | protected def deconstructRecordType(recordType: Type): (Seq[(Type, Type)], Option[Type]) = { 39 | val (fields, rest) = flattenRefinedTypes(recordType.dealias) 40 | .foldLeft((List.empty[(Type, Type)], Option.empty[Either[Tree, Type]])) { case ((fields, rest), parentType) => 41 | (if (parentType.typeSymbol == baseFieldType.typeSymbol) { 42 | parentType.typeArgs match { 43 | case List(labelType, valueType) => Some(labelType -> valueType) 44 | case _ => None 45 | } 46 | } else { 47 | None 48 | }) match { 49 | case Some((labelType, valueType)) => 50 | ((labelType -> valueType) :: fields, rest) 51 | case None => 52 | (fields, withType(rest, parentType)) 53 | } 54 | } 55 | (fields, rest.map(toType)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sager 2 | [![Release Version](https://img.shields.io/nexus/r/https/oss.sonatype.org/com.github.mvv.sager/sager_2.13.svg)](https://oss.sonatype.org/content/repositories/releases/com/github/mvv/sager) 3 | [![Snapshot Version](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.github.mvv.sager/sager_2.13.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/github/mvv/sager) 4 | [![Build Status](https://travis-ci.com/mvv/sager.svg?branch=master)](https://travis-ci.com/mvv/sager) 5 | 6 | Generic records for Scala. Record types have the form 7 | 8 | ```scala 9 | Field[Label1, Value1] with ... with Field[LabelN, ValueN] 10 | ``` 11 | 12 | where `Field` is invariant in its first argument and covariant in the second. 13 | 14 | ## Using Sager in your project 15 | 16 | Add Sager to your dependencies 17 | 18 | ```scala 19 | libraryDependencies += "com.github.mvv.sager" %% "sager" % "0.2-M1" 20 | ``` 21 | 22 | To construct a record, use 23 | 24 | ```scala 25 | import com.github.mvv.sager._ 26 | // Label types 27 | trait Foo 28 | trait Bar 29 | trait Baz 30 | val r = Field[Foo]("foo").add[Bar](123).add[Baz](Vector.empty[Double]) 31 | // val r: Field[Foo,String] with Field[Bar,Int] with Field[Baz,Vector[Double]] 32 | ``` 33 | 34 | Record types are polymorthic in expected ways: 35 | 36 | ```scala 37 | // All of the following implicit searches succeed 38 | implicitly[r.type <:< Field[Bar,Int] with Field[Foo,String]] 39 | implicitly[r.type <:< Field[Baz,Seq[Double]]] 40 | ``` 41 | 42 | Accessing and modifying fields is simple: 43 | 44 | ```scala 45 | val bar = r.get[Bar] 46 | // val bar: Int = 123 47 | val newBar = r.add[Bar](false).get[Bar] 48 | // val newBar: Boolean = false 49 | // You can omit the `: Int` annotation in Scala 3 50 | val updatedBar = r.update[Bar]((_: Int) > 100).get[Bar] 51 | // val updatedBar: Boolean = true 52 | // Same thing, but more inference-friendly 53 | val mappedBar = r.field[Bar].map(_ > 100).get[Bar] 54 | // val mappedBar: Boolean = true 55 | val r1 = r.remove[Bar] 56 | // val r1: Field[Foo,String] with Field[Baz,Vector[Double]] 57 | ``` 58 | 59 | ## Submodules 60 | 61 | * `sager-zio` contains helpers for using records as [ZIO](https://github.com/zio/zio) environments 62 | * `sager-zio-interop-cats` extends ZIO Cats-Effect [interop](https://github.com/zio/interop-cats) with `sager-zio` 63 | support 64 | -------------------------------------------------------------------------------- /zio/src/main/scala/com/github/mvv/sager/zio/random/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.zio 2 | 3 | import _root_.zio.{random => zioRandom, Chunk, ULayer, URIO, URLayer, ZLayer} 4 | 5 | import java.util.UUID 6 | 7 | package object random { 8 | type Random = Haz[zioRandom.Random.Service] 9 | type RandomEnv[A <: zioRandom.Random.Service] = Env[zioRandom.Random.Service, A] 10 | val Random: zioRandom.Random.type = zioRandom.Random 11 | 12 | object SagerRandom { 13 | val any: URLayer[Random, Random] = ZLayer.requires[Random] 14 | val live: ULayer[Random] = ZLayer.succeedHaz(Random.Service.live) 15 | } 16 | 17 | val nextBoolean: URIO[Random, Boolean] = 18 | URIO.accessM(_.value.nextBoolean) 19 | def nextBytes(length: => Int): URIO[Random, Chunk[Byte]] = 20 | URIO.accessM(_.value.nextBytes(length)) 21 | val nextDouble: URIO[Random, Double] = 22 | URIO.accessM(_.value.nextDouble) 23 | def nextDoubleBetween(minInclusive: Double, maxExclusive: Double): URIO[Random, Double] = 24 | URIO.accessM(_.value.nextDoubleBetween(minInclusive, maxExclusive)) 25 | val nextFloat: URIO[Random, Float] = 26 | URIO.accessM(_.value.nextFloat) 27 | def nextFloatBetween(minInclusive: Float, maxExclusive: Float): URIO[Random, Float] = 28 | URIO.accessM(_.value.nextFloatBetween(minInclusive, maxExclusive)) 29 | val nextGaussian: URIO[Random, Double] = 30 | URIO.accessM(_.value.nextGaussian) 31 | val nextInt: URIO[Random, Int] = 32 | URIO.accessM(_.value.nextInt) 33 | def nextIntBetween(minInclusive: Int, maxExclusive: Int): URIO[Random, Int] = 34 | URIO.accessM(_.value.nextIntBetween(minInclusive, maxExclusive)) 35 | def nextIntBounded(n: => Int): URIO[Random, Int] = 36 | URIO.accessM(_.value.nextIntBounded(n)) 37 | val nextLong: URIO[Random, Long] = 38 | URIO.accessM(_.value.nextLong) 39 | def nextLongBetween(minInclusive: Long, maxExclusive: Long): URIO[Random, Long] = 40 | URIO.accessM(_.value.nextLongBetween(minInclusive, maxExclusive)) 41 | def nextLongBounded(n: => Long): URIO[Random, Long] = 42 | URIO.accessM(_.value.nextLongBounded(n)) 43 | val nextUUID: URIO[Random, UUID] = 44 | URIO.accessM(_.value.nextUUID) 45 | val nextPrintableChar: URIO[Random, Char] = 46 | URIO.accessM(_.value.nextPrintableChar) 47 | def nextString(length: => Int): URIO[Random, String] = 48 | URIO.accessM(_.value.nextString(length)) 49 | def setSeed(seed: Long): URIO[Random, Unit] = 50 | URIO.accessM(_.value.setSeed(seed)) 51 | def shuffle[A](list: => List[A]): URIO[Random, List[A]] = 52 | URIO.accessM(_.value.shuffle(list)) 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/scala2/com/github/mvv/sager/impl/SagerWhiteBoxMacros.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.impl 2 | 3 | import com.github.mvv.sager.Record 4 | import com.github.mvv.typine.impl.TypineMacros 5 | 6 | import scala.reflect.macros.whitebox 7 | 8 | class SagerWhiteBoxMacros(val c: whitebox.Context) extends SagerMacroUtils { 9 | import c.universe._ 10 | 11 | protected val baseEqType = c.typeOf[=:=[Any, Any]] 12 | protected val baseFoundType = c.typeOf[Record.Found[Any, Any, Any, Record]] 13 | protected val anyType = c.typeOf[Any] 14 | protected val nothingType = c.typeOf[Nothing] 15 | 16 | private def createEqType(tpe1: c.Type, tpe2: c.Type): Type = { 17 | val TypeRef(eqPre, eqSym, _) = baseEqType 18 | c.internal.typeRef(eqPre, eqSym, List(tpe1, tpe2)) 19 | } 20 | 21 | private def createFoundType(labelType: c.Type, recordType: c.Type): Type = { 22 | val TypeRef(foundPre, foundSym, _) = baseFoundType 23 | c.internal.typeRef(foundPre, foundSym, List(labelType, nothingType, anyType, recordType)) 24 | } 25 | 26 | def select[L: WeakTypeTag, R <: Record: WeakTypeTag](witness: Tree): Tree = { 27 | val labelType = weakTypeTag[L].tpe 28 | val recordType = weakTypeTag[R].tpe 29 | val refineTree = 30 | q""" 31 | { 32 | def refine[V](x: _root_.com.github.mvv.sager.Field[$labelType, V]): V = ??? 33 | refine((???): $recordType) 34 | } 35 | """ 36 | val valueType = c.typecheck(refineTree).tpe 37 | q"_root_.com.github.mvv.sager.Record.Present.make[$labelType, $valueType, $recordType]" 38 | } 39 | 40 | def found[L: WeakTypeTag, VL, VU, R <: Record: WeakTypeTag]: Tree = { 41 | val labelType = weakTypeTag[L].tpe 42 | val recordType = weakTypeTag[R].tpe 43 | 44 | val (fields, rest) = deconstructRecordType(recordType) 45 | val (sameType, diffType, unknownType) = 46 | fields.foldLeft( 47 | (Option.empty[(Either[Tree, Type], Either[Tree, Type])], 48 | Option.empty[Either[Tree, Type]], 49 | rest.map(Right(_): Either[Tree, Type]))) { 50 | case ((sameType, diffType, unknownType), (fieldLabelType, fieldValueType)) => 51 | val fieldType = createFieldType(fieldLabelType, fieldValueType) 52 | val eqType = createEqType(labelType, fieldLabelType) 53 | if (c.inferImplicitValue(eqType) != EmptyTree) { 54 | (withType(sameType.map(_._1), fieldType).flatMap(tpe => 55 | withType(sameType.map(_._2), fieldValueType).map((tpe, _))), 56 | diffType, 57 | unknownType) 58 | } else if (TypineMacros.searchUnequal(c)(labelType, fieldLabelType) != EmptyTree) { 59 | (sameType, withType(diffType, fieldType), unknownType) 60 | } else { 61 | (sameType, diffType, withType(unknownType, fieldType)) 62 | } 63 | } 64 | val absentInDiff = diffType 65 | .map(toType) 66 | .map(diffType => (diffType, q"_root_.com.github.mvv.sager.Record.NotFound.unsafeMake[$labelType, $diffType]")) 67 | sameType.map { case (sameType, valueType) => (toType(sameType), toType(valueType)) } match { 68 | case Some((sameType, valueType)) => 69 | val foundInSame = 70 | q"_root_.com.github.mvv.sager.Record.Found.unsafeMake[$labelType, $valueType, $valueType, $valueType, $baseRecordType, $sameType]" 71 | val (knownType, foundInKnown) = absentInDiff.fold((tq"$sameType", foundInSame)) { 72 | case (diffType, absentInDiff) => 73 | (tq"$diffType with $sameType", 74 | q"_root_.com.github.mvv.sager.Record.Found.make[$labelType, $valueType, $valueType, $sameType, $diffType]($foundInSame, $absentInDiff)") 75 | } 76 | unknownType.map(toType) match { 77 | case Some(unknownType) => 78 | q""" 79 | _root_.com.github.mvv.sager.Record.Found.make[$labelType, $valueType, $valueType, $knownType, $unknownType]( 80 | $foundInKnown, 81 | implicitly[_root_.com.github.mvv.sager.Record.Absent[$labelType, $unknownType]] 82 | ) 83 | """ 84 | case None => 85 | foundInKnown 86 | } 87 | case None => 88 | (absentInDiff, unknownType.map(toType)) match { 89 | case (Some((diffType, absentInDiff)), Some(unknownType)) => 90 | c.inferImplicitValue(createFoundType(labelType, unknownType)) match { 91 | case q"com.github.mvv.sager.Record.Found.found[$_, $_]" => 92 | EmptyTree 93 | case tree if tree == EmptyTree => 94 | EmptyTree 95 | case tree => 96 | val (lowerType, upperType) = tree.tpe.baseType(baseFoundType.typeSymbol) match { 97 | case AppliedTypeTree(_, List(_, lowerType, upperType, _)) => 98 | (lowerType, upperType) 99 | } 100 | q""" 101 | _root_.com.github.mvv.sager.Record.Found.make[$labelType, $lowerType, $upperType, $unknownType, $diffType]( 102 | $tree, $absentInDiff) 103 | """ 104 | } 105 | case _ => 106 | EmptyTree 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /zio/src/main/scala/com/github/mvv/sager/zio/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager 2 | 3 | import _root_.zio.{Has, IO, Runtime, Tag, ULayer, URIO, URLayer, ZIO, ZLayer, ZManaged} 4 | import _root_.zio.internal.Platform 5 | 6 | package object zio { 7 | type Haz[A] = Field[A, A] 8 | type Env[A, +B <: A] = Field[A, B] 9 | type UsesService[A, R <: Record] = Record.Find[A, A, R] 10 | type UsesServiceEnv[A, -B <: A, R <: Record] = Record.Find[A, B, R] 11 | type FoundService[A, R <: Record] = Record.FoundSub[A, A, R] 12 | type SEnv = clock.Clock with console.Console with system.System with random.Random with blocking.Blocking 13 | 14 | object SEnv { 15 | object Services { 16 | val live: SEnv = 17 | Record.empty 18 | .add[clock.Clock.Service](clock.Clock.Service.live) 19 | .add[console.Console.Service](console.Console.Service.live) 20 | .add[system.System.Service](system.System.Service.live) 21 | .add[random.Random.Service](random.Random.Service.live) 22 | .add[blocking.Blocking.Service](blocking.Blocking.Service.live) 23 | } 24 | val any: URLayer[SEnv, SEnv] = ZLayer.requires[SEnv] 25 | val live: ULayer[SEnv] = ZLayer.succeedMany(Services.live) 26 | } 27 | 28 | trait SagerRuntime extends Runtime[SEnv] { 29 | override val environment: SEnv = SEnv.Services.live 30 | override val platform: Platform = Platform.default 31 | } 32 | object SagerRuntime { 33 | lazy val default = Runtime(SEnv.Services.live, Platform.default) 34 | lazy val global = Runtime(SEnv.Services.live, Platform.global) 35 | } 36 | 37 | trait SagerApp extends SagerRuntime { 38 | def run(args: List[String]): URIO[SEnv, Int] 39 | final def main(args: Array[String]): Unit = 40 | try 41 | sys.exit( 42 | unsafeRun( 43 | (for { 44 | fiber <- run(args.toList).fork 45 | _ <- IO.effectTotal(java.lang.Runtime.getRuntime.addShutdownHook(new Thread { 46 | override def run(): Unit = 47 | if (_root_.zio.expose.hadFatalError()) { 48 | System.err.println("Fatal error, not interrupting the main Fiber") 49 | } else { 50 | val _ = unsafeRunSync(fiber.interrupt) 51 | } 52 | })) 53 | result <- fiber.join 54 | _ <- fiber.interrupt 55 | } yield result) 56 | ) 57 | ) 58 | catch { case _: SecurityException => } 59 | } 60 | 61 | implicit class SagerHazSyntax[R <: Record](val record: R) extends AnyVal { 62 | def has[A: Tag](implicit witness: R <:< Haz[A]): Has[A] = Has(record.get[A]) 63 | } 64 | 65 | implicit class SagerHasSyntax[R <: Has[_]](val has: R) extends AnyVal { 66 | def haz[A: Tag](implicit witness: R <:< Has[A]): Haz[A] = Field[A](has.get[A]) 67 | } 68 | 69 | implicit class SagerZioSyntax[R, E, A](val self: ZIO[R, E, A]) extends AnyVal { 70 | def asHaz(implicit tag: Tag[A]): ZIO[R, E, Haz[A]] = self.map(Field[A](_)) 71 | def asEnv[B >: A: Tag]: ZIO[R, E, Env[B, A]] = self.map(Field[B](_)) 72 | def toLayerHaz(implicit tag: Tag[A]): ZLayer[R, E, Haz[A]] = ZLayer.fromEffectHaz(self) 73 | def toLayerEnv[B >: A: Tag]: ZLayer[R, E, Env[B, A]] = ZLayer.fromEffectEnv[B](self) 74 | } 75 | 76 | implicit class SagerZManagedSyntax[R, E, A](val self: ZManaged[R, E, A]) extends AnyVal { 77 | def asHaz(implicit tag: Tag[A]): ZManaged[R, E, Haz[A]] = self.map(Field[A](_)) 78 | def asEnv[B >: A: Tag]: ZManaged[R, E, Env[B, A]] = self.map(Field[B](_)) 79 | } 80 | 81 | final class SagerZLayerSucceedEnvSyntax[A](val dummy: Unit) extends AnyVal { 82 | def apply[B <: A](a: => B)(implicit tag: Tag[A]): ULayer[Env[A, B]] = ZLayer.succeedMany(Field[A](a)) 83 | } 84 | 85 | final class SagerZLayerFromFunctionEnvSyntax[A](val dummy: Unit) extends AnyVal { 86 | def apply[C, B <: A](f: C => B)(implicit tag: Tag[A]): URLayer[C, Env[A, B]] = 87 | ZLayer.fromFunctionMany((c: C) => Field[A](f(c))) 88 | } 89 | 90 | final class SagerZLayerFromFunctionEnvMSyntax[A](val dummy: Unit) extends AnyVal { 91 | def apply[E, C, B <: A](f: C => IO[E, B])(implicit tag: Tag[A]): ZLayer[C, E, Env[A, B]] = 92 | ZLayer.fromFunctionManyM((c: C) => f(c).asEnv[A]) 93 | } 94 | 95 | final class SagerZLayerFromEffectEnvSyntax[A](val dummy: Unit) extends AnyVal { 96 | def apply[R, E, B <: A](zio: ZIO[R, E, B])(implicit tag: Tag[A]): ZLayer[R, E, Env[A, B]] = 97 | ZLayer.fromEffectMany(zio.asEnv[A]) 98 | } 99 | 100 | final class SagerZLayerFromManagedEnvSyntax[A](val dummy: Unit) extends AnyVal { 101 | def apply[R, E, B <: A](managed: ZManaged[R, E, B])(implicit tag: Tag[A]): ZLayer[R, E, Env[A, B]] = 102 | ZLayer.fromManagedMany(managed.asEnv[A]) 103 | } 104 | 105 | final class SagerZLayerFromServiceEnvSyntax[A](val dummy: Unit) extends AnyVal { 106 | def apply[B <: A, A0](f: A0 => B)(implicit tag0: Tag[A0], tag: Tag[A]): URLayer[Haz[A0], Env[A, B]] = 107 | ZLayer.fromFunctionEnv[A]((c: Haz[A0]) => f(c.get[A0])) 108 | } 109 | 110 | final class SagerZLayerFromServicesEnvSyntax[A](val dummy: Unit) extends AnyVal { 111 | def apply[B <: A, A0, A1](f: (A0, A1) => B)( 112 | implicit tag0: Tag[A0], 113 | tag1: Tag[A1], 114 | tag: Tag[A]): URLayer[Haz[A0] with Haz[A1], Env[A, B]] = 115 | ZLayer.fromFunctionEnv[A]((a: Haz[A0] with Haz[A1]) => f(a.get[A0], a.get[A1])) 116 | } 117 | 118 | final class SagerZLayerFromServiceEnvMSyntax[A](val dummy: Unit) extends AnyVal { 119 | def apply[E, B <: A, A0](f: A0 => IO[E, B])(implicit tag0: Tag[A0], tag: Tag[A]): ZLayer[Haz[A0], E, Env[A, B]] = 120 | ZLayer.fromFunctionEnvM[A]((c: Haz[A0]) => f(c.get[A0])) 121 | } 122 | 123 | final class SagerZLayerFromServicesEnvMSyntax[A](val dummy: Unit) extends AnyVal { 124 | def apply[E, B <: A, A0, A1](f: (A0, A1) => IO[E, B])( 125 | implicit tag0: Tag[A0], 126 | tag1: Tag[A1], 127 | tag: Tag[A]): ZLayer[Haz[A0] with Haz[A1], E, Env[A, B]] = 128 | ZLayer.fromFunctionEnvM[A]((a: Haz[A0] with Haz[A1]) => f(a.get[A0], a.get[A1])) 129 | } 130 | 131 | implicit class SagerZLayerCompanionSyntax(val underlying: ZLayer.type) extends AnyVal { 132 | def succeedHaz[A: Tag](a: => A): ULayer[Haz[A]] = ZLayer.succeedMany(Field[A](a)) 133 | def succeedEnv[A]: SagerZLayerSucceedEnvSyntax[A] = new SagerZLayerSucceedEnvSyntax[A](()) 134 | def fromFunctionHaz[A, B: Tag](f: A => B): URLayer[A, Haz[B]] = 135 | ZLayer.fromFunctionMany((a: A) => Field[B](f(a))) 136 | def fromFunctionEnv[A]: SagerZLayerFromFunctionEnvSyntax[A] = new SagerZLayerFromFunctionEnvSyntax[A](()) 137 | def fromFunctionHazM[E, A, B: Tag](f: A => IO[E, B]): ZLayer[A, E, Haz[B]] = 138 | ZLayer.fromFunctionManyM((a: A) => f(a).asHaz) 139 | def fromFunctionEnvM[A]: SagerZLayerFromFunctionEnvMSyntax[A] = new SagerZLayerFromFunctionEnvMSyntax[A](()) 140 | def fromEffectHaz[R, E, A: Tag](zio: ZIO[R, E, A]): ZLayer[R, E, Haz[A]] = ZLayer.fromEffectMany(zio.asHaz) 141 | def fromEffectEnv[A]: SagerZLayerFromEffectEnvSyntax[A] = new SagerZLayerFromEffectEnvSyntax[A](()) 142 | def fromManagedHaz[R, E, A: Tag](managed: ZManaged[R, E, A]): ZLayer[R, E, Haz[A]] = 143 | ZLayer.fromManagedMany(managed.asHaz) 144 | def fromManagedEnv[A]: SagerZLayerFromManagedEnvSyntax[A] = new SagerZLayerFromManagedEnvSyntax[A](()) 145 | def fromServiceHaz[A: Tag, B: Tag](f: A => B): URLayer[Haz[A], Haz[B]] = 146 | fromFunctionHaz((a: Haz[A]) => f(a.get[A])) 147 | def fromServiceEnv[A]: SagerZLayerFromServiceEnvSyntax[A] = new SagerZLayerFromServiceEnvSyntax[A](()) 148 | def fromServicesHaz[A0: Tag, A1: Tag, A: Tag](f: (A0, A1) => A): URLayer[Haz[A0] with Haz[A1], Haz[A]] = 149 | fromFunctionHaz((a: Haz[A0] with Haz[A1]) => f(a.get[A0], a.get[A1])) 150 | def fromServicesEnv[A]: SagerZLayerFromServicesEnvSyntax[A] = new SagerZLayerFromServicesEnvSyntax[A](()) 151 | def fromServiceHazM[E, A: Tag, B: Tag](f: A => IO[E, B]): ZLayer[Haz[A], E, Haz[B]] = 152 | fromFunctionHazM((a: Haz[A]) => f(a.get[A])) 153 | def fromServiceEnvM[A]: SagerZLayerFromServiceEnvMSyntax[A] = new SagerZLayerFromServiceEnvMSyntax[A](()) 154 | def fromServicesHazM[E, A0: Tag, A1: Tag, A: Tag]( 155 | f: (A0, A1) => IO[E, A]): ZLayer[Haz[A0] with Haz[A1], E, Haz[A]] = 156 | fromFunctionHazM((a: Haz[A0] with Haz[A1]) => f(a.get[A0], a.get[A1])) 157 | def fromServicesEnvM[A]: SagerZLayerFromServicesEnvMSyntax[A] = new SagerZLayerFromServicesEnvMSyntax[A](()) 158 | def haz[A]: URLayer[Haz[A], Haz[A]] = ZLayer.requires[Haz[A]] 159 | } 160 | 161 | final class SagerZLayerUpdateEnvSyntax[A, RIn, E, ROut <: Record](val self: ZLayer[RIn, E, ROut]) extends AnyVal { 162 | def apply[B <: A, C <: A](f: B => C)( 163 | implicit tag: Tag[A], 164 | found: Record.FoundSub[A, B, ROut]): ZLayer[RIn, E, found.Rest with Env[A, C]] = 165 | self >>> ZLayer.fromFunctionMany(_.update(f)) 166 | } 167 | 168 | implicit class SagerZLayerOutRSyntax[RIn, E, ROut <: Record](val self: ZLayer[RIn, E, ROut]) extends AnyVal { 169 | def updateHaz[A: Tag](f: A => A)(implicit found: Record.Found[A, A, A, ROut]): ZLayer[RIn, E, ROut] = 170 | self >>> ZLayer.fromFunctionMany(_.updateMono(f)) 171 | def updateEnv[A: Tag]: SagerZLayerUpdateEnvSyntax[A, RIn, E, ROut] = 172 | new SagerZLayerUpdateEnvSyntax[A, RIn, E, ROut](self) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /core/src/main/scala3/com/github/mvv/sager/impl/SagerMacros.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.impl 2 | 3 | import com.github.mvv.sager.{Field, Record} 4 | import com.github.mvv.typine.!:= 5 | import com.github.mvv.typine.impl.TypineMacros 6 | import izumi.reflect.macrortti.LightTypeTagRef.AbstractReference 7 | import izumi.reflect.dottyreflection.TypeInspections 8 | 9 | import scala.annotation.tailrec 10 | import scala.quoted.* 11 | 12 | object SagerMacros: 13 | private def unsafeNotFound( 14 | using qctx: Quotes)(labelRepr: qctx.reflect.TypeRepr, recordRepr: qctx.reflect.TypeRepr): qctx.reflect.Term = { 15 | import qctx.reflect.* 16 | Ref(Symbol.requiredMethod("com.github.mvv.sager.Record.NotFound.unsafeMake")) 17 | .appliedToTypes(List(labelRepr, recordRepr)) 18 | } 19 | 20 | private def makeNotFound(using qctx: Quotes)(labelRepr: qctx.reflect.TypeRepr, 21 | record1Repr: qctx.reflect.TypeRepr, 22 | record2Repr: qctx.reflect.TypeRepr)( 23 | absent1Term: qctx.reflect.Term, 24 | absent2Term: qctx.reflect.Term): qctx.reflect.Term = { 25 | import qctx.reflect.* 26 | Ref(Symbol.requiredMethod("com.github.mvv.sager.Record.NotFound.make")) 27 | .appliedToTypes(List(labelRepr, record1Repr, record2Repr)) 28 | .appliedToArgs(List(absent1Term, absent2Term)) 29 | } 30 | 31 | private def summonAbsent( 32 | using qctx: Quotes)(labelRepr: qctx.reflect.TypeRepr, recordRepr: qctx.reflect.TypeRepr): qctx.reflect.Term = { 33 | import qctx.reflect.* 34 | Ref(Symbol.requiredMethod("scala.Predef.summon")) 35 | .appliedToType(TypeRepr.of[Record.Absent].appliedTo(List(labelRepr, recordRepr))) 36 | } 37 | 38 | private def unsafeFound(using qctx: Quotes)(labelRepr: qctx.reflect.TypeRepr, 39 | lowerRepr: qctx.reflect.TypeRepr, 40 | upperRepr: qctx.reflect.TypeRepr, 41 | valueRepr: qctx.reflect.TypeRepr, 42 | restRepr: qctx.reflect.TypeRepr, 43 | recordRepr: qctx.reflect.TypeRepr): qctx.reflect.Term = { 44 | import qctx.reflect.* 45 | Ref(Symbol.requiredMethod("com.github.mvv.sager.Record.Found.unsafeMake")) 46 | .appliedToTypes(List(labelRepr, lowerRepr, upperRepr, valueRepr, restRepr, recordRepr)) 47 | } 48 | 49 | private def makeFound(using qctx: Quotes)(labelRepr: qctx.reflect.TypeRepr, 50 | lowerRepr: qctx.reflect.TypeRepr, 51 | upperRepr: qctx.reflect.TypeRepr, 52 | foundRecordRepr: qctx.reflect.TypeRepr, 53 | absentRecordRepr: qctx.reflect.TypeRepr)( 54 | foundTerm: qctx.reflect.Term, 55 | absentTerm: qctx.reflect.Term): qctx.reflect.Term = { 56 | import qctx.reflect.* 57 | Ref(Symbol.requiredMethod("com.github.mvv.sager.Record.Found.make")) 58 | .appliedToTypes(List(labelRepr, lowerRepr, upperRepr, foundRecordRepr, absentRecordRepr)) 59 | .appliedToArgs(List(foundTerm, absentTerm)) 60 | } 61 | 62 | def deriveFound[L: Type, VL: Type, VU: Type, R <: Record: Type]( 63 | using qctx: Quotes): Expr[Record.Found[L, VL, VU, R]] = 64 | import qctx.reflect.* 65 | val labelRepr = TypeRepr.of[L] 66 | val recordRepr = TypeRepr.of[R] 67 | val (fields, restRepr) = deconstructRecordType(recordRepr) 68 | val (sameReprs, diffRepr, unknownRepr) = 69 | fields.foldLeft((Option.empty[(TypeRepr, TypeRepr)], Option.empty[TypeRepr], restRepr)) { 70 | case ((sameReprs, diffRepr, unknownRepr), (fieldLabelRepr, fieldValueRepr)) => 71 | val fieldRepr = TypeRepr.of[Field].appliedTo(List(fieldLabelRepr, fieldValueRepr)) 72 | Implicits.search(TypeRepr.of[=:=].appliedTo(List(labelRepr, fieldLabelRepr))) match 73 | case _: ImplicitSearchSuccess => 74 | (Some(sameReprs.fold((fieldRepr, fieldValueRepr))((sameRepr, valueRepr) => 75 | (AndType(sameRepr, fieldRepr), AndType(valueRepr, fieldValueRepr)))), 76 | diffRepr, 77 | unknownRepr) 78 | case _: ImplicitSearchFailure => 79 | Implicits.search(TypeRepr.of[!:=].appliedTo(List(labelRepr, fieldLabelRepr))) match 80 | case _: ImplicitSearchSuccess => 81 | (sameReprs, Some(diffRepr.fold(fieldRepr)(AndType(_, fieldRepr))), unknownRepr) 82 | case _: ImplicitSearchFailure => 83 | (sameReprs, diffRepr, Some(unknownRepr.fold(fieldRepr)(AndType(_, fieldRepr)))) 84 | } 85 | val absentInDiff = diffRepr.map(diffRepr => (diffRepr, unsafeNotFound(labelRepr, diffRepr))) 86 | sameReprs match 87 | case Some((sameRepr, valueRepr)) => 88 | val foundInSame = unsafeFound(labelRepr, valueRepr, valueRepr, valueRepr, TypeRepr.of[Record], sameRepr) 89 | val (knownRepr, foundInKnown) = absentInDiff.fold((sameRepr, foundInSame)) { (diffRepr, absentInDiff) => 90 | (AndType(sameRepr, diffRepr), 91 | makeFound(labelRepr, valueRepr, valueRepr, sameRepr, diffRepr)(foundInSame, absentInDiff)) 92 | } 93 | unknownRepr match 94 | case Some(unknownRepr) => 95 | val absentInUnknown = summonAbsent(labelRepr, unknownRepr) 96 | makeFound(labelRepr, valueRepr, valueRepr, knownRepr, unknownRepr)(foundInKnown, absentInUnknown) 97 | .asExprOf[Record.Found[L, VL, VU, R]] 98 | case None => 99 | foundInKnown.asExprOf[Record.Found[L, VL, VU, R]] 100 | case None => 101 | (absentInDiff, unknownRepr) match 102 | case (Some((diffRepr, absentInDiff)), Some(unknownRepr)) => 103 | Implicits.search(TypeRepr.of[Record.FoundSome].appliedTo(List(labelRepr, unknownRepr))) match 104 | case success: ImplicitSearchSuccess => 105 | val (lowerRepr, upperRepr) = success.tree.tpe.baseType(TypeRepr.of[Record.Found].typeSymbol) match 106 | case AppliedType(conRepr, List(_, lowerRepr, upperRepr, _)) => 107 | (lowerRepr, upperRepr) 108 | makeFound(labelRepr, lowerRepr, upperRepr, unknownRepr, diffRepr)(success.tree, absentInDiff) 109 | .asExprOf[Record.Found[L, VL, VU, R]] 110 | case failure: ImplicitSearchFailure => 111 | report.throwError( 112 | s"while looking for field ${Type.show[L]} in record ${Type.show[R]}: ${failure.explanation}") 113 | case _ => 114 | report.throwError(s"could not prove that record ${Type.show[R]} has field ${Type.show[L]}") 115 | 116 | def deriveNotFound[L: Type, R <: Record: Type](using qctx: Quotes): Expr[Record.NotFound[L, R]] = 117 | import qctx.reflect.* 118 | val labelRepr = TypeRepr.of[L] 119 | val recordRepr = TypeRepr.of[R] 120 | val (fields, restRepr) = deconstructRecordType(recordRepr) 121 | val (diffRepr, unknownRepr) = fields.foldLeft((Option.empty[TypeRepr], restRepr)) { 122 | case ((diffRepr, unknownRepr), (fieldLabelRepr, fieldValueRepr)) => 123 | val fieldRepr = TypeRepr.of[Field].appliedTo(List(fieldLabelRepr, fieldValueRepr)) 124 | Implicits.search(TypeRepr.of[!:=].appliedTo(List(labelRepr, fieldLabelRepr))) match 125 | case _: ImplicitSearchSuccess => 126 | (Some(diffRepr.fold(fieldRepr)(AndType(_, fieldRepr))), unknownRepr) 127 | case _: ImplicitSearchFailure => 128 | (diffRepr, Some(unknownRepr.fold(fieldRepr)(AndType(_, fieldRepr)))) 129 | } 130 | diffRepr match 131 | case Some(diffRepr) => 132 | val absentInDiff = unsafeNotFound(labelRepr, diffRepr) 133 | unknownRepr match 134 | case Some(unknownRepr) => 135 | val absentInUnknown = summonAbsent(labelRepr, unknownRepr) 136 | makeNotFound(labelRepr, diffRepr, unknownRepr)(absentInDiff, absentInUnknown) 137 | .asExprOf[Record.NotFound[L, R]] 138 | case None => 139 | absentInDiff.asExprOf[Record.NotFound[L, R]] 140 | case None => 141 | report.throwError(s"could not prove that record ${Type.show[R]} does not have field ${Type.show[L]}") 142 | 143 | private def deconstructRecordType(using qctx: Quotes)(recordRepr: qctx.reflect.TypeRepr) 144 | : (Seq[(qctx.reflect.TypeRepr, qctx.reflect.TypeRepr)], Option[qctx.reflect.TypeRepr]) = 145 | import qctx.reflect.* 146 | def linearize(left: TypeRepr): LazyList[TypeRepr] = 147 | LazyList(left).flatMap { 148 | case AndType(firstRepr, secondRepr) => 149 | linearize(firstRepr.dealias) ++ linearize(secondRepr.dealias) 150 | case repr => 151 | LazyList(repr) 152 | } 153 | @tailrec 154 | def loop(fields: Seq[(TypeRepr, TypeRepr)], 155 | rest: Option[TypeRepr], 156 | left: Seq[TypeRepr]): (Seq[(TypeRepr, TypeRepr)], Option[TypeRepr]) = 157 | left.headOption match 158 | case Some(tpe @ AppliedType(conRepr, List(labelRepr, valueRepr))) if conRepr =:= TypeRepr.of[Field] => 159 | loop((labelRepr, valueRepr) +: fields, rest, left.tail) 160 | case Some(tpe) => 161 | loop(fields, Some(rest.fold(tpe)(AndType(_, tpe))), left.tail) 162 | case None => 163 | val left1 = rest.fold(left)(_ +: left) 164 | (fields, left1.headOption.map(left1.tail.foldLeft(_)(AndType(_, _)))) 165 | loop(Nil, None, linearize(recordRepr.dealias.simplified)) 166 | -------------------------------------------------------------------------------- /core/src/main/scala3/com/github/mvv/sager/Record.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager 2 | 3 | import com.github.mvv.sager.impl.SagerMacros 4 | import com.github.mvv.typine.!:= 5 | import izumi.reflect.Tag 6 | import izumi.reflect.macrortti.LightTypeTag 7 | 8 | import scala.annotation.implicitNotFound 9 | 10 | sealed trait Record extends Serializable 11 | sealed trait Field[L, +V] extends Record 12 | 13 | object Field: 14 | extension [L, V](field: Field[L, V]) 15 | def value(using tag: Tag[L]): V = field.get[L] 16 | def map[V1](f: V => V1)(using tag: Tag[L]): Field[L, V1] = Field[L](f(field.value)) 17 | 18 | opaque type Make[L] = Unit 19 | extension [L, V](make: Make[L]) def apply(value: V)(using tag: Tag[L]): Field[L, V] = Record.empty.add[L](value) 20 | 21 | def apply[L]: Make[L] = () 22 | 23 | object Record: 24 | final private case class Impl(fields: Map[LightTypeTag, Any]) extends Field[Any, Any] { 25 | override def toString: String = 26 | fields.iterator.map((key, value) => s"$key -> $value").mkString("Record(", ", ", ")") 27 | } 28 | 29 | @implicitNotFound("could not prove that record ${R} has field ${L}") 30 | sealed trait Present[L, +V, -R <: Record]: 31 | type Value <: V 32 | given valueWitness: (R <:< Field[L, Value]) 33 | def exact: Present[L, Value, R] 34 | object Present: 35 | private val singleton = new Present[Any, Any, Field[Any, Any]]: 36 | override type Value = Any 37 | override given valueWitness: (Field[Any, Any] <:< Field[Any, Any]) = summon 38 | override def exact: Present[Any, Any, Field[Any, Any]] = this 39 | override def toString: String = "Record.Present" 40 | inline given make[L, V, R <: Record](using witness: R <:< Field[L, V]): (Present[L, V, R] { type Value = V }) = 41 | singleton.asInstanceOf[Present[L, V, R] { type Value = V }] 42 | 43 | type Select[L, -R <: Record] = Present[L, Any, R] 44 | 45 | @implicitNotFound("could not prove that record ${R} does not have field ${L}") 46 | sealed trait Absent[L, +R <: Record]: 47 | given distinct[L1](using Select[L1, R]): (L !:= L1) 48 | sealed trait AbsentLowest: 49 | inline given derive[L, R <: Record]: Absent[L, R] = ${ SagerMacros.deriveNotFound[L, R] } 50 | sealed trait AbsentLow extends AbsentLowest: 51 | inline given distinct[L1, L2, V2](using L1 !:= L2): Absent[L1, Field[L2, V2]] = NotFound.distinct[L1, L2, V2] 52 | object Absent extends AbsentLow: 53 | inline given empty[L]: Absent[L, Record] = NotFound.empty[L] 54 | inline def make[L, R1 <: Record, R2 <: Record](absent1: Absent[L, R1], absent2: Absent[L, R2]): Absent[L, R1 & R2] = 55 | NotFound.make[L, R1, R2](absent1, absent2) 56 | 57 | @implicitNotFound("could not extract part of ${R} that does not contain field ${L}") 58 | sealed trait Find[L, -V, R <: Record]: 59 | type Rest >: R <: Record 60 | given restContainsNot: NotFound[L, Rest] 61 | given restContains[S <: Record](using R <:< S, Absent[L, S]): (Rest <:< S) 62 | given restFind[L1, V1](using L1 !:= L, Find[L1, V1, R]): Find[L1, V1, Rest] 63 | given restNotFound[L1](using L1 !:= L, Absent[L1, R]): NotFound[L1, Rest] 64 | given restFound[L1, VL, VU]( 65 | using distinct: L1 !:= L, 66 | found: Found[L1, VL, VU, R]): (Found[L1, VL, VU, Rest] { type Value = found.Value }) 67 | given reconstructWitness: (Field[L, V] with Rest <:< R) 68 | sealed trait FindLow: 69 | inline given notFound[L, R <: Record](using witness: NotFound[L, R]): (Find[L, Any, R] { type Rest = R }) = witness 70 | object Find extends FindLow: 71 | inline given found[L, R <: Record]( 72 | using witness: FoundSome[L, R]): (Find[L, witness.Value, R] { type Rest = witness.Rest }) = witness.exact 73 | 74 | type FindAny[L, R <: Record] = Find[L, Nothing, R] 75 | 76 | @implicitNotFound("could not split ${R} into field ${L} with value >: ${VL} <: ${VU} and the rest") 77 | sealed trait Found[L, -VL, +VU, R <: Record] extends Find[L, VL, R] with Present[L, VU, R]: 78 | self => 79 | override type Value >: VL <: VU 80 | override given reconstructWitness: (Field[L, Value] with Rest =:= R) 81 | override def exact: Found[L, Value, Value, R] { type Rest = self.Rest } 82 | sealed trait FoundLowest: 83 | transparent inline given derive[L, VL, VU, R <: Record]: Found[L, VL, VU, R] = ${ 84 | SagerMacros.deriveFound[L, VL, VU, R] 85 | } 86 | sealed trait FoundLower extends FoundLowest: 87 | transparent inline given deriveSub[L, V, R <: Record]: FoundSub[L, V, R] = ${ 88 | SagerMacros.deriveFound[L, Nothing, V, R] 89 | } 90 | sealed trait FoundLow extends FoundLower: 91 | transparent inline given deriveSome[L, R <: Record]: FoundSome[L, R] = ${ 92 | SagerMacros.deriveFound[L, Nothing, Any, R] 93 | } 94 | object Found extends FoundLow: 95 | private val singleton = new Found[Any, Nothing, Any, Field[Any, Any]]: 96 | type Value = Any 97 | type Rest = Record 98 | override given valueWitness: (Field[Any, Any] <:< Field[Any, Any]) = 99 | reconstructWitness 100 | override given restContainsNot: NotFound[Any, Record] = 101 | summon[NotFound[Any, Record]] 102 | override given restContains[S <: Record](using sup: Field[Any, Any] <:< S, absent: Absent[Any, S]): (Rest <:< S) = 103 | sup.asInstanceOf[Rest <:< S] 104 | override given restFind[L1, V1]( 105 | using distinct: L1 !:= Any, 106 | find: Find[L1, V1, Field[Any, Any]]): Find[L1, V1, Rest] = 107 | find.asInstanceOf[Find[L1, V1, Rest]] 108 | override given restNotFound[L1]( 109 | using distinct: L1 !:= Any, 110 | absent: Absent[L1, Field[Any, Any]]): NotFound[L1, Rest] = 111 | absent.asInstanceOf[NotFound[L1, Rest]] 112 | override given restFound[L1, VL, VU]( 113 | using distinct: L1 !:= Any, 114 | found: Found[L1, VL, VU, Field[Any, Any]]): (Found[L1, VL, VU, Rest] { type Value = found.Value }) = 115 | asInstanceOf[Found[L1, VL, VU, Rest] { type Value = found.Value }] 116 | override given reconstructWitness: (Field[Any, Value] with Rest =:= Field[Any, Any]) = 117 | summon[Field[Any, Any] with Record =:= Field[Any, Any]] 118 | override def exact: Found[Any, Any, Any, Field[Any, Any]] { type Rest = Record } = 119 | asInstanceOf[Found[Any, Any, Any, Field[Any, Any]] { type Rest = Record }] 120 | override def toString: String = "Record.Found" 121 | def unsafeMake[L, VL, VU, V >: VL <: VU, RT <: Record, R <: RT] 122 | : Found[L, VL, VU, R] { type Value = V; type Rest = RT } = 123 | singleton.asInstanceOf[Found[L, VL, VU, R] { type Value = V; type Rest = RT }] 124 | inline given field[L1, L2, V]( 125 | using L1 =:= L2): (Found[L1, V, V, Field[L2, V]] { type Value = V; type Rest = Record }) = 126 | unsafeMake[L1, V, V, V, Record, Field[L2, V]] 127 | inline def make[L, VL, VU, R1 <: Record, R2 <: Record]( 128 | found: Found[L, VL, VU, R1], 129 | absent: Absent[L, R2]): Found[L, VL, VU, R1 & R2] { type Value = found.Value; type Rest = found.Rest & R2 } = 130 | unsafeMake[L, VL, VU, found.Value, found.Rest & R2, R1 & R2] 131 | 132 | type FoundSome[L, R <: Record] = Found[L, Nothing, Any, R] 133 | type FoundSup[L, V, R <: Record] = Found[L, V, Any, R] 134 | type FoundSub[L, V, R <: Record] = Found[L, Nothing, V, R] 135 | 136 | @implicitNotFound("could not prove that record ${R} does not have field ${L}") 137 | sealed trait NotFound[L, R <: Record] extends Find[L, Any, R] with Absent[L, R]: 138 | final override type Rest = R 139 | object NotFound: 140 | private val singleton = new NotFound[Any, Record]: 141 | override given restContainsNot: NotFound[Any, Rest] = this 142 | override given restContains[S <: Record](using sup: Record <:< S, absent: Absent[Any, S]): (Record <:< S) = sup 143 | override given restFind[L1, V1](using distinct: L1 !:= Any, find: Find[L1, V1, Record]): Find[L1, V1, Rest] = 144 | find 145 | override given restNotFound[L1](using distinct: L1 !:= Any, absent: Absent[L1, Record]): NotFound[L1, Rest] = 146 | absent.asInstanceOf[NotFound[L1, Rest]] 147 | override given restFound[L1, VL, VU]( 148 | using distinct: L1 !:= Any, 149 | found: Found[L1, VL, VU, Record]): (Found[L1, VL, VU, Rest] { type Value = found.Value }) = 150 | found 151 | override given reconstructWitness: (Field[Any, Any] with Record <:< Record) = 152 | summon[Field[Any, Any] with Record <:< Record] 153 | override given distinct[L1](using Select[L1, Record]): (Any !:= L1) = !:=.unsafeMake[Any, L1] 154 | override def toString: String = "Record.NotFound" 155 | def unsafeMake[L, R <: Record]: NotFound[L, R] = singleton.asInstanceOf[NotFound[L, R]] 156 | def empty[L]: NotFound[L, Record] = singleton.asInstanceOf[NotFound[L, Record]] 157 | def distinct[L1, L2, V2](using L1 !:= L2): NotFound[L1, Field[L2, V2]] = 158 | singleton.asInstanceOf[NotFound[L1, Field[L2, V2]]] 159 | inline given absent[L, R <: Record](using absent: Absent[L, R]): NotFound[L, R] = 160 | absent.asInstanceOf[NotFound[L, R]] 161 | def make[L, R1 <: Record, R2 <: Record](absent1: Absent[L, R1], absent2: Absent[L, R2]): NotFound[L, R1 & R2] = 162 | unsafeMake[L, R1 & R2] 163 | 164 | extension [R <: Record](record: R) 165 | def get[L](using tag: Tag[L], present: Select[L, R]): present.Value = 166 | record match 167 | case Impl(fields) => fields(tag.tag).asInstanceOf[present.Value] 168 | def add[L]: AddSyntax[L, R] = new AddSyntax[L, R](record) 169 | def remove[L](using tag: Tag[L], find: FindAny[L, R]): find.Rest = 170 | record match 171 | case Impl(fields) => Impl(fields.removed(tag.tag)).asInstanceOf[find.Rest] 172 | def update[L](using found: FoundSome[L, R]): UpdateSyntax[L, found.Value, found.Rest, R] = 173 | new UpdateSyntax[L, found.Value, found.Rest, R](record) 174 | def updateMono[L](using found: FoundSome[L, R]): UpdateMonoSyntax[L, found.Value, R] = 175 | new UpdateMonoSyntax[L, found.Value, R](record) 176 | def field[L](using present: Select[L, R]): FieldSyntax[L, present.Value, R] = 177 | new FieldSyntax[L, present.Value, R](record) 178 | 179 | final class AddSyntax[L, R <: Record](val record: R) extends AnyVal: 180 | def apply[V](value: V)(using tag: Tag[L], find: Find[L, Nothing, R]): Field[L, V] & find.Rest = 181 | record match 182 | case Impl(fields) => Impl(fields.updated(tag.tag, value)).asInstanceOf[Field[L, V] & find.Rest] 183 | 184 | final class UpdateSyntax[L, V, Rest <: Record, R <: Record](val record: R) extends AnyVal: 185 | def apply[V1](f: V => V1)(using tag: Tag[L]): Field[L, V1] & Rest = 186 | record match 187 | case Impl(fields) => 188 | val key = tag.tag 189 | val value = fields(key).asInstanceOf[V] 190 | Impl(fields.updated(key, f(value))).asInstanceOf[Field[L, V1] & Rest] 191 | 192 | final class UpdateMonoSyntax[L, V, R <: Record](val record: R) extends AnyVal: 193 | def apply(f: V => V)(using tag: Tag[L]): R = 194 | record match 195 | case Impl(fields) => 196 | val key = tag.tag 197 | val value = fields(key).asInstanceOf[V] 198 | Impl(fields.updated(key, f(value))).asInstanceOf[R] 199 | 200 | final class FieldSyntax[L, V, R <: Record](val record: R) extends AnyVal: 201 | def value(using tag: Tag[L]): V = 202 | record match 203 | case Impl(fields) => fields(tag.tag).asInstanceOf[V] 204 | def remove(using tag: Tag[L], find: FindAny[L, R]): find.Rest = 205 | record.remove[L] 206 | def map[V1](f: V => V1)(using tag: Tag[L], found: FoundSub[L, V, R]): Field[L, V1] & found.Rest = 207 | record.update[L](f) 208 | def mapMono(f: V => V)(using tag: Tag[L], found: Found[L, V, V, R]): R = 209 | record.updateMono[L](f) 210 | 211 | val empty: Record = Impl(Map.empty) 212 | -------------------------------------------------------------------------------- /core/src/main/scala2/com/github/mvv/sager/Record.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager 2 | 3 | import com.github.mvv.sager.impl.{SagerBlackBoxMacros, SagerWhiteBoxMacros} 4 | import com.github.mvv.typine.!:= 5 | import izumi.reflect.Tag 6 | import izumi.reflect.macrortti.LightTypeTag 7 | 8 | import scala.annotation.implicitNotFound 9 | import scala.language.experimental.macros 10 | 11 | sealed trait Record extends Serializable 12 | sealed trait Field[L, +V] extends Record 13 | 14 | object Field { 15 | implicit final class FieldSyntax[L, V](val underlying: Field[L, V]) extends AnyVal { 16 | def value(implicit tag: Tag[L]): V = underlying.get[L](tag, Record.Present.make[L, V, Field[L, V]]) 17 | def map[V1](f: V => V1)(implicit tag: Tag[L]): Field[L, V1] = Field[L](f(value)) 18 | } 19 | 20 | final class Single[L](val dummy: Unit) extends AnyVal { 21 | def apply[V](value: V)(implicit tag: Tag[L]): Field[L, V] = Record.empty.add[L](value) 22 | } 23 | 24 | def apply[L]: Single[L] = new Single[L](()) 25 | } 26 | 27 | object Record { 28 | final private case class Impl(fields: Map[LightTypeTag, Any]) extends Field[Any, Any] { 29 | override def toString: String = 30 | fields.iterator.map { case (key, value) => s"$key -> $value" }.mkString("Record(", ", ", ")") 31 | } 32 | 33 | @implicitNotFound("could not prove that record ${R} has field ${L}") 34 | sealed trait Present[L, +V, -R <: Record] { 35 | type Value <: V 36 | implicit def valueWitness: R <:< Field[L, Value] 37 | def exact: Present[L, Value, R] 38 | } 39 | object Present { 40 | private val singleton: Present[Any, Any, Field[Any, Any]] = new Present[Any, Any, Field[Any, Any]] { 41 | override type Value = Any 42 | implicit override val valueWitness: Field[Any, Any] <:< Field[Any, Any] = implicitly 43 | override def exact: Present[Any, Any, Field[Any, Any]] = this 44 | override def toString: String = "Record.Present" 45 | } 46 | def make[L, V, R <: Record](implicit witness: R <:< Field[L, V]): Present[L, V, R] { type Value = V } = 47 | singleton.asInstanceOf[Present[L, V, R] { type Value = V }] 48 | implicit def select[L, R <: Record](implicit witness: R <:< Field[L, Any]): Select[L, R] = 49 | macro SagerWhiteBoxMacros.select[L, R] 50 | } 51 | 52 | type Select[L, -R <: Record] = Present[L, Any, R] 53 | 54 | @implicitNotFound("could not prove that record ${R} does not have field ${L}") 55 | sealed trait Absent[L, +R <: Record] { 56 | implicit def distinct[L1](implicit present: Select[L1, R]): L !:= L1 57 | } 58 | sealed trait AbsentLowest { 59 | implicit def absent[L, R <: Record]: Absent[L, R] = macro SagerBlackBoxMacros.notFound[L, R] 60 | } 61 | sealed trait AbsentLow extends AbsentLowest { 62 | implicit def distinct[L1, L2, V2](implicit witness: L1 !:= L2): Absent[L1, Field[L2, V2]] = 63 | NotFound.distinct[L1, L2, V2] 64 | } 65 | object Absent extends AbsentLow { 66 | implicit def empty[L]: Absent[L, Record] = NotFound.empty[L] 67 | def make[L, R1 <: Record, R2 <: Record](absent1: Absent[L, R1], absent2: Absent[L, R2]): Absent[L, R1 with R2] = 68 | NotFound.make[L, R1, R2](absent1, absent2) 69 | } 70 | 71 | @implicitNotFound("could not extract part of ${R} that does not contain field ${L}") 72 | sealed trait Find[L, -V, R <: Record] { 73 | type Rest >: R <: Record 74 | implicit def restContainsNot: NotFound[L, Rest] 75 | implicit def restContains[S <: Record](implicit sup: R <:< S, absent: Absent[L, S]): Rest <:< S 76 | implicit def restFind[L1, V1](implicit distinct: L1 !:= L, find: Find[L1, V1, R]): Find[L1, V1, Rest] 77 | implicit def restNotFound[L1](implicit distinct: L1 !:= L, absent: Absent[L1, R]): NotFound[L1, Rest] 78 | implicit def restFound[L1, VL, VU >: VL]( 79 | implicit distinct: L1 !:= L, 80 | found: Found[L1, VL, VU, R]): Found[L1, VL, VU, Rest] { type Value = found.Value } 81 | implicit def reconstructWitness: Field[L, V] with Rest <:< R 82 | } 83 | sealed trait FindLow { 84 | implicit def notFound[L, R <: Record](implicit witness: NotFound[L, R]): Find[L, Any, R] { type Rest = R } = witness 85 | } 86 | object Find extends FindLow { 87 | implicit def found[L, R <: Record]( 88 | implicit witness: FoundSome[L, R]): Find[L, witness.Value, R] { type Rest = witness.Rest } = witness.exact 89 | } 90 | 91 | type FindAny[L, R <: Record] = Find[L, Nothing, R] 92 | 93 | @implicitNotFound("could not split ${R} into field ${L} with value >: ${VL} <: ${VU} and the rest") 94 | sealed trait Found[L, -VL, +VU >: VL, R <: Record] extends Find[L, VL, R] with Present[L, VU, R] { self => 95 | override type Value >: VL <: VU 96 | implicit override def reconstructWitness: Field[L, Value] with Rest =:= R 97 | override def exact: Found[L, Value, Value, R] { type Rest = self.Rest } 98 | } 99 | sealed trait FoundLow { 100 | implicit def found[L, VL, VU >: VL, R <: Record]: Found[L, VL, VU, R] = 101 | macro SagerWhiteBoxMacros.found[L, VL, VU, R] 102 | } 103 | object Found extends FoundLow { 104 | private val singleton = new Found[Any, Nothing, Any, Field[Any, Any]] { 105 | type Value = Any 106 | type Rest = Record 107 | implicit override def valueWitness: (Field[Any, Any] <:< Field[Any, Any]) = 108 | reconstructWitness 109 | implicit override def restContainsNot: NotFound[Any, Record] = 110 | implicitly[NotFound[Any, Record]] 111 | implicit override def restContains[S <: Record]( 112 | implicit sup: Field[Any, Any] <:< S, 113 | absent: Absent[Any, S]): (Rest <:< S) = 114 | sup.asInstanceOf[Rest <:< S] 115 | implicit override def restFind[L1, V1]( 116 | implicit distinct: L1 !:= Any, 117 | find: Find[L1, V1, Field[Any, Any]]): Find[L1, V1, Rest] = 118 | find.asInstanceOf[Find[L1, V1, Rest]] 119 | implicit override def restNotFound[L1]( 120 | implicit distinct: L1 !:= Any, 121 | absent: Absent[L1, Field[Any, Any]]): NotFound[L1, Rest] = 122 | absent.asInstanceOf[NotFound[L1, Rest]] 123 | implicit override def restFound[L1, VL, VU >: VL]( 124 | implicit distinct: L1 !:= Any, 125 | found: Found[L1, VL, VU, Field[Any, Any]]): Found[L1, VL, VU, Rest] { type Value = found.Value } = 126 | asInstanceOf[Found[L1, VL, VU, Rest] { type Value = found.Value }] 127 | implicit override def reconstructWitness: Field[Any, Value] with Rest =:= Field[Any, Any] = 128 | implicitly[Field[Any, Any] with Record =:= Field[Any, Any]] 129 | override def exact: Found[Any, Any, Any, Field[Any, Any]] { type Rest = Record } = 130 | asInstanceOf[Found[Any, Any, Any, Field[Any, Any]] { type Rest = Record }] 131 | override def toString: String = "Record.Found" 132 | } 133 | def unsafeMake[L, VL, VU >: VL, V >: VL <: VU, RT <: Record, R <: RT] 134 | : Found[L, VL, VU, R] { type Value = V; type Rest = RT } = 135 | singleton.asInstanceOf[Found[L, VL, VU, R] { type Value = V; type Rest = RT }] 136 | implicit def field[L1, L2, V]( 137 | implicit distinct: L1 =:= L2): Found[L1, V, V, Field[L2, V]] { type Value = V; type Rest = Record } = 138 | unsafeMake[L1, V, V, V, Record, Field[L2, V]] 139 | def make[L, VL, VU >: VL, R1 <: Record, R2 <: Record](found: Found[L, VL, VU, R1], absent: Absent[L, R2]) 140 | : Found[L, VL, VU, R1 with R2] { type Value = found.Value; type Rest = found.Rest with R2 } = 141 | unsafeMake[L, VL, VU, found.Value, found.Rest with R2, R1 with R2] 142 | } 143 | 144 | type FoundSome[L, R <: Record] = Found[L, Nothing, Any, R] 145 | type FoundSup[L, V, R <: Record] = Found[L, V, Any, R] 146 | type FoundSub[L, V, R <: Record] = Found[L, Nothing, V, R] 147 | 148 | @implicitNotFound("could not prove that record ${R} does not have field ${L}") 149 | sealed trait NotFound[L, R <: Record] extends Find[L, Any, R] with Absent[L, R] { 150 | final override type Rest = R 151 | } 152 | object NotFound { 153 | private val singleton = new NotFound[Any, Record] { 154 | implicit override def restContainsNot: NotFound[Any, Rest] = this 155 | implicit override def restContains[S <: Record]( 156 | implicit sup: Record <:< S, 157 | absent: Absent[Any, S]): Record <:< S = sup 158 | implicit override def restFind[L1, V1]( 159 | implicit distinct: L1 !:= Any, 160 | find: Find[L1, V1, Record]): Find[L1, V1, Rest] = 161 | find 162 | implicit override def restNotFound[L1]( 163 | implicit distinct: L1 !:= Any, 164 | absent: Absent[L1, Record]): NotFound[L1, Rest] = 165 | absent.asInstanceOf[NotFound[L1, Rest]] 166 | implicit override def restFound[L1, VL, VU >: VL]( 167 | implicit distinct: L1 !:= Any, 168 | found: Found[L1, VL, VU, Record]): Found[L1, VL, VU, Rest] { type Value = found.Value } = 169 | found 170 | implicit override def reconstructWitness: (Field[Any, Any] with Record <:< Record) = 171 | implicitly[Field[Any, Any] with Record <:< Record] 172 | implicit override def distinct[L1](implicit present: Select[L1, Record]): Any !:= L1 = !:=.unsafeMake[Any, L1] 173 | override def toString: String = "Record.NotFound" 174 | } 175 | def unsafeMake[L, R <: Record]: NotFound[L, R] = singleton.asInstanceOf[NotFound[L, R]] 176 | def empty[L]: NotFound[L, Record] = singleton.asInstanceOf[NotFound[L, Record]] 177 | def distinct[L1, L2, V2](implicit witness: L1 !:= L2): NotFound[L1, Field[L2, V2]] = 178 | singleton.asInstanceOf[NotFound[L1, Field[L2, V2]]] 179 | implicit def absent[L, R <: Record](implicit absent: Absent[L, R]): NotFound[L, R] = 180 | absent.asInstanceOf[NotFound[L, R]] 181 | def make[L, R1 <: Record, R2 <: Record](absent1: Absent[L, R1], absent2: Absent[L, R2]): NotFound[L, R1 with R2] = 182 | unsafeMake[L, R1 with R2] 183 | } 184 | 185 | final class AddSyntax[L, R <: Record](val record: R) extends AnyVal { 186 | def apply[V](value: V)(implicit tag: Tag[L], find: FindAny[L, R]): find.Rest with Field[L, V] = 187 | record match { 188 | case Impl(fields) => Impl(fields + (tag.tag -> value)).asInstanceOf[find.Rest with Field[L, V]] 189 | } 190 | } 191 | 192 | final class UpdateSyntax[L, R <: Record](val record: R) extends AnyVal { 193 | def apply[V1, V2](f: V1 => V2)(implicit tag: Tag[L], found: FoundSub[L, V1, R]): found.Rest with Field[L, V2] = { 194 | import found.valueWitness 195 | record.add[L](f(record.get[L])) 196 | } 197 | } 198 | 199 | final class UpdateMonoSyntax[L, R <: Record](val record: R) extends AnyVal { 200 | def apply[V](f: V => V)(implicit tag: Tag[L], found: Found[L, V, V, R]): R = { 201 | import found.reconstructWitness 202 | record.update[L](f) 203 | } 204 | } 205 | 206 | final class FieldSyntax[L, V, R <: Record](val record: R) extends AnyVal { 207 | def value(implicit tag: Tag[L]): V = 208 | record match { 209 | case Impl(fields) => fields(tag.tag).asInstanceOf[V] 210 | } 211 | def map[V1](f: V => V1)(implicit tag: Tag[L], found: FoundSub[L, V, R]): found.Rest with Field[L, V1] = 212 | record.update[L](f) 213 | def mapMono(f: V => V)(implicit tag: Tag[L], found: Found[L, V, V, R]): R = 214 | record.updateMono[L](f) 215 | def remove(implicit tag: Tag[L], find: FindAny[L, R]): find.Rest = 216 | record.remove[L] 217 | } 218 | 219 | implicit final class Syntax[R <: Record](val record: R) extends AnyVal { 220 | def get[L](implicit tag: Tag[L], select: Select[L, R]): select.Value = 221 | record match { 222 | case Impl(fields) => fields(tag.tag).asInstanceOf[select.Value] 223 | } 224 | def add[L]: AddSyntax[L, R] = new AddSyntax[L, R](record) 225 | def remove[L](implicit tag: Tag[L], find: FindAny[L, R]): find.Rest = 226 | record match { 227 | case Impl(fields) => Impl(fields - tag.tag).asInstanceOf[find.Rest] 228 | } 229 | def update[L]: UpdateSyntax[L, R] = new UpdateSyntax[L, R](record) 230 | def updateMono[L]: UpdateMonoSyntax[L, R] = new UpdateMonoSyntax[L, R](record) 231 | def field[L](implicit tag: Tag[L], select: Select[L, R]): FieldSyntax[L, select.Value, R] = 232 | new FieldSyntax[L, select.Value, R](record) 233 | } 234 | 235 | val empty: Record = Impl(Map.empty) 236 | } 237 | -------------------------------------------------------------------------------- /zio-interop-cats/src/main/scala/com/github/mvv/sager/zio/interop/catz/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.mvv.sager.zio.interop 2 | 3 | import _root_.zio.interop.{ 4 | CatsEffectZManagedInstances, 5 | CatsMtlPlatform, 6 | CatsPlatform, 7 | CatsZManagedInstances, 8 | CatsZManagedSyntax, 9 | CatsZioInstances 10 | } 11 | import cats.data.State 12 | import cats.{MonadError, Show, StackSafeMonad} 13 | import cats.effect.kernel.{ 14 | Async, 15 | Concurrent, 16 | Cont, 17 | Deferred, 18 | Fiber => CFiber, 19 | GenConcurrent, 20 | GenTemporal, 21 | Outcome, 22 | Poll, 23 | Ref => CRef, 24 | Sync, 25 | Temporal, 26 | Unique 27 | } 28 | import cats.effect.{IO => CIO, LiftIO} 29 | import cats.effect.unsafe.IORuntime 30 | import com.github.mvv.sager.zio.blocking.{effectBlocking, effectBlockingInterrupt, Blocking} 31 | import com.github.mvv.sager.zio.clock.{currentTime, nanoTime, Clock} 32 | import com.github.mvv.sager.zio.console.Console 33 | import com.github.mvv.sager.zio.{clock, console, SEnv, SagerApp, SagerRuntime} 34 | import zio.duration.Duration 35 | import zio.{Cause, ERef, Exit, Fiber, FiberFailure, IO, Promise, RIO, Runtime, Task, ZIO, ZRef} 36 | 37 | import java.util.concurrent.atomic.AtomicBoolean 38 | import scala.concurrent.duration.{FiniteDuration, MILLISECONDS, NANOSECONDS} 39 | import scala.concurrent.{ExecutionContext, Future} 40 | 41 | object catz extends SagerCatsEffectPlatform { 42 | object core extends CatsPlatform 43 | object mtl extends CatsMtlPlatform 44 | 45 | object implicits { 46 | implicit val rts: Runtime[Clock with Blocking] = SagerRuntime.default 47 | } 48 | } 49 | 50 | abstract class SagerCatsEffectInstances extends CatsZioInstances { 51 | implicit final def liftIOInstance[R](implicit runtime: IORuntime): LiftIO[RIO[R, *]] = 52 | new ZioLiftIO 53 | 54 | implicit final def asyncInstance[R <: Clock with Blocking]: Async[RIO[R, *]] = 55 | asyncInstance0.asInstanceOf[Async[RIO[R, *]]] 56 | 57 | implicit final def temporalInstance[R <: Clock, E]: GenTemporal[ZIO[R, E, *], E] = 58 | temporalInstance0.asInstanceOf[GenTemporal[ZIO[R, E, *], E]] 59 | 60 | implicit final def concurrentInstance[R, E]: GenConcurrent[ZIO[R, E, *], E] = 61 | concurrentInstance0.asInstanceOf[GenConcurrent[ZIO[R, E, *], E]] 62 | 63 | implicit final def asyncRuntimeInstance[E](implicit runtime: Runtime[Clock with Blocking]): Async[Task] = 64 | new ZioRuntimeAsync 65 | 66 | implicit final def temporalRuntimeInstance[E](implicit runtime: Runtime[Clock]): GenTemporal[IO[E, *], E] = 67 | new ZioRuntimeTemporal[E] 68 | 69 | private[this] val asyncInstance0: Async[RIO[Clock with Blocking, *]] = 70 | new ZioAsync 71 | 72 | private[this] val temporalInstance0: Temporal[RIO[Clock, *]] = 73 | new ZioTemporal 74 | 75 | private[this] val concurrentInstance0: Concurrent[Task] = 76 | new ZioConcurrent[Any, Throwable] 77 | } 78 | 79 | object SagerCatsConsole { 80 | def putStr[A](a: A)(implicit ev: Show[A]): ZIO[Console, Nothing, Unit] = 81 | console.putStr(ev.show(a)).orDie 82 | def putStrLn[A](a: A)(implicit ev: Show[A]): ZIO[Console, Nothing, Unit] = 83 | console.putStrLn(ev.show(a)).orDie 84 | } 85 | 86 | abstract class SagerCatsEffectPlatform 87 | extends SagerCatsEffectInstances 88 | with CatsEffectZManagedInstances 89 | with CatsZManagedInstances 90 | with CatsZManagedSyntax { 91 | 92 | trait CatsApp extends SagerApp { 93 | implicit val runtime: Runtime[SEnv] = this 94 | } 95 | 96 | val console: SagerCatsConsole.type = SagerCatsConsole 97 | } 98 | 99 | private class ZioLiftIO[R](implicit runtime: IORuntime) extends LiftIO[RIO[R, *]] { 100 | final override def liftIO[A](ioa: CIO[A]): RIO[R, A] = 101 | ZIO.effectAsync(k => ioa.unsafeRunAsync(k.compose(ZIO.fromEither(_)))) 102 | } 103 | 104 | private class ZioMonadError[R, E] extends MonadError[ZIO[R, E, *], E] with StackSafeMonad[ZIO[R, E, *]] { 105 | type F[A] = ZIO[R, E, A] 106 | 107 | final override def pure[A](a: A): F[A] = 108 | ZIO.succeed(a) 109 | 110 | final override def map[A, B](fa: F[A])(f: A => B): F[B] = 111 | fa.map(f) 112 | 113 | final override def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = 114 | fa.flatMap(f) 115 | 116 | final override def flatTap[A, B](fa: F[A])(f: A => F[B]): F[A] = 117 | fa.tap(f) 118 | 119 | final override def widen[A, B >: A](fa: F[A]): F[B] = 120 | fa 121 | 122 | final override def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = 123 | fa.zipWith(fb)(f) 124 | 125 | final override def as[A, B](fa: F[A], b: B): F[B] = 126 | fa.as(b) 127 | 128 | final override def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = 129 | ZIO.when(cond)(f) 130 | 131 | final override def unit: F[Unit] = 132 | ZIO.unit 133 | 134 | final override def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] = 135 | fa.catchAll(f) 136 | 137 | final override def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] = 138 | fa.catchSome(pf) 139 | 140 | final override def raiseError[A](e: E): F[A] = 141 | ZIO.fail(e) 142 | 143 | final override def attempt[A](fa: F[A]): F[Either[E, A]] = 144 | fa.either 145 | 146 | final override def adaptError[A](fa: F[A])(pf: PartialFunction[E, E]): F[A] = 147 | fa.mapError(pf.orElse { case error => error }) 148 | } 149 | 150 | final private class ZioRef[R, E, A](ref: ERef[E, A]) extends CRef[ZIO[R, E, *], A] { 151 | type F[T] = ZIO[R, E, T] 152 | 153 | override def access: F[(A, A => F[Boolean])] = 154 | get.map { current => 155 | val called = new AtomicBoolean(false) 156 | def setter(a: A): F[Boolean] = 157 | ZIO.effectSuspendTotal { 158 | if (called.getAndSet(true)) { 159 | ZIO.succeed(false) 160 | } else { 161 | ref.modify { updated => 162 | if (current == updated) (true, a) 163 | else (false, updated) 164 | } 165 | } 166 | } 167 | 168 | (current, setter) 169 | } 170 | 171 | override def tryUpdate(f: A => A): F[Boolean] = 172 | update(f).as(true) 173 | 174 | override def tryModify[B](f: A => (A, B)): F[Option[B]] = 175 | modify(f).asSome 176 | 177 | override def update(f: A => A): F[Unit] = 178 | ref.update(f) 179 | 180 | override def modify[B](f: A => (A, B)): F[B] = 181 | ref.modify(f(_).swap) 182 | 183 | override def tryModifyState[B](state: State[A, B]): F[Option[B]] = 184 | modifyState(state).asSome 185 | 186 | override def modifyState[B](state: State[A, B]): F[B] = 187 | modify(state.run(_).value) 188 | 189 | override def set(a: A): F[Unit] = 190 | ref.set(a) 191 | 192 | override def get: F[A] = 193 | ref.get 194 | } 195 | 196 | final private class ZioDeferred[R, E, A](promise: Promise[E, A]) extends Deferred[ZIO[R, E, *], A] { 197 | type F[T] = ZIO[R, E, T] 198 | 199 | override val get: F[A] = 200 | promise.await 201 | 202 | override def complete(a: A): F[Boolean] = 203 | promise.succeed(a) 204 | 205 | override val tryGet: F[Option[A]] = 206 | promise.isDone.flatMap { 207 | case true => get.asSome 208 | case false => ZIO.none 209 | } 210 | } 211 | 212 | private class ZioConcurrent[R, E] extends ZioMonadError[R, E] with GenConcurrent[ZIO[R, E, *], E] { 213 | import ZioConcurrent._ 214 | 215 | private def toPoll(restore: ZIO.InterruptStatusRestore) = 216 | new Poll[ZIO[R, E, *]] { 217 | override def apply[T](fa: ZIO[R, E, T]): ZIO[R, E, T] = restore(fa) 218 | } 219 | 220 | private def toFiber[A](fiber: Fiber[E, A]) = 221 | new CFiber[F, E, A] { 222 | final override val cancel: F[Unit] = fiber.interrupt.unit 223 | final override val join: F[Outcome[F, E, A]] = fiber.await.map(toOutcome) 224 | } 225 | 226 | private def fiberFailure(error: E) = 227 | FiberFailure(Cause.fail(error)) 228 | 229 | override def ref[A](a: A): F[CRef[F, A]] = 230 | ZRef.make(a).map(new ZioRef(_)) 231 | 232 | override def deferred[A]: F[Deferred[F, A]] = 233 | Promise.make[E, A].map(new ZioDeferred(_)) 234 | 235 | final override def start[A](fa: F[A]): F[CFiber[F, E, A]] = 236 | fa.interruptible.forkDaemon.map(toFiber) 237 | 238 | override def never[A]: F[A] = 239 | ZIO.never 240 | 241 | final override def cede: F[Unit] = 242 | ZIO.yieldNow 243 | 244 | final override def forceR[A, B](fa: F[A])(fb: F[B]): F[B] = 245 | fa.foldCauseM(cause => if (cause.interrupted) ZIO.halt(cause) else fb, _ => fb) 246 | 247 | final override def uncancelable[A](body: Poll[F] => F[A]): F[A] = 248 | ZIO.uninterruptibleMask(body.compose(toPoll)) 249 | 250 | final override def canceled: F[Unit] = 251 | ZIO.interrupt 252 | 253 | final override def onCancel[A](fa: F[A], fin: F[Unit]): F[A] = 254 | fa.onError(cause => fin.orDieWith(fiberFailure).unless(cause.failed)) 255 | 256 | final override def memoize[A](fa: F[A]): F[F[A]] = 257 | fa.memoize 258 | 259 | final override def racePair[A, B](fa: F[A], fb: F[B]) = 260 | (fa.interruptible raceWith fb.interruptible)( 261 | (exit, fiber) => ZIO.succeed(Left((toOutcome(exit), toFiber(fiber)))), 262 | (exit, fiber) => ZIO.succeed(Right((toFiber(fiber), toOutcome(exit)))) 263 | ) 264 | 265 | final override def both[A, B](fa: F[A], fb: F[B]): F[(A, B)] = 266 | fa.interruptible zipPar fb.interruptible 267 | 268 | final override def guarantee[A](fa: F[A], fin: F[Unit]): F[A] = 269 | fa.ensuring(fin.orDieWith(fiberFailure)) 270 | 271 | final override def bracket[A, B](acquire: F[A])(use: A => F[B])(release: A => F[Unit]): F[B] = 272 | acquire.bracket(release.andThen(_.orDieWith(fiberFailure)), use) 273 | 274 | override val unique: F[Unique.Token] = 275 | ZIO.effectTotal(new Unique.Token) 276 | } 277 | 278 | private object ZioConcurrent { 279 | @inline def toOutcome[R, E, A](exit: Exit[E, A]): Outcome[ZIO[R, E, *], E, A] = 280 | exit match { 281 | case Exit.Success(value) => 282 | Outcome.Succeeded[ZIO[R, E, *], E, A](ZIO.succeed(value)) 283 | case Exit.Failure(cause) if cause.interrupted => 284 | Outcome.Canceled[ZIO[R, E, *], E, A]() 285 | case Exit.Failure(cause) => 286 | cause.failureOrCause match { 287 | case Left(error) => Outcome.Errored[ZIO[R, E, *], E, A](error) 288 | case Right(cause) => Outcome.Succeeded[ZIO[R, E, *], E, A](ZIO.halt(cause)) 289 | } 290 | } 291 | } 292 | 293 | private class ZioTemporal[R <: Clock, E] extends ZioConcurrent[R, E] with GenTemporal[ZIO[R, E, *], E] { 294 | 295 | final override def sleep(time: FiniteDuration): F[Unit] = 296 | clock.sleep(Duration.fromScala(time)) 297 | 298 | final override val monotonic: F[FiniteDuration] = 299 | nanoTime.map(FiniteDuration(_, NANOSECONDS)) 300 | 301 | final override val realTime: F[FiniteDuration] = 302 | currentTime(MILLISECONDS).map(FiniteDuration(_, MILLISECONDS)) 303 | } 304 | 305 | private class ZioAsync[R <: Clock with Blocking] extends ZioTemporal[R, Throwable] with Async[RIO[R, *]] { 306 | 307 | final override def evalOn[A](fa: F[A], ec: ExecutionContext): F[A] = 308 | fa.on(ec) 309 | 310 | final override val executionContext: F[ExecutionContext] = 311 | ZIO.executor.map(_.asEC) 312 | 313 | final override val unique: F[Unique.Token] = 314 | ZIO.effectTotal(new Unique.Token) 315 | 316 | final override def cont[K, Q](body: Cont[F, K, Q]): F[Q] = 317 | Async.defaultCont(body)(this) 318 | 319 | final override def suspend[A](hint: Sync.Type)(thunk: => A): F[A] = 320 | hint match { 321 | case Sync.Type.Delay => ZIO.effect(thunk) 322 | case Sync.Type.Blocking => effectBlocking(thunk) 323 | case Sync.Type.InterruptibleOnce | Sync.Type.InterruptibleMany => effectBlockingInterrupt(thunk) 324 | } 325 | 326 | final override def delay[A](thunk: => A): F[A] = 327 | ZIO.effect(thunk) 328 | 329 | final override def defer[A](thunk: => F[A]): F[A] = 330 | ZIO.effectSuspend(thunk) 331 | 332 | final override def blocking[A](thunk: => A): F[A] = 333 | effectBlocking(thunk) 334 | 335 | final override def interruptible[A](many: Boolean)(thunk: => A): F[A] = 336 | effectBlockingInterrupt(thunk) 337 | 338 | final override def async[A](k: (Either[Throwable, A] => Unit) => F[Option[F[Unit]]]): F[A] = 339 | Promise.make[Nothing, Unit].flatMap { promise => 340 | ZIO.effectAsyncM { register => 341 | k(either => register(promise.await *> ZIO.fromEither(either))) *> promise.succeed(()) 342 | } 343 | } 344 | 345 | final override def async_[A](k: (Either[Throwable, A] => Unit) => Unit): F[A] = 346 | ZIO.effectAsync(register => k(register.compose(fromEither))) 347 | 348 | final override def fromFuture[A](fut: F[Future[A]]): F[A] = 349 | fut.flatMap(f => ZIO.fromFuture(_ => f)) 350 | 351 | final override def never[A]: F[A] = 352 | ZIO.never 353 | } 354 | 355 | private class ZioRuntimeTemporal[E](implicit runtime: Runtime[Clock]) 356 | extends ZioConcurrent[Any, E] 357 | with GenTemporal[IO[E, *], E] { 358 | 359 | private[this] val underlying: GenTemporal[ZIO[Clock, E, *], E] = new ZioTemporal[Clock, E] 360 | private[this] val clock: Clock = runtime.environment 361 | 362 | final override def sleep(time: FiniteDuration): F[Unit] = 363 | underlying.sleep(time).provide(clock) 364 | 365 | final override val monotonic: F[FiniteDuration] = 366 | underlying.monotonic.provide(clock) 367 | 368 | final override val realTime: F[FiniteDuration] = 369 | underlying.realTime.provide(clock) 370 | } 371 | 372 | private class ZioRuntimeAsync(implicit runtime: Runtime[Clock with Blocking]) 373 | extends ZioRuntimeTemporal[Throwable] 374 | with Async[Task] { 375 | 376 | private[this] val underlying: Async[RIO[Clock with Blocking, *]] = new ZioAsync[Clock with Blocking] 377 | private[this] val environment: Clock with Blocking = runtime.environment 378 | 379 | final override def evalOn[A](fa: F[A], ec: ExecutionContext): F[A] = 380 | underlying.evalOn(fa, ec).provide(environment) 381 | 382 | final override val executionContext: F[ExecutionContext] = 383 | underlying.executionContext.provide(environment) 384 | 385 | final override val unique: F[Unique.Token] = 386 | underlying.unique.provide(environment) 387 | 388 | final override def cont[K, Q](body: Cont[F, K, Q]): F[Q] = 389 | Async.defaultCont(body)(this) 390 | 391 | final override def suspend[A](hint: Sync.Type)(thunk: => A): F[A] = 392 | underlying.suspend(hint)(thunk).provide(environment) 393 | 394 | final override def delay[A](thunk: => A): F[A] = 395 | underlying.delay(thunk).provide(environment) 396 | 397 | final override def defer[A](thunk: => F[A]): F[A] = 398 | underlying.defer(thunk).provide(environment) 399 | 400 | final override def blocking[A](thunk: => A): F[A] = 401 | underlying.blocking(thunk).provide(environment) 402 | 403 | final override def interruptible[A](many: Boolean)(thunk: => A): F[A] = 404 | underlying.interruptible(many)(thunk).provide(environment) 405 | 406 | final override def async[A](k: (Either[Throwable, A] => Unit) => F[Option[F[Unit]]]): F[A] = 407 | underlying.async(k).provide(environment) 408 | 409 | final override def async_[A](k: (Either[Throwable, A] => Unit) => Unit): F[A] = 410 | underlying.async_(k).provide(environment) 411 | 412 | final override def fromFuture[A](fut: F[Future[A]]): F[A] = 413 | underlying.fromFuture(fut).provide(environment) 414 | 415 | final override def never[A]: F[A] = 416 | ZIO.never 417 | } 418 | --------------------------------------------------------------------------------