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

Initial support for export-hook #22

Merged
merged 6 commits into from
Nov 17, 2015
Merged
Show file tree
Hide file tree
Changes from 5 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
15 changes: 11 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@ addCommandAlias("gitSnapshots", ";set version in ThisBuild := git.gitDescribedVe
val gh = GitHubSettings(org = "non", proj = "alleycats", publishOrg = "org.typelevel", license = mit)
val devs = Seq(Dev("Erik Osheim", "non"))

val vers = typelevel.versions ++ alleycats.versions
val updates = Map(
"macro-compat" -> "1.1.0",
"export-hook" -> "1.1.0",
"simulacrum" -> "0.5.0"
)
val updatesSettings = Seq( resolvers += Resolver.sonatypeRepo("snapshots"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All snapshot dependencies now replaced with releases, so this is ready to merge now.

Small point; As you now have no shapshot dependendencies, might we an idea to remove/comment out this line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.


val vers = typelevel.versions ++ alleycats.versions ++ updates
val libs = typelevel.libraries ++ alleycats.libraries
val addins = typelevel.scalacPlugins ++ alleycats.scalacPlugins
val vAll = Versions(vers, libs, addins)

/**
* alleycats - This is the root project that aggregates the alleycatsJVM and alleycatsJS sub projects
*/
lazy val rootSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings
lazy val rootSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings ++ updatesSettings

lazy val module = mkModuleFactory(gh.proj, mkConfig(rootSettings, commonJvmSettings, commonJsSettings))
lazy val prj = mkPrjFactory(rootSettings)
Expand All @@ -51,7 +58,7 @@ lazy val coreJVM = coreM.jvm
lazy val coreJS = coreM.js
lazy val coreM = module("core", CrossType.Pure)
.settings(typelevel.macroCompatSettings(vAll):_*)
.settings(addLibs(vAll, "cats-core"):_*)
.settings(addLibs(vAll, "cats-core", "export-hook", "simulacrum"):_*)

/**
* Laws project
Expand Down Expand Up @@ -86,7 +93,7 @@ lazy val commonSettings = sharedCommonSettings ++
addCompilerPlugins(vAll, "kind-projector") ++ Seq(
scalacOptions ++= scalacAllOptions,
parallelExecution in Test := false
) ++ warnUnusedImport ++ unidocCommonSettings
) /* ++ warnUnusedImport */ ++ unidocCommonSettings // spurious warnings from macro annotations expected
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small point (again): locally this works for me enabled, perhaps worth re-adding?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Interesting ... I'll try that again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still seeing the unused import warnings :-(

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for this, so am I :-(


lazy val commonJsSettings = Seq(
scalaJSStage in Global := FastOptStage
Expand Down
20 changes: 20 additions & 0 deletions core/src/main/scala/alleycats/ConsK.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package alleycats

import cats.SemigroupK
import export.imports
import simulacrum.typeclass

@typeclass trait ConsK[F[_]] {
def cons[A](hd: A, tl: F[A]): F[A]
}

object ConsK extends ConsK0 {
implicit def pureSemigroupKIsConsK[F[_]](implicit p: Pure[F], s: SemigroupK[F]): ConsK[F] =
new ConsK[F] {
def cons[A](hd: A, tl: F[A]): F[A] = s.combine(p.pure(hd), tl)
}
}

@imports[ConsK]
trait ConsK0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be private[alleycats] sealed trait ConsK0? I don't know if that causes any issues with export hook. But in general I think that if a trait just exists as an implementation detail then we shouldn't expose it for extension/usage by users of the library. If nothing else, making it private makes it obvious to users that they aren't meant to try to use it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment applies to various other places in this PR as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My default position is that all pure code should be public unless there's a compelling reason to the contrary. I don't think there is here, so I wouldn't make this private as a matter of course.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a huge scala infrastructure at work, so fighting binary incompatibilities in library versions is usually my compelling reason to not expose something that is kind of an internal detail :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that there's a discussion to be had here which goes beyond the scope of this PR. Specifically though, I don't see any binary incompatibility issues arising from this trait.


7 changes: 6 additions & 1 deletion core/src/main/scala/alleycats/Empty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package alleycats

import cats.{Eq, Monoid}
import cats.syntax.eq._
import export.imports
import simulacrum.typeclass
import scala.collection.generic.CanBuildFrom

Expand All @@ -25,7 +26,11 @@ trait EmptyInstances0 extends EmptyInstances1 {
Empty(cbf().result)
}

trait EmptyInstances1 {
trait EmptyInstances1 extends EmptyInstances2 {
// If Monoid extended Empty then this could be an exported subclass instance provided by Monoid
implicit def monoidIsEmpty[A: Monoid]: Empty[A] =
Empty(Monoid[A].empty)
}

@imports[Empty]
trait EmptyInstances2
10 changes: 10 additions & 0 deletions core/src/main/scala/alleycats/EmptyK.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package alleycats

import export._
import simulacrum.typeclass

@typeclass trait EmptyK[F[_]] { self =>
Expand All @@ -10,3 +11,12 @@ import simulacrum.typeclass
def empty: F[A] = self.empty[A]
}
}

@imports[EmptyK]
object EmptyK

@exports
object EmptyKInstances {
@export(Instantiated)
implicit def instantiate[F[_], T](implicit ekf: EmptyK[F]): Empty[F[T]] = ekf.synthesize[T]
}
8 changes: 7 additions & 1 deletion core/src/main/scala/alleycats/Extract.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
package alleycats

import cats.{Applicative, CoflatMap, Comonad}
import export.imports
import simulacrum.typeclass

@typeclass trait Extract[F[_]] {
def extract[A](fa: F[A]): A
}

object Extract {
object Extract extends Extract0 {
// Ideally this would be an exported subclass instance provided by Comonad
implicit def comonadIsExtract[F[_]](implicit ev: Comonad[F]): Extract[F] =
new Extract[F] {
def extract[A](fa: F[A]): A = ev.extract(fa)
}

// Ideally this would be an instance exported to Comonad
implicit def extractCoflatMapIsComonad[F[_]](implicit e: Extract[F], cf: CoflatMap[F]): Comonad[F] =
new Comonad[F] {
def extract[A](fa: F[A]): A = e.extract(fa)
override def map[A, B](fa: F[A])(f: A => B): F[B] = cf.map(fa)(f)
def coflatMap[A, B](fa: F[A])(f: F[A] => B): F[B] = cf.coflatMap(fa)(f)
}
}

@imports[Extract]
trait Extract0
8 changes: 7 additions & 1 deletion core/src/main/scala/alleycats/One.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package alleycats
import algebra.ring.{MultiplicativeMonoid, MultiplicativeSemigroup}
import cats.{Eq, Monoid}
import cats.syntax.eq._
import export.imports
import simulacrum.typeclass
import scala.collection.generic.CanBuildFrom

Expand All @@ -16,16 +17,21 @@ import scala.collection.generic.CanBuildFrom
one =!= a
}

object One {
object One extends One0 {
def apply[A](a: => A): One[A] =
new One[A] { lazy val one: A = a }

// Ideally this would be an exported subclass instance provided by MultiplicativeMonoid
implicit def multiplicativeMonoidIsOne[A](implicit ev: MultiplicativeMonoid[A]): One[A] =
One(ev.one)

// Ideally this would be an instance exported to MultiplicativeMonoid
implicit def oneWithSemigroupIsMonoid[A](implicit z: One[A], s: MultiplicativeSemigroup[A]): MultiplicativeMonoid[A] =
new MultiplicativeMonoid[A] {
def one: A = z.one
def times(x: A, y: A): A = s.times(x, y)
}
}

@imports[One]
trait One0
8 changes: 7 additions & 1 deletion core/src/main/scala/alleycats/Pure.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
package alleycats

import cats.{Applicative, FlatMap, Monad}
import export.imports
import simulacrum.typeclass

@typeclass trait Pure[F[_]] {
def pure[A](a: A): F[A]
}

object Pure {
object Pure extends Pure0 {
// Ideally this would be an exported subclass instance provided by Applicative
implicit def applicativeIsPure[F[_]](implicit ev: Applicative[F]): Pure[F] =
new Pure[F] {
def pure[A](a: A): F[A] = ev.pure(a)
}

// Ideally this would be an instance exported to Monad
implicit def pureFlatMapIsMonad[F[_]](implicit p: Pure[F], fm: FlatMap[F]): Monad[F] =
new Monad[F] {
def pure[A](a: A): F[A] = p.pure(a)
override def map[A, B](fa: F[A])(f: A => B): F[B] = fm.map(fa)(f)
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = fm.flatMap(fa)(f)
}
}

@imports[Pure]
trait Pure0
8 changes: 7 additions & 1 deletion core/src/main/scala/alleycats/Zero.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package alleycats
import algebra.ring.{AdditiveMonoid, AdditiveSemigroup}
import cats.{Eq, Monoid}
import cats.syntax.eq._
import export.imports
import simulacrum.typeclass
import scala.collection.generic.CanBuildFrom

Expand All @@ -16,16 +17,21 @@ import scala.collection.generic.CanBuildFrom
zero =!= a
}

object Zero {
object Zero extends Zero0 {
def apply[A](a: => A): Zero[A] =
new Zero[A] { lazy val zero: A = a }

// Ideally this would be an exported subclass instance provided by AdditiveMonoid
implicit def additiveMonoidIsZero[A](implicit ev: AdditiveMonoid[A]): Zero[A] =
Zero(ev.zero)

// Ideally this would be an instance exported to AdditiveMonoid
implicit def zeroWithSemigroupIsMonoid[A](implicit z: Zero[A], s: AdditiveSemigroup[A]): AdditiveMonoid[A] =
new AdditiveMonoid[A] {
def zero: A = z.zero
def plus(x: A, y: A): A = s.plus(x, y)
}
}

@imports[Zero]
trait Zero0
17 changes: 11 additions & 6 deletions core/src/main/scala/alleycats/std/all.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package alleycats.std
package alleycats
package std

object all
extends ListInstances
with OptionInstances
with SetInstances
with TryInstances
import export._

@reexports(
EmptyKInstances,
ListInstances,
OptionInstances,
SetInstances,
TryInstances
) object all extends LegacySetInstances with LegacyTryInstances
17 changes: 14 additions & 3 deletions core/src/main/scala/alleycats/std/list.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package alleycats
package std

object list extends ListInstances
import export._

trait ListInstances {
implicit val listEmptyK: EmptyK[List] =
@reexports(ListInstances)
object list

@exports
object ListInstances {
@export(Orphan)
implicit val exportListEmptyK: EmptyK[List] =
new EmptyK[List] {
def empty[A]: List[A] = Nil
}

@export(Orphan)
implicit val exportListConsK: ConsK[List] =
new ConsK[List] {
def cons[A](hd: A, tl: List[A]): List[A] = hd :: tl
}
}
11 changes: 8 additions & 3 deletions core/src/main/scala/alleycats/std/option.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package alleycats
package std

object option extends OptionInstances
import export._

trait OptionInstances {
implicit val optionEmptyK: EmptyK[Option] =
@reexports(OptionInstances)
object option

@exports
object OptionInstances {
@export(Orphan)
implicit val exportOptionEmptyK: EmptyK[Option] =
new EmptyK[Option] {
def empty[A]: Option[A] = None
}
Expand Down
19 changes: 15 additions & 4 deletions core/src/main/scala/alleycats/std/set.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package alleycats.std

import cats.{Applicative, Eval, Foldable, Monad, Traverse}
import export._

object set extends SetInstances

trait SetInstances {

@exports
object SetInstances {
// This method advertises parametricity, but relies on using
// universal hash codes and equality, which hurts our ability to
// rely on free theorems.
Expand All @@ -26,6 +25,7 @@ trait SetInstances {
// contain three. Since `g` is not a function (speaking strictly)
// this would not be considered a law violation, but it still makes
// people uncomfortable.
@export(Orphan)
implicit val setMonad: Monad[Set] =
new Monad[Set] {
def pure[A](a: A): Set[A] = Set(a)
Expand All @@ -36,6 +36,7 @@ trait SetInstances {
// Since iteration order is not guaranteed for sets, folds and other
// traversals may produce different results for input sets which
// appear to be the same.
@export(Orphan)
implicit val setTraverse: Traverse[Set] =
new Traverse[Set] {
def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B =
Expand All @@ -50,3 +51,13 @@ trait SetInstances {
}
}
}

@reexports(SetInstances)
object set extends LegacySetInstances

// TODO: remove when cats.{ Set, Traverse } support export-hook
trait LegacySetInstances {
implicit def legacySetMonad(implicit e: ExportOrphan[Monad[Set]]): Monad[Set] = e.instance

implicit def legacySetTraverse(implicit e: ExportOrphan[Traverse[Set]]): Traverse[Set] = e.instance
}
15 changes: 12 additions & 3 deletions core/src/main/scala/alleycats/std/try.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package alleycats
package std

import cats.Bimonad
import export._
import scala.util.Try

object try_ extends TryInstances

trait TryInstances {
@reexports(TryInstances)
object try_ extends LegacyTryInstances

@exports
object TryInstances {
// There are various concerns people have over Try's ability to
// satisfy the monad laws. For example, consider the following code:
//
Expand All @@ -28,6 +30,8 @@ trait TryInstances {
// Furthermore, since Cats has introduced a Bimonad[A], the Monad[Try]
// and Comanad[Try] instances have been replaced by a single Bimonad[Try]
// instance.
//
@export(Orphan)
implicit val tryBimonad: Bimonad[Try] =
new Bimonad[Try] {
def pure[A](a: A): Try[A] = Try(a)
Expand All @@ -37,3 +41,8 @@ trait TryInstances {
def extract[A](p: Try[A]): A = p.get
}
}

// TODO: remove when cats.{ Monad, Comonad, Bimonad } support export-hook
trait LegacyTryInstances {
implicit def legacyTryBimonad(implicit e: ExportOrphan[Bimonad[Try]]): Bimonad[Try] = e.instance
}