Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add integration for cats typeclasses #64

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ val Scala212 = "2.12.15"
val Scala213 = "2.13.8"
val Scala3 = "3.1.3"

val CatsVersion = "2.8.0"
val CirceVersionV0_14 = "0.14.3"
val PureConfigV0_17 = "0.17.1"
val ScalaTestVersion = "3.2.14"
val Shapeless2xVersion = "2.3.9"
val Shapeless3xVersion = "3.2.0"
val CatsVersion = "2.9.0"
val CirceVersionV0_14 = "0.14.3"
val PureConfigV0_17 = "0.17.1"
val ScalaTestVersion = "3.2.14"
val ScalaTestPlusVersion = "3.2.14.0"
val Shapeless2xVersion = "2.3.9"
val Shapeless3xVersion = "3.2.0"

// ---------------------------------------------------------------------------

Expand Down Expand Up @@ -216,8 +217,10 @@ lazy val root = project.in(file("."))
.enablePlugins(ScalaUnidocPlugin)
.disablePlugins(MimaPlugin)
.aggregate(
coreJVM,
coreJS,
coreJVM,
coreJS,
integrationCatsV2JVM,
integrationCatsV2JS,
integrationCirceV014JVM,
integrationCirceV014JS,
integrationPureConfigV017JVM,
Expand Down Expand Up @@ -253,6 +256,7 @@ lazy val site = project.in(file("site"))
.settings(sharedSettings)
.settings(doNotPublishArtifact)
.dependsOn(coreJVM)
.dependsOn(integrationCatsV2JVM)
.dependsOn(integrationCirceV014JVM)
.dependsOn(integrationPureConfigV017JVM)
.settings {
Expand Down Expand Up @@ -377,6 +381,29 @@ def integrationSharedSettings(other: Setting[_]*) =
}
}

// ---
def catsSharedSettings(ver: String) =
integrationSharedSettings(
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % ver,
"org.scalatestplus" %%% "scalacheck-1-17" % ScalaTestPlusVersion % Test,
)
)

lazy val integrationCatsV2 = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Full)
.in(file("integration-cats/v2"))
.jsConfigure(_.disablePlugins(MimaPlugin))
.configureCross(defaultCrossProjectConfiguration(JSPlatform, JVMPlatform))
.dependsOn(core)
.settings(catsSharedSettings(CatsVersion))
.settings(name := "newtypes-cats-v2")
.jvmSettings(mimaSettings("newtypes-cats-v2"))

lazy val integrationCatsV2JVM = integrationCatsV2.jvm
lazy val integrationCatsV2JS = integrationCatsV2.js

// ---
def circeSharedSettings(ver: String) =
integrationSharedSettings(
libraryDependencies ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2021-2022 the Newtypes contributors.
* See the project homepage at: https://newtypes.monix.io/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package monix.newtypes
package integrations

import cats.{Eq, Hash, Order, Show}

trait DerivedCatsInstances extends DerivedCatsEq with DerivedCatsHash with DerivedCatsOrder with DerivedCatsShow

trait DerivedCatsEq {
implicit def catsEq[T, S](implicit extractor: HasExtractor.Aux[T, S], eqs: Eq[S]): Eq[T] = new Eq[T] {
override def eqv(x: T, y: T): Boolean = eqs.eqv(extractor.extract(x), extractor.extract(y))
}
}

trait DerivedCatsHash { self: DerivedCatsEq =>
implicit def catsHash[T, S](implicit extractor: HasExtractor.Aux[T, S], hashS: Hash[S]): Hash[T] = new Hash[T] {
override def eqv(x: T, y: T): Boolean = self.catsEq.eqv(x, y)
override def hash(x: T): Int = hashS.hash(extractor.extract(x))
}
}

trait DerivedCatsOrder {
def catsOrder[T, S](implicit extractor: HasExtractor.Aux[T, S], orderS: Order[S]): Order[T] = new Order[T] {
override def compare(x: T, y: T): Int = orderS.compare(extractor.extract(x), extractor.extract(y))
}
}

trait DerivedCatsShow {
implicit def catsShow[T, S](implicit extractor: HasExtractor.Aux[T, S], showS: Show[S]): Show[T] = new Show[T] {
override def show(t: T): String = showS.show(extractor.extract(t))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2021-2022 the Newtypes contributors.
* See the project homepage at: https://newtypes.monix.io/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package monix.newtypes
package integrations

import cats.{Eq, Hash, Order, Show}
import cats.syntax.all._
import monix.newtypes.NewtypeWrapped
import monix.newtypes.integrations.DerivedCatsInstancesSuite._
import org.scalacheck.Prop._
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks

class DerivedCatsInstancesSuite extends AnyFunSuite with ScalaCheckPropertyChecks {

test("an instance of Eq exists") {
implicitly[Eq[SomeNewtype]]
}
test("the Eq instances produce the same result") {
forAll { (x: Internal, y: Internal) =>
x.eqv(y) == SomeNewtype(x).eqv(SomeNewtype(y))
}
}

test("an instance of Hash exists") {
implicitly[Hash[SomeNewtype]]
}
test("the Hash instances produce the same result") {
forAll { (x: Internal) =>
x.hash == SomeNewtype(x).hash
}
}

test("an instance of Show exists") {
implicitly[Show[SomeNewtype]]
}
test("the Show instances produce the same result") {
forAll { (x: Internal) =>
x.show == SomeNewtype(x).show
}
}

test("an instance of Order exists") {
assert(SomeNewtype.catsOrder[SomeNewtype, Internal].isInstanceOf[Order[_]])
}
test("the Order instances produce the same result") {
forAll { (x: Internal, y: Internal) =>
x.compare(y) == SomeNewtype.catsOrder[SomeNewtype, Internal].compare(SomeNewtype(x), SomeNewtype(y))
}
}
}

object DerivedCatsInstancesSuite {
final type Internal = String

final type SomeNewtype = SomeNewtype.Type
object SomeNewtype extends NewtypeWrapped[Internal] with DerivedCatsInstances
}
37 changes: 37 additions & 0 deletions site/src/mdoc/docs/cats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
layout: docs
title: "Integration with Cats typeclasses (Eq, Show, Hash, Order)"
---

# Integration with Cats typeclasses (Eq, Show, Hash, Order)

Importing the [Cats](https://github.com/typelevel/cats) integration:

```scala
// For Cats version 2.x.x
libraryDependencies += "io.monix" %% "newtypes-cats-v2" % "@VERSION@"
```

Usage:

```scala mdoc:silent
import monix.newtypes._
import monix.newtypes.integrations.DerivedCatsInstances

type Email = Email.Type
object Email extends NewtypeValidated[String] with DerivedCatsInstances {
def apply(v: String): Either[BuildFailure[Type], Type] =
if (v.contains("@"))
Right(unsafeCoerce(v))
else
Left(BuildFailure("missing @"))
}
```

You can now use the syntax from cats:

```scala mdoc
import cats.syntax.all._

Email.unsafe("noreply@alexn.org").show
```
4 changes: 4 additions & 0 deletions site/src/resources/microsite/data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ options:
url: docs/core.html
menu_section: docs

- title: Cats Integration
url: docs/cats.html
menu_section: docs

- title: Circe JSON Integration
url: docs/circe.html
menu_section: docs
Expand Down