├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── README.md ├── .github └── workflows │ ├── release.yml │ └── scala.yml └── shared └── src ├── main ├── scala-3 │ └── implicitbox │ │ └── Not.scala ├── scala-2 │ └── implicitbox │ │ └── Not.scala └── scala │ └── implicitbox │ └── Priority.scala └── test └── scala └── implicitbox ├── MyType.scala ├── NotSuite.scala └── PrioritySuite.scala /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.5 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test-output/ 2 | TODO 3 | .tags* 4 | .idea* 5 | *.log 6 | .DS_Store 7 | target/ 8 | /.lib/ 9 | project/target/ 10 | *.class 11 | *.log 12 | *.iml 13 | 14 | # sbt specific 15 | .cache/ 16 | .history/ 17 | .lib/ 18 | dist/* 19 | target/ 20 | lib_managed/ 21 | src_managed/ 22 | project/boot/ 23 | project/plugins/project/ 24 | 25 | # Scala-IDE specific 26 | .scala_dependencies 27 | .worksheet 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Implicit Box 2 | 3 | Scala micro-library providing utilities for capturing implicits. 4 | 5 | ### Compiler support 6 | 7 | - Scala 2.12, 2.13, 3.0.0 8 | - [Scala.js](https://www.scala-js.org/) 1.3 9 | 10 | ## Usage in SBT 11 | 12 | For `build.sbt` (use the `%%%` operator for Scala.js): 13 | 14 | ```scala 15 | // use the %%% operator for Scala.js 16 | libraryDependencies += "io.monix" %% "implicitbox" % "0.3.4" 17 | ``` 18 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") 2 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") 3 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") 4 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") 5 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.3") 6 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0") 7 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.7") 8 | addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") 9 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [master, main] 5 | tags: ["*"] 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v2.3.4 11 | with: 12 | fetch-depth: 0 13 | - uses: olafurpg/setup-scala@v13 14 | - uses: olafurpg/setup-gpg@v3 15 | - run: sbt ci-release 16 | env: 17 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 18 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 19 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 20 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 21 | -------------------------------------------------------------------------------- /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up JDK 8 12 | uses: olafurpg/setup-scala@v13 13 | with: 14 | java-version: 8 15 | - name: Cache Coursier 16 | uses: actions/cache@v1 17 | with: 18 | path: ~/.cache/coursier 19 | key: sbt-coursier-cache 20 | - name: Cache SBT 21 | uses: actions/cache@v1 22 | with: 23 | path: ~/.sbt 24 | key: sbt-${{ hashFiles('**/build.sbt') }} 25 | - name: Run test 26 | run: sbt ci-all 27 | -------------------------------------------------------------------------------- /shared/src/main/scala-3/implicitbox/Not.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2021 by The Monix Project Developers. 3 | * See the project homepage at: https://monix.io 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package implicitbox 19 | 20 | type Not[+A] = scala.util.NotGiven[A] 21 | val Not = scala.util.NotGiven 22 | -------------------------------------------------------------------------------- /shared/src/test/scala/implicitbox/MyType.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2021 by The Monix Project Developers. 3 | * See the project homepage at: https://monix.io 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package implicitbox 19 | 20 | class MyType[A] { 21 | def upcast[B >: A]: MyType[B] = 22 | this.asInstanceOf[MyType[B]] 23 | } 24 | 25 | object MyType { 26 | /** For strings. */ 27 | implicit val forString: MyType[String] = 28 | new MyType[String] 29 | 30 | def forAny[A]: MyType[A] = 31 | forAnyInst.asInstanceOf[MyType[A]] 32 | 33 | object Implicits { 34 | implicit def forAny[A](implicit ev: Not[MyType[A]]): MyType[A] = 35 | MyType.forAny 36 | } 37 | 38 | private[this] val forAnyInst: MyType[Any] = 39 | new MyType[Any] 40 | } 41 | -------------------------------------------------------------------------------- /shared/src/test/scala/implicitbox/NotSuite.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2021 by The Monix Project Developers. 3 | * See the project homepage at: https://monix.io 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package implicitbox 19 | 20 | import minitest.SimpleTestSuite 21 | 22 | object NotSuite extends SimpleTestSuite { 23 | test("default implicit resolution") { 24 | import MyType.Implicits.forAny 25 | 26 | val forString = implicitly[MyType[String]] 27 | val forInt = implicitly[MyType[Int]] 28 | 29 | assertEquals(forString, MyType.forString) 30 | assertEquals(forInt, MyType.forAny[Int]) 31 | } 32 | 33 | test("should resolve") { 34 | val ev = implicitly[Not[MyType[Int]]] 35 | assert(ev.isInstanceOf[Not[_]]) 36 | } 37 | 38 | test("should fail if implicit is in scope") { 39 | assertDoesNotCompile("implicitly[Not[MyType[String]]]") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /shared/src/main/scala-2/implicitbox/Not.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2021 by The Monix Project Developers. 3 | * See the project homepage at: https://monix.io 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package implicitbox 19 | 20 | import scala.annotation.implicitNotFound 21 | 22 | /** 23 | * Type for proving that an implicit parameter does 24 | * not exist in scope. 25 | * 26 | * ==Credits== 27 | * 28 | * Code was copied from [[https://github.com/milessabin/shapeless/ Shapeless]], 29 | * authored by Miles Sabin. 30 | */ 31 | @implicitNotFound("An implicit for ${A} exists in scope, cannot prove its absence") 32 | sealed trait Not[A] 33 | 34 | object Not { 35 | trait Impl[A] 36 | object Impl { 37 | /** This results in ambiguous implicits if there is implicit evidence of `T` */ 38 | implicit def amb1[T](implicit ev: T): Impl[T] = null 39 | implicit def amb2[T]: Impl[T] = null 40 | } 41 | 42 | /** This always declares an instance of `Not` 43 | * 44 | * This instance will only be found when there is no evidence of `T` 45 | * */ 46 | implicit def not[T: Impl]: Not[T] = new Not[T] {} 47 | } 48 | -------------------------------------------------------------------------------- /shared/src/test/scala/implicitbox/PrioritySuite.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2021 by The Monix Project Developers. 3 | * See the project homepage at: https://monix.io 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package implicitbox 19 | 20 | import minitest.SimpleTestSuite 21 | 22 | object PrioritySuite extends SimpleTestSuite { 23 | test("fallback") { 24 | val ev = implicitly[Priority[MyType[Int], MyType[String]]] 25 | 26 | assert(ev.isFallback, "ev.isFallback") 27 | assert(!ev.isPreferred, "!ev.isPreferred") 28 | 29 | assertEquals(ev.getFallback, Some(MyType.forString)) 30 | assertEquals(ev.fold(x => x.upcast[Any])(x => x.upcast[Any]), MyType.forString) 31 | assertEquals(ev.join, MyType.forString) 32 | assertEquals(ev.getPreferred, None) 33 | assertEquals(ev.bimap(_ => "int")(_ => "string").toEither, Right("string")) 34 | } 35 | 36 | test("preferred") { 37 | import MyType.Implicits.forAny 38 | val ev = implicitly[Priority[MyType[Int], MyType[String]]] 39 | 40 | assert(!ev.isFallback, "!ev.isFallback") 41 | assert(ev.isPreferred, "ev.isPreferred") 42 | 43 | assertEquals(ev.getPreferred, Some(MyType.forAny)) 44 | assertEquals(ev.getFallback, None) 45 | 46 | assertEquals(ev.fold(x => x.upcast[Any])(x => x.upcast[Any]), MyType.forAny) 47 | assertEquals(ev.join, MyType.forAny) 48 | assertEquals(ev.bimap(_ => "int")(_ => "string").toEither, Left("int")) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /shared/src/main/scala/implicitbox/Priority.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2021 by The Monix Project Developers. 3 | * See the project homepage at: https://monix.io 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package implicitbox 19 | 20 | import scala.annotation.implicitNotFound 21 | 22 | /** 23 | * Priority is a type class for prioritized implicit search. 24 | * 25 | * This type class will attempt to provide an implicit instance of `P` 26 | * (the preferred type). If that type is not available it will 27 | * fallback to `F` (the fallback type). If neither type is available 28 | * then a `Priority[P, F]` instance will not be available. 29 | * 30 | * This type can be useful for problems where multiple algorithms can 31 | * be used, depending on the type classes available. 32 | * 33 | * ==Credits== 34 | * 35 | * `Priority` is a type copied from [[https://github.com/typelevel/algebra/ Algebra]], 36 | * authored by Erik Osheim. 37 | */ 38 | @implicitNotFound("No implicit exists in scope for either ${P} or for ${F}") 39 | sealed trait Priority[+P, +F] { 40 | import Priority.{Fallback, Preferred} 41 | 42 | def fold[B](f1: P => B)(f2: F => B): B = 43 | this match { 44 | case Preferred(x) => f1(x) 45 | case Fallback(y) => f2(y) 46 | } 47 | 48 | def join[U >: P with F]: U = 49 | fold(_.asInstanceOf[U])(_.asInstanceOf[U]) 50 | 51 | def bimap[P2, F2](f1: P => P2)(f2: F => F2): Priority[P2, F2] = 52 | this match { 53 | case Preferred(x) => Preferred(f1(x)) 54 | case Fallback(y) => Fallback(f2(y)) 55 | } 56 | 57 | def toEither: Either[P, F] = 58 | fold[Either[P, F]](p => Left(p))(f => Right(f)) 59 | 60 | def isPreferred: Boolean = 61 | fold(_ => true)(_ => false) 62 | 63 | def isFallback: Boolean = 64 | fold(_ => false)(_ => true) 65 | 66 | def getPreferred: Option[P] = 67 | fold[Option[P]](p => Some(p))(_ => None) 68 | 69 | def getFallback: Option[F] = 70 | fold[Option[F]](_ => None)(f => Some(f)) 71 | } 72 | 73 | object Priority extends FindPreferred { 74 | final case class Preferred[P](get: P) extends Priority[P, Nothing] 75 | final case class Fallback[F](get: F) extends Priority[Nothing, F] 76 | 77 | def apply[P, F](implicit ev: Priority[P, F]): Priority[P, F] = ev 78 | } 79 | 80 | private[implicitbox] trait FindPreferred extends FindFallback { 81 | implicit def preferred[P](implicit ev: P): Priority[P, Nothing] = 82 | Priority.Preferred(ev) 83 | } 84 | 85 | private[implicitbox] trait FindFallback { 86 | implicit def fallback[F](implicit ev: F): Priority[Nothing, F] = 87 | Priority.Fallback(ev) 88 | } 89 | --------------------------------------------------------------------------------