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

Welcome, Alleycats #1984

Merged
merged 2 commits into from
Oct 26, 2017
Merged
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
20 changes: 20 additions & 0 deletions alleycats-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.combineK(p.pure(hd), tl)
}
}

@imports[ConsK]
trait ConsK0

36 changes: 36 additions & 0 deletions alleycats-core/src/main/scala/alleycats/Empty.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package alleycats

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

@typeclass trait Empty[A] {
def empty: A

def isEmpty(a: A)(implicit ev: Eq[A]): Boolean =
empty === a

def nonEmpty(a: A)(implicit ev: Eq[A]): Boolean =
empty =!= a
}

object Empty extends EmptyInstances0 {
def apply[A](a: => A): Empty[A] =
new Empty[A] { lazy val empty: A = a }
}

trait EmptyInstances0 extends EmptyInstances1 {
implicit def iterableIsEmpty[CC[X] <: Iterable[X], A](implicit cbf: CanBuildFrom[CC[A], A, CC[A]]): Empty[CC[A]] =
Empty(cbf().result)
}

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
22 changes: 22 additions & 0 deletions alleycats-core/src/main/scala/alleycats/EmptyK.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package alleycats

import export._
import simulacrum.typeclass

@typeclass trait EmptyK[F[_]] { self =>
def empty[A]: F[A]

def synthesize[A]: Empty[F[A]] =
new Empty[F[A]] {
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]
}
28 changes: 28 additions & 0 deletions alleycats-core/src/main/scala/alleycats/Extract.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package alleycats

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

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

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
24 changes: 24 additions & 0 deletions alleycats-core/src/main/scala/alleycats/One.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package alleycats

import cats.{Eq, Monoid}
import cats.syntax.eq._
import export.imports
import simulacrum.typeclass

@typeclass trait One[A] {
def one: A

def isOne(a: A)(implicit ev: Eq[A]): Boolean =
one === a

def nonOne(a: A)(implicit ev: Eq[A]): Boolean =
one =!= a
}

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

@imports[One]
trait One0
29 changes: 29 additions & 0 deletions alleycats-core/src/main/scala/alleycats/Pure.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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 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)
def tailRecM[A, B](a: A)(f: (A) => F[Either[A, B]]): F[B] = fm.tailRecM(a)(f)
}
}

@imports[Pure]
trait Pure0
24 changes: 24 additions & 0 deletions alleycats-core/src/main/scala/alleycats/Zero.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package alleycats

import cats.Eq
import cats.syntax.eq._
import export.imports
import simulacrum.typeclass

@typeclass trait Zero[A] {
def zero: A

def isZero(a: A)(implicit ev: Eq[A]): Boolean =
zero === a

def nonZero(a: A)(implicit ev: Eq[A]): Boolean =
zero =!= a
}

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

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

import export._

@reexports(
EmptyKInstances,
ListInstances,
OptionInstances,
SetInstances,
TryInstances,
IterableInstances
) object all extends LegacySetInstances with LegacyTryInstances with LegacyIterableInstances
27 changes: 27 additions & 0 deletions alleycats-core/src/main/scala/alleycats/std/iterable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package alleycats
package std

import cats.{Eval, Foldable}
import export._

@reexports(IterableInstances)
object iterable extends LegacyIterableInstances

@exports
object IterableInstances {
@export(Orphan)
implicit val exportIterableFoldable: Foldable[Iterable] =
new Foldable[Iterable] {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't actually know why this shouldn't be in cats-core. What is the problem exactly? Foldable is lawless to begin with.

Copy link
Member

Choose a reason for hiding this comment

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

AFAIK Iterable's only method is iterator which isn't exactly referentially transparent, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

But that is the only method you need to implement Foldable. And I don’t see why creating a new Iterator is not referentially transparent. It certainly is for all instances of Iterable is the standard library AFAIK.

Copy link
Contributor Author

@kailuowang kailuowang Oct 26, 2017

Choose a reason for hiding this comment

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

I don't know if all Iterable are actually Foldable, what about Stream?

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn’t Stream have a Foldable instance in cats already? What prevents it from being Foldable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you are right, Stream is Foldable. Yeah, I don't see why we couldn't move Foldable ofIterable into core.

override def foldLeft[A, B](fa: Iterable[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f)

override def foldRight[A, B](fa: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Foldable.iterateRight(fa, lb)(f)
}

}

// TODO: remove when cats.Foldable support export-hook
trait LegacyIterableInstances {
implicit def legacyIterableFoldable(implicit e: ExportOrphan[Foldable[Iterable]]): Foldable[Iterable] = e.instance

}
22 changes: 22 additions & 0 deletions alleycats-core/src/main/scala/alleycats/std/list.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package alleycats
package std

import export._

@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
}
}
16 changes: 16 additions & 0 deletions alleycats-core/src/main/scala/alleycats/std/option.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package alleycats
package std

import export._

@reexports(OptionInstances)
object option

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

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

import scala.annotation.tailrec

@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.
//
// Another problem some people have pointed out is the
// non-associativity of map when using impure functions. For
// example, consider the following expressions:
//
// import scala.util.Random
//
// val f = (_: Int) => 1
// val g = (_: Int) => Random.nextInt
//
// Set(1, 2, 3).map(f).map(g)
// Set(1, 2, 3).map(f andThen g)
//
// The first Set will contain one random number, and the second will
// 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)
override def map[A, B](fa: Set[A])(f: A => B): Set[B] = fa.map(f)
def flatMap[A, B](fa: Set[A])(f: A => Set[B]): Set[B] = fa.flatMap(f)

def tailRecM[A, B](a: A)(f: (A) => Set[Either[A, B]]): Set[B] = {
val bldr = Set.newBuilder[B]

@tailrec def go(set: Set[Either[A, B]]): Unit = {
val lefts = set.foldLeft(Set[A]()) { (memo, either) =>
either.fold(
memo + _,
b => {
bldr += b
memo
}
)
}
if (lefts.isEmpty)
()
else
go(lefts.flatMap(f))
}
go(f(a))
bldr.result()
}
}

// 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 =
fa.foldLeft(b)(f)
def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Foldable.iterateRight(fa, lb)(f)
def traverse[G[_]: Applicative, A, B](sa: Set[A])(f: A => G[B]): G[Set[B]] = {
val G = Applicative[G]
sa.foldLeft(G.pure(Set.empty[B])) { (buf, a) =>
G.map2(buf, f(a))(_ + _)
}
}
}
}

@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
}
Loading