├── project ├── build.properties └── plugins.sbt ├── version.sbt ├── .gitignore ├── core └── src │ ├── main │ ├── scala-2.x │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ └── io.chrisdavenport.semigroups │ │ │ ├── Max.scala │ │ │ ├── Min.scala │ │ │ ├── Dual.scala │ │ │ ├── Last.scala │ │ │ ├── First.scala │ │ │ └── Intersect.scala │ ├── scala-3.x │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ └── io.chrisdavenport.semigroups │ │ │ ├── Max.scala │ │ │ ├── Min.scala │ │ │ ├── Dual.scala │ │ │ ├── Last.scala │ │ │ ├── First.scala │ │ │ └── Intersect.scala │ └── scala │ │ └── io │ │ └── chrisdavenport │ │ └── semigroups │ │ ├── IntersectInstances.scala │ │ ├── DualInstances.scala │ │ ├── FirstInstances.scala │ │ ├── LastInstances.scala │ │ ├── MinInstances.scala │ │ └── MaxInstances.scala │ └── test │ └── scala │ └── io │ └── chrisdavenport │ └── semigroups │ ├── MaxSuite.scala │ ├── MinSuite.scala │ ├── LastSuite.scala │ ├── FirstSuite.scala │ ├── SemigroupsArbitraries.scala │ ├── IntersectSuite.scala │ └── DualSuite.scala ├── .mergify.yml ├── .scalafmt.conf ├── README.md └── .github └── workflows ├── ci.yml └── clean.yml /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.5 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.2.1-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | # vim 4 | *.sw? 5 | 6 | # Ignore [ce]tags files 7 | tags 8 | .bloop 9 | .metals 10 | .bsp -------------------------------------------------------------------------------- /core/src/main/scala-2.x/src/main/scala/io.chrisdavenport.semigroups/Max.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | final case class Max[A](getMax: A) extends AnyVal 4 | 5 | object Max extends MaxInstances 6 | -------------------------------------------------------------------------------- /core/src/main/scala-2.x/src/main/scala/io.chrisdavenport.semigroups/Min.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | final case class Min[A](getMin: A) extends AnyVal 4 | 5 | object Min extends MinInstances 6 | -------------------------------------------------------------------------------- /core/src/main/scala-2.x/src/main/scala/io.chrisdavenport.semigroups/Dual.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | final case class Dual[A](getDual: A) extends AnyVal 4 | 5 | object Dual extends DualInstances 6 | -------------------------------------------------------------------------------- /core/src/main/scala-2.x/src/main/scala/io.chrisdavenport.semigroups/Last.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | final case class Last[A](getLast: A) extends AnyVal 4 | 5 | object Last extends LastInstances 6 | -------------------------------------------------------------------------------- /core/src/main/scala-2.x/src/main/scala/io.chrisdavenport.semigroups/First.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | final case class First[A](getFirst: A) extends AnyVal 4 | 5 | object First extends FirstInstances 6 | -------------------------------------------------------------------------------- /core/src/main/scala-2.x/src/main/scala/io.chrisdavenport.semigroups/Intersect.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | final case class Intersect[A](getIntersect: Set[A]) extends AnyVal 4 | 5 | object Intersect extends IntersectInstances 6 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatically merge scala-steward's PRs 3 | conditions: 4 | - author=scala-steward 5 | - status-success=Travis CI - Pull Request 6 | - body~=labels:.*semver-patch.* 7 | actions: 8 | merge: 9 | method: merge 10 | -------------------------------------------------------------------------------- /core/src/main/scala-3.x/src/main/scala/io.chrisdavenport.semigroups/Max.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | opaque type Max[A] = A 4 | 5 | object Max extends MaxInstances: 6 | def apply[A](getMax: A): Max[A] = getMax 7 | def unapply[A](arg: Max[A]): Option[A] = Some(arg.getMax) 8 | 9 | extension [A](m: Max[A]) 10 | def getMax: A = m 11 | -------------------------------------------------------------------------------- /core/src/main/scala-3.x/src/main/scala/io.chrisdavenport.semigroups/Min.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | opaque type Min[A] = A 4 | 5 | object Min extends MinInstances: 6 | def apply[A](getMin: A): Min[A] = getMin 7 | def unapply[A](arg: Min[A]): Option[A] = Some(arg.getMin) 8 | 9 | extension [A](m: Min[A]) 10 | def getMin: A = m 11 | -------------------------------------------------------------------------------- /core/src/main/scala-3.x/src/main/scala/io.chrisdavenport.semigroups/Dual.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | opaque type Dual[A] = A 4 | 5 | object Dual extends DualInstances: 6 | def apply[A](getDual: A): Dual[A] = getDual 7 | def unapply[A](arg: Dual[A]): Option[A] = Some(arg.getDual) 8 | 9 | extension [A](d: Dual[A]) 10 | def getDual: A = d 11 | -------------------------------------------------------------------------------- /core/src/main/scala-3.x/src/main/scala/io.chrisdavenport.semigroups/Last.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | opaque type Last[A] = A 4 | 5 | object Last extends LastInstances: 6 | def apply[A](getLast: A): Last[A] = getLast 7 | def unapply[A](arg: Last[A]): Option[A] = Some(arg.getLast) 8 | 9 | extension [A](l: Last[A]) 10 | def getLast: A = l 11 | -------------------------------------------------------------------------------- /core/src/main/scala-3.x/src/main/scala/io.chrisdavenport.semigroups/First.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | opaque type First[A] = A 4 | 5 | object First extends FirstInstances: 6 | def apply[A](getFirst: A): First[A] = getFirst 7 | def unapply[A](arg: First[A]): Option[A] = Some(arg.getFirst) 8 | 9 | extension [A](f: First[A]) 10 | def getFirst: A = f 11 | -------------------------------------------------------------------------------- /core/src/main/scala-3.x/src/main/scala/io.chrisdavenport.semigroups/Intersect.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | opaque type Intersect[A] = Set[A] 4 | 5 | object Intersect extends IntersectInstances: 6 | def apply[A](getIntersect: Set[A]): Intersect[A] = getIntersect 7 | def unapply[A](arg: Intersect[A]): Option[Set[A]] = Some(arg.getIntersect) 8 | 9 | extension [A](i: Intersect[A]) 10 | def getIntersect: Set[A] = i 11 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | # tune this file as appropriate to your style! see: 2 | # https://scalameta.org/scalafmt/docs/configuration.html 3 | 4 | version = 3.0.8 5 | 6 | maxColumn = 100 7 | 8 | continuationIndent.callSite = 2 9 | 10 | newlines { 11 | sometimesBeforeColonInMethodReturnType = false 12 | source=keep 13 | } 14 | 15 | align { 16 | arrowEnumeratorGenerator = false 17 | ifWhileOpenParen = false 18 | openParenCallSite = false 19 | openParenDefnSite = false 20 | 21 | tokens = ["%", "%%"] 22 | } 23 | 24 | docstrings.style = Asterisk 25 | 26 | rewrite { 27 | rules = [SortImports, RedundantBraces] 28 | redundantBraces.maxLines = 1 29 | } 30 | 31 | fileOverride { 32 | "glob:**/scala-3.x/src/main/scala/**" { 33 | runner.dialect = scala3 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # semigroups 2 | 3 | ## This library has moved to [another repository](https://github.com/typelevel/monoids). 4 | 5 | Set of Generic Semigroup Types and Accompanying Instances very useful for abstract programming. 6 | 7 | Exposes instances for 8 | 9 | - `Dual` inverts the combine operation. 10 | - `Max` exposes a Max that given an `Order` will return the maximum value. 11 | - `Min` exposes a Min that given an `Order` will returh the minimum value. 12 | 13 | ## Quick Start 14 | 15 | To use this project in an existing SBT project with Scala 2.12 or a later version, add the following dependencies to your 16 | `build.sbt` depending on your needs: 17 | 18 | ```scala 19 | libraryDependencies ++= Seq( 20 | "io.chrisdavenport" %% "semigroups" % "" 21 | ) 22 | ``` 23 | -------------------------------------------------------------------------------- /core/src/test/scala/io/chrisdavenport/semigroups/MaxSuite.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.kernel.laws.discipline._ 5 | import cats.laws.discipline._ 6 | import cats.syntax.all._ 7 | 8 | class MaxSuite extends munit.DisciplineSuite with SemigroupsArbitraries { 9 | checkAll("Max", OrderTests[Max[Int]].order) 10 | checkAll("Max", BoundedSemilatticeTests[Max[Int]].boundedSemilattice) 11 | checkAll("Max", MonadTests[Max].monad[Int, Int, Int]) 12 | checkAll( 13 | "Max", 14 | NonEmptyTraverseTests[Max].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] 15 | ) 16 | checkAll("Max", DistributiveTests[Max].distributive[Int, Int, Int, Option, Id]) 17 | 18 | test("show") { 19 | assertEquals(Max(true).show, "Max(true)") 20 | assertEquals(Max(false).show, "Max(false)") 21 | } 22 | 23 | test("returns Larger") { 24 | val first = Max(1) 25 | val second = Max(3) 26 | assertEquals(first |+| second, second) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/test/scala/io/chrisdavenport/semigroups/MinSuite.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.kernel.laws.discipline._ 5 | import cats.laws.discipline._ 6 | import cats.syntax.all._ 7 | 8 | class MinSuite extends munit.DisciplineSuite with SemigroupsArbitraries { 9 | checkAll("Min", OrderTests[Min[Int]].order) 10 | checkAll("Min", BoundedSemilatticeTests[Min[Int]].boundedSemilattice) 11 | checkAll("Min", MonadTests[Min].monad[Int, Int, Int]) 12 | checkAll( 13 | "Min", 14 | NonEmptyTraverseTests[Min].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] 15 | ) 16 | checkAll("Min", DistributiveTests[Min].distributive[Int, Int, Int, Option, Id]) 17 | 18 | test("show") { 19 | assertEquals(Min(true).show, "Min(true)") 20 | assertEquals(Min(false).show, "Min(false)") 21 | } 22 | 23 | test("returns Smaller") { 24 | val first = Min(1) 25 | val second = Min(3) 26 | assertEquals(first |+| second, first) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/test/scala/io/chrisdavenport/semigroups/LastSuite.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.kernel.laws.discipline._ 5 | import cats.laws.discipline._ 6 | import cats.syntax.all._ 7 | 8 | class LastSuite extends munit.DisciplineSuite with SemigroupsArbitraries { 9 | checkAll("Last", OrderTests[Last[Int]].order) 10 | checkAll("Last", BandTests[Last[Int]].band) 11 | checkAll("Last", MonadTests[Last].monad[Int, Int, Int]) 12 | checkAll( 13 | "Last", 14 | NonEmptyTraverseTests[Last].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] 15 | ) 16 | checkAll("Last", DistributiveTests[Last].distributive[Int, Int, Int, Option, Id]) 17 | 18 | test("show") { 19 | assertEquals(Last(true).show, "Last(true)") 20 | assertEquals(Last(false).show, "Last(false)") 21 | } 22 | 23 | test("returns Last") { 24 | val first = Last("Hello ") 25 | val second = Last("World") 26 | assertEquals(first |+| second, second) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/test/scala/io/chrisdavenport/semigroups/FirstSuite.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.kernel.laws.discipline._ 5 | import cats.laws.discipline._ 6 | import cats.syntax.all._ 7 | 8 | class FirstSuite extends munit.DisciplineSuite with SemigroupsArbitraries { 9 | checkAll("First", OrderTests[First[Int]].order) 10 | checkAll("First", BandTests[First[Int]].band) 11 | checkAll("First", MonadTests[First].monad[Int, Int, Int]) 12 | checkAll( 13 | "First", 14 | NonEmptyTraverseTests[First].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] 15 | ) 16 | checkAll("First", DistributiveTests[First].distributive[Int, Int, Int, Option, Id]) 17 | 18 | test("show") { 19 | assertEquals(First(true).show, "First(true)") 20 | assertEquals(First(false).show, "First(false)") 21 | } 22 | 23 | test("returns first") { 24 | val first = First("Hello ") 25 | val second = First("World") 26 | assertEquals(first |+| second, first) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/test/scala/io/chrisdavenport/semigroups/SemigroupsArbitraries.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import org.scalacheck._ 4 | 5 | trait SemigroupsArbitraries { 6 | implicit def functionArb[A, B: Arbitrary]: Arbitrary[A => B] = 7 | Arbitrary(Arbitrary.arbitrary[B].map(b => (_: A) => b)) 8 | implicit def dualArbitrary[A: Arbitrary]: Arbitrary[Dual[A]] = 9 | Arbitrary(Arbitrary.arbitrary[A].map(Dual(_))) 10 | implicit def firstArbitrary[A: Arbitrary]: Arbitrary[First[A]] = 11 | Arbitrary(Arbitrary.arbitrary[A].map(First(_))) 12 | implicit def lastArbitary[A: Arbitrary]: Arbitrary[Last[A]] = 13 | Arbitrary(Arbitrary.arbitrary[A].map(Last(_))) 14 | implicit def maxArbitrary[A: Arbitrary]: Arbitrary[Max[A]] = 15 | Arbitrary(Arbitrary.arbitrary[A].map(Max(_))) 16 | implicit def minArbitary[A: Arbitrary]: Arbitrary[Min[A]] = 17 | Arbitrary(Arbitrary.arbitrary[A].map(Min(_))) 18 | implicit def intersectArbitrary[A: Arbitrary]: Arbitrary[Intersect[A]] = 19 | Arbitrary(Arbitrary.arbitrary[Set[A]].map(Intersect(_))) 20 | } 21 | -------------------------------------------------------------------------------- /core/src/test/scala/io/chrisdavenport/semigroups/IntersectSuite.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats.kernel.laws.discipline._ 4 | import cats.laws.discipline._ 5 | import cats.syntax.all._ 6 | 7 | class IntersectSuite extends munit.DisciplineSuite with SemigroupsArbitraries { 8 | checkAll("Intersect", EqTests[Intersect[Int]].eqv) 9 | checkAll("Intersect", SemilatticeTests[Intersect[Int]].semilattice) 10 | checkAll("Intersect", MonoidKTests[Intersect].monoidK[Int]) 11 | checkAll( 12 | "Intersect", 13 | UnorderedTraverseTests[Intersect].unorderedTraverse[Int, Int, Int, Option, Option] 14 | ) 15 | 16 | test("show") { 17 | assertEquals(Intersect(Set(1, 2)).show, "Intersect(Set(1, 2))") 18 | assertEquals(Intersect(Set(1)).show, "Intersect(Set(1))") 19 | assertEquals(Intersect(Set.empty[Int]).show, "Intersect(Set())") 20 | } 21 | 22 | test("returns Intersect") { 23 | val first = Intersect(Set(1, 2, 3)) 24 | val second = Intersect(Set(2, 3, 4)) 25 | assertEquals(first |+| second, Intersect(Set(2, 3))) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/test/scala/io/chrisdavenport/semigroups/DualSuite.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.kernel.laws.discipline._ 5 | import cats.laws.discipline._ 6 | import cats.syntax.all._ 7 | 8 | class DualSuite extends munit.DisciplineSuite with SemigroupsArbitraries { 9 | checkAll("Dual", OrderTests[Dual[Int]].order) 10 | checkAll("Dual", SemigroupTests[Dual[Int]].semigroup) 11 | checkAll("Dual", MonadTests[Dual].monad[Int, Int, Int]) 12 | checkAll( 13 | "Dual", 14 | NonEmptyTraverseTests[Dual].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option] 15 | ) 16 | checkAll("Dual", DistributiveTests[Dual].distributive[Int, Int, Int, Option, Id]) 17 | 18 | test("show") { 19 | assertEquals(Dual(true).show, "Dual(true)") 20 | assertEquals(Dual(false).show, "Dual(false)") 21 | } 22 | 23 | test("inverse default") { 24 | val first = "Hello " 25 | val second = "World" 26 | val expected = "Hello World" 27 | 28 | assertEquals(first |+| second, expected) 29 | assertEquals((Dual(second) |+| Dual(first)).getDual, expected) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.2") 2 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") 3 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.20") 4 | addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.2") 5 | addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") 6 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.10") 7 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") 8 | addSbtPlugin("com.47deg" % "sbt-microsites" % "1.3.4") 9 | addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") 10 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") 11 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") 12 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") 13 | addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.13.0") 14 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") 15 | -------------------------------------------------------------------------------- /core/src/main/scala/io/chrisdavenport/semigroups/IntersectInstances.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.syntax.all._ 5 | import cats.kernel.{CommutativeMonoid, Semilattice} 6 | 7 | private[semigroups] abstract class IntersectInstances { 8 | implicit def intersectSemilattice[A]: Semilattice[Intersect[A]] = new Semilattice[Intersect[A]] { 9 | def combine(x: Intersect[A], y: Intersect[A]): Intersect[A] = Intersect( 10 | x.getIntersect.intersect(y.getIntersect) 11 | ) 12 | } 13 | 14 | implicit def intersectShow[A: Show]: Show[Intersect[A]] = 15 | Show.show[Intersect[A]](ia => s"Intersect(${ia.getIntersect.show})") 16 | 17 | implicit def IntersectEq[A]: Eq[Intersect[A]] = Eq.by(_.getIntersect) 18 | 19 | implicit val intersectInstances: MonoidK[Intersect] with UnorderedTraverse[Intersect] = 20 | new MonoidK[Intersect] with UnorderedTraverse[Intersect] { 21 | def empty[A]: Intersect[A] = Intersect(MonoidK[Set].empty) 22 | def combineK[A](x: Intersect[A], y: Intersect[A]): Intersect[A] = 23 | Intersect(SemigroupK[Set].combineK(x.getIntersect, y.getIntersect)) 24 | def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: Intersect[A])( 25 | f: A => G[B] 26 | ): G[Intersect[B]] = 27 | UnorderedTraverse[Set].unorderedTraverse(sa.getIntersect)(f).map(Intersect(_)) 28 | def unorderedFoldMap[A, B: CommutativeMonoid](fa: Intersect[A])(f: A => B): B = 29 | UnorderedFoldable[Set].unorderedFoldMap(fa.getIntersect)(f) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['**'] 13 | push: 14 | branches: ['**'] 15 | 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | jobs: 20 | build: 21 | name: Build and Test 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest] 25 | scala: [2.12.15, 2.13.6, 3.1.0] 26 | java: [adopt@1.8] 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - name: Checkout current branch (full) 30 | uses: actions/checkout@v2 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Setup Java and Scala 35 | uses: olafurpg/setup-scala@v13 36 | with: 37 | java-version: ${{ matrix.java }} 38 | 39 | - name: Cache sbt 40 | uses: actions/cache@v2 41 | with: 42 | path: | 43 | ~/.sbt 44 | ~/.ivy2/cache 45 | ~/.coursier/cache/v1 46 | ~/.cache/coursier/v1 47 | ~/AppData/Local/Coursier/Cache/v1 48 | ~/Library/Caches/Coursier/v1 49 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 50 | 51 | - name: Check that workflows are up to date 52 | run: sbt ++${{ matrix.scala }} githubWorkflowCheck 53 | 54 | - name: Check formatting 55 | run: sbt ++${{ matrix.scala }} scalafmtCheckAll scalafmtSbtCheck 56 | 57 | - name: Check binary issues 58 | if: matrix.scala != '3.1.0' 59 | run: sbt ++${{ matrix.scala }} coreJVM/mimaReportBinaryIssues coreJS/mimaReportBinaryIssues 60 | 61 | - name: Compile 62 | run: sbt ++${{ matrix.scala }} Test/compile 63 | 64 | - name: Run tests 65 | run: sbt ++${{ matrix.scala }} test 66 | -------------------------------------------------------------------------------- /core/src/main/scala/io/chrisdavenport/semigroups/DualInstances.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.syntax.all._ 5 | 6 | private[semigroups] trait DualInstances extends DualInstances1 { 7 | implicit def dualSemigroup[A: Semigroup]: Semigroup[Dual[A]] = new Semigroup[Dual[A]] { 8 | def combine(x: Dual[A], y: Dual[A]): Dual[A] = Dual(Semigroup[A].combine(y.getDual, x.getDual)) 9 | } 10 | implicit def dualShow[A: Show]: Show[Dual[A]] = 11 | Show.show[Dual[A]](dualA => s"Dual(${dualA.getDual.show})") 12 | 13 | implicit def dualOrder[A: Order]: Order[Dual[A]] = 14 | Order.by(_.getDual) 15 | 16 | implicit val dualInstances: CommutativeMonad[Dual] 17 | with NonEmptyTraverse[Dual] 18 | with Distributive[Dual] = 19 | new CommutativeMonad[Dual] with NonEmptyTraverse[Dual] with Distributive[Dual] { 20 | def pure[A](a: A): Dual[A] = Dual(a) 21 | def flatMap[A, B](fa: Dual[A])(f: A => Dual[B]): Dual[B] = f(fa.getDual) 22 | 23 | @scala.annotation.tailrec 24 | def tailRecM[A, B](a: A)(f: A => Dual[Either[A, B]]): Dual[B] = 25 | f(a).getDual match { 26 | case Left(a) => tailRecM(a)(f) 27 | case Right(b) => Dual(b) 28 | } 29 | // Members declared in cats.Foldable 30 | def foldLeft[A, B](fa: Dual[A], b: B)(f: (B, A) => B): B = 31 | f(b, fa.getDual) 32 | def foldRight[A, B](fa: Dual[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): cats.Eval[B] = 33 | f(fa.getDual, lb) 34 | 35 | // Members declared in cats.NonEmptyTraverse 36 | def nonEmptyTraverse[G[_]: Apply, A, B](fa: Dual[A])(f: A => G[B]): G[Dual[B]] = 37 | f(fa.getDual).map(Dual(_)) 38 | 39 | // Members declared in cats.Reducible 40 | def reduceLeftTo[A, B](fa: Dual[A])(f: A => B)(g: (B, A) => B): B = 41 | f(fa.getDual) 42 | def reduceRightTo[A, B](fa: Dual[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = 43 | f(fa.getDual).pure[Eval] 44 | 45 | def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => Dual[B]): Dual[G[B]] = 46 | Dual(ga.map(f).map(_.getDual)) 47 | } 48 | } 49 | 50 | private[semigroups] trait DualInstances1 { 51 | implicit def dualEq[A: Eq]: Eq[Dual[A]] = Eq.by(_.getDual) 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/scala/io/chrisdavenport/semigroups/FirstInstances.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.syntax.all._ 5 | import cats.kernel.Band 6 | 7 | private[semigroups] trait FirstInstances extends FirstInstances1 { 8 | implicit def firstBand[A]: Band[First[A]] = new Band[First[A]] { 9 | def combine(x: First[A], y: First[A]): First[A] = x 10 | } 11 | 12 | implicit def firstShow[A: Show]: Show[First[A]] = 13 | Show.show[First[A]](firstA => s"First(${firstA.getFirst.show})") 14 | 15 | implicit def firstOrder[A: Order]: Order[First[A]] = 16 | Order.by(_.getFirst) 17 | 18 | implicit val firstInstances: CommutativeMonad[First] 19 | with NonEmptyTraverse[First] 20 | with Distributive[First] = 21 | new CommutativeMonad[First] with NonEmptyTraverse[First] with Distributive[First] { 22 | def pure[A](a: A): First[A] = First(a) 23 | def flatMap[A, B](fa: First[A])(f: A => First[B]): First[B] = f(fa.getFirst) 24 | 25 | @scala.annotation.tailrec 26 | def tailRecM[A, B](a: A)(f: A => First[Either[A, B]]): First[B] = 27 | f(a).getFirst match { 28 | case Left(a) => tailRecM(a)(f) 29 | case Right(b) => First(b) 30 | } 31 | // Members declared in cats.Foldable 32 | def foldLeft[A, B](fa: First[A], b: B)(f: (B, A) => B): B = 33 | f(b, fa.getFirst) 34 | def foldRight[A, B](fa: First[A], lb: cats.Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = 35 | f(fa.getFirst, lb) 36 | 37 | // Members declared in cats.NonEmptyTraverse 38 | def nonEmptyTraverse[G[_]: Apply, A, B](fa: First[A])(f: A => G[B]): G[First[B]] = 39 | f(fa.getFirst).map(First(_)) 40 | 41 | // Members declared in cats.Reducible 42 | def reduceLeftTo[A, B](fa: First[A])(f: A => B)(g: (B, A) => B): B = 43 | f(fa.getFirst) 44 | def reduceRightTo[A, B](fa: First[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = 45 | f(fa.getFirst).pure[Eval] 46 | 47 | def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => First[B]): First[G[B]] = 48 | First(ga.map(f).map(_.getFirst)) 49 | } 50 | } 51 | 52 | private[semigroups] trait FirstInstances1 { 53 | implicit def firstEq[A: Eq]: Eq[First[A]] = Eq.by(_.getFirst) 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/scala/io/chrisdavenport/semigroups/LastInstances.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.syntax.all._ 5 | import cats.kernel.Band 6 | 7 | private[semigroups] trait LastInstances extends LastInstances1 { 8 | implicit def lastBand[A]: Band[Last[A]] = new Band[Last[A]] { 9 | def combine(x: Last[A], y: Last[A]): Last[A] = y 10 | } 11 | } 12 | 13 | private[semigroups] trait LastInstances1 { 14 | implicit def lastShow[A: Show]: Show[Last[A]] = 15 | Show.show[Last[A]](lastA => s"Last(${lastA.getLast.show})") 16 | 17 | implicit def LastOrder[A: Order]: Order[Last[A]] = 18 | Order.by(_.getLast) 19 | 20 | implicit val lastInstances: CommutativeMonad[Last] 21 | with NonEmptyTraverse[Last] 22 | with Distributive[Last] = 23 | new CommutativeMonad[Last] with NonEmptyTraverse[Last] with Distributive[Last] { 24 | def pure[A](a: A): Last[A] = Last(a) 25 | def flatMap[A, B](fa: Last[A])(f: A => Last[B]): Last[B] = f(fa.getLast) 26 | 27 | @scala.annotation.tailrec 28 | def tailRecM[A, B](a: A)(f: A => Last[Either[A, B]]): Last[B] = 29 | f(a).getLast match { 30 | case Left(a) => tailRecM(a)(f) 31 | case Right(b) => Last(b) 32 | } 33 | // Members declared in cats.Foldable 34 | def foldLeft[A, B](fa: Last[A], b: B)(f: (B, A) => B): B = 35 | f(b, fa.getLast) 36 | def foldRight[A, B](fa: Last[A], lb: cats.Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = 37 | f(fa.getLast, lb) 38 | 39 | // Members declared in cats.NonEmptyTraverse 40 | def nonEmptyTraverse[G[_]: Apply, A, B](fa: Last[A])(f: A => G[B]): G[Last[B]] = 41 | f(fa.getLast).map(Last(_)) 42 | 43 | // Members declared in cats.Reducible 44 | def reduceLeftTo[A, B](fa: Last[A])(f: A => B)(g: (B, A) => B): B = 45 | f(fa.getLast) 46 | def reduceRightTo[A, B](fa: Last[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = 47 | f(fa.getLast).pure[Eval] 48 | 49 | def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => Last[B]): Last[G[B]] = 50 | Last(ga.map(f).map(_.getLast)) 51 | } 52 | } 53 | 54 | private[semigroups] trait LastInstances2 { 55 | implicit def lastEq[A: Eq]: Eq[Last[A]] = Eq.by(_.getLast) 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done 60 | -------------------------------------------------------------------------------- /core/src/main/scala/io/chrisdavenport/semigroups/MinInstances.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.syntax.all._ 5 | import cats.kernel.{BoundedSemilattice, Semilattice, UpperBounded} 6 | 7 | private[semigroups] trait MinInstances extends MinInstances1 { 8 | def min[F[_]: Reducible, A](fa: F[A])(implicit L: Semigroup[Min[A]]): A = 9 | fa.reduceMap(Min(_)).getMin 10 | 11 | implicit def orderedMinBoundedSemilattice[A: UpperBounded: Order]: BoundedSemilattice[Min[A]] = 12 | new BoundedSemilattice[Min[A]] { 13 | def combine(x: Min[A], y: Min[A]): Min[A] = Min(Order[A].min(x.getMin, y.getMin)) 14 | def empty: Min[A] = Min(UpperBounded[A].maxBound) 15 | } 16 | } 17 | 18 | private[semigroups] trait MinInstances1 { 19 | implicit def minShow[A: Show]: Show[Min[A]] = 20 | Show.show[Min[A]](minA => s"Min(${minA.getMin.show})") 21 | 22 | implicit def minOrder[A: Order]: Order[Min[A]] = 23 | Order.by(_.getMin) 24 | 25 | implicit def orderedMinSemilattice[A: Order]: Semilattice[Min[A]] = new Semilattice[Min[A]] { 26 | def combine(x: Min[A], y: Min[A]): Min[A] = 27 | Min(Order.min(x.getMin, y.getMin)) 28 | } 29 | 30 | implicit val minInstances: CommutativeMonad[Min] 31 | with NonEmptyTraverse[Min] 32 | with Distributive[Min] = 33 | new CommutativeMonad[Min] with NonEmptyTraverse[Min] with Distributive[Min] { 34 | def pure[A](a: A): Min[A] = Min(a) 35 | def flatMap[A, B](fa: Min[A])(f: A => Min[B]): Min[B] = f(fa.getMin) 36 | 37 | @scala.annotation.tailrec 38 | def tailRecM[A, B](a: A)(f: A => Min[Either[A, B]]): Min[B] = 39 | f(a).getMin match { 40 | case Left(a) => tailRecM(a)(f) 41 | case Right(b) => Min(b) 42 | } 43 | // Members declared in cats.Foldable 44 | def foldLeft[A, B](fa: Min[A], b: B)(f: (B, A) => B): B = 45 | f(b, fa.getMin) 46 | def foldRight[A, B](fa: Min[A], lb: cats.Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = 47 | f(fa.getMin, lb) 48 | 49 | // Members declared in cats.NonEmptyTraverse 50 | def nonEmptyTraverse[G[_]: Apply, A, B](fa: Min[A])(f: A => G[B]): G[Min[B]] = 51 | f(fa.getMin).map(Min(_)) 52 | 53 | // Members declared in cats.Reducible 54 | def reduceLeftTo[A, B](fa: Min[A])(f: A => B)(g: (B, A) => B): B = 55 | f(fa.getMin) 56 | def reduceRightTo[A, B](fa: Min[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = 57 | f(fa.getMin).pure[Eval] 58 | 59 | def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => Min[B]): Min[G[B]] = 60 | Min(ga.map(f).map(_.getMin)) 61 | } 62 | } 63 | 64 | private[semigroups] trait MinInstances2 { 65 | implicit def minEq[A: Eq]: Eq[Min[A]] = Eq.by(_.getMin) 66 | } 67 | -------------------------------------------------------------------------------- /core/src/main/scala/io/chrisdavenport/semigroups/MaxInstances.scala: -------------------------------------------------------------------------------- 1 | package io.chrisdavenport.semigroups 2 | 3 | import cats._ 4 | import cats.syntax.all._ 5 | import cats.kernel.Semilattice 6 | import cats.kernel.LowerBounded 7 | import cats.kernel.BoundedSemilattice 8 | 9 | private[semigroups] trait MaxInstances extends MaxInstances1 { 10 | def max[F[_]: Reducible, A](fa: F[A])(implicit L: Semigroup[Max[A]]): A = 11 | fa.reduceMap(Max(_)).getMax 12 | 13 | implicit def orderedMaxBoundedSemilattice[A: LowerBounded: Order]: BoundedSemilattice[Max[A]] = 14 | new BoundedSemilattice[Max[A]] { 15 | def combine(x: Max[A], y: Max[A]): Max[A] = Max(Order[A].max(x.getMax, y.getMax)) 16 | def empty: Max[A] = Max(LowerBounded[A].minBound) 17 | } 18 | } 19 | 20 | private[semigroups] trait MaxInstances1 { 21 | implicit def maxShow[A: Show]: Show[Max[A]] = 22 | Show.show[Max[A]](maxA => s"Max(${maxA.getMax.show})") 23 | 24 | implicit def maxOrder[A: Order]: Order[Max[A]] = 25 | Order.by(_.getMax) 26 | 27 | implicit def orderedMaxSemilattice[A: Order]: Semilattice[Max[A]] = new Semilattice[Max[A]] { 28 | def combine(x: Max[A], y: Max[A]): Max[A] = Max(Order[A].max(x.getMax, y.getMax)) 29 | } 30 | 31 | implicit val maxInstances: CommutativeMonad[Max] 32 | with NonEmptyTraverse[Max] 33 | with Distributive[Max] = 34 | new CommutativeMonad[Max] with NonEmptyTraverse[Max] with Distributive[Max] { 35 | def pure[A](a: A): Max[A] = Max(a) 36 | def flatMap[A, B](fa: Max[A])(f: A => Max[B]): Max[B] = f(fa.getMax) 37 | 38 | @scala.annotation.tailrec 39 | def tailRecM[A, B](a: A)(f: A => Max[Either[A, B]]): Max[B] = 40 | f(a).getMax match { 41 | case Left(a) => tailRecM(a)(f) 42 | case Right(b) => Max(b) 43 | } 44 | // Members declared in cats.Foldable 45 | def foldLeft[A, B](fa: Max[A], b: B)(f: (B, A) => B): B = 46 | f(b, fa.getMax) 47 | def foldRight[A, B](fa: Max[A], lb: cats.Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = 48 | f(fa.getMax, lb) 49 | 50 | // Members declared in cats.NonEmptyTraverse 51 | def nonEmptyTraverse[G[_]: Apply, A, B](fa: Max[A])(f: A => G[B]): G[Max[B]] = 52 | f(fa.getMax).map(Max(_)) 53 | 54 | // Members declared in cats.Reducible 55 | def reduceLeftTo[A, B](fa: Max[A])(f: A => B)(g: (B, A) => B): B = 56 | f(fa.getMax) 57 | def reduceRightTo[A, B](fa: Max[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = 58 | f(fa.getMax).pure[Eval] 59 | 60 | def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => Max[B]): Max[G[B]] = 61 | Max(ga.map(f).map(_.getMax)) 62 | } 63 | } 64 | 65 | private[semigroups] trait MaxInstances2 { 66 | implicit def maxEq[A: Eq]: Eq[Max[A]] = Eq.by(_.getMax) 67 | } 68 | --------------------------------------------------------------------------------