Skip to content

Commit

Permalink
Merge pull request #1631 from kailuowang/improve-partially
Browse files Browse the repository at this point in the history
make all PartialApplied class AnyVal to achieve zero cost
  • Loading branch information
ceedubs authored May 11, 2017
2 parents 2580d74 + b0593e4 commit e5ff7d1
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 60 deletions.
5 changes: 4 additions & 1 deletion core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ object Const extends ConstInstances {
def empty[A, B](implicit A: Monoid[A]): Const[A, B] =
Const(A.empty)

final class OfPartiallyApplied[B] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class OfPartiallyApplied[B](val dummy: Boolean = true ) extends AnyVal {
def apply[A](a: A): Const[A, B] = Const(a)
}

Expand Down
39 changes: 29 additions & 10 deletions core/src/main/scala/cats/data/EitherT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,12 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
Nested[F, ValidatedNel[A, ?], B](F.map(value)(_.toValidatedNel))
}

object EitherT extends EitherTInstances with EitherTFunctions
object EitherT extends EitherTInstances {

private[data] trait EitherTFunctions {

final class LeftPartiallyApplied[B] private[EitherTFunctions] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class LeftPartiallyApplied[B](val dummy: Boolean = true ) extends AnyVal {
def apply[F[_], A](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(Either.left))
}

Expand All @@ -248,7 +249,10 @@ private[data] trait EitherTFunctions {
*/
final def left[B]: LeftPartiallyApplied[B] = new LeftPartiallyApplied[B]

final class LeftTPartiallyApplied[F[_], B] private[EitherTFunctions] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class LeftTPartiallyApplied[F[_], B](val dummy: Boolean = true ) extends AnyVal {
def apply[A](a: A)(implicit F: Applicative[F]): EitherT[F, A, B] = EitherT(F.pure(Either.left(a)))
}

Expand All @@ -263,7 +267,10 @@ private[data] trait EitherTFunctions {
*/
final def leftT[F[_], B]: LeftTPartiallyApplied[F, B] = new LeftTPartiallyApplied[F, B]

final class RightPartiallyApplied[A] private[EitherTFunctions] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class RightPartiallyApplied[A](val dummy: Boolean = true ) extends AnyVal {
def apply[F[_], B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(Either.right))
}

Expand All @@ -278,7 +285,10 @@ private[data] trait EitherTFunctions {
*/
final def right[A]: RightPartiallyApplied[A] = new RightPartiallyApplied[A]

final class PurePartiallyApplied[F[_], A] private[EitherTFunctions] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class PurePartiallyApplied[F[_], A](val dummy: Boolean = true ) extends AnyVal {
def apply[B](b: B)(implicit F: Applicative[F]): EitherT[F, A, B] = right(F.pure(b))
}

Expand Down Expand Up @@ -335,7 +345,10 @@ private[data] trait EitherTFunctions {
*/
final def fromEither[F[_]]: FromEitherPartiallyApplied[F] = new FromEitherPartiallyApplied

final class FromEitherPartiallyApplied[F[_]] private[EitherTFunctions] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class FromEitherPartiallyApplied[F[_]](val dummy: Boolean = true ) extends AnyVal {
def apply[E, A](either: Either[E, A])(implicit F: Applicative[F]): EitherT[F, E, A] =
EitherT(F.pure(either))
}
Expand All @@ -353,7 +366,10 @@ private[data] trait EitherTFunctions {
*/
final def fromOption[F[_]]: FromOptionPartiallyApplied[F] = new FromOptionPartiallyApplied

final class FromOptionPartiallyApplied[F[_]] private[EitherTFunctions] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class FromOptionPartiallyApplied[F[_]](val dummy: Boolean = true ) extends AnyVal {
def apply[E, A](opt: Option[A], ifNone: => E)(implicit F: Applicative[F]): EitherT[F, E, A] =
EitherT(F.pure(Either.fromOption(opt, ifNone)))
}
Expand Down Expand Up @@ -388,7 +404,10 @@ private[data] trait EitherTFunctions {
*/
final def cond[F[_]]: CondPartiallyApplied[F] = new CondPartiallyApplied

final class CondPartiallyApplied[F[_]] private[EitherTFunctions] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class CondPartiallyApplied[F[_]](val dummy: Boolean = true ) extends AnyVal {
def apply[E, A](test: Boolean, right: => A, left: => E)(implicit F: Applicative[F]): EitherT[F, E, A] =
EitherT(F.pure(Either.cond(test, right, left)))
}
Expand Down
43 changes: 33 additions & 10 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,33 +138,56 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
}

object OptionT extends OptionTInstances {
def pure[F[_], A](a: A)(implicit F: Applicative[F]): OptionT[F, A] =
OptionT(F.pure(Some(a)))

/** An alias for pure */
def some[F[_], A](a: A)(implicit F: Applicative[F]): OptionT[F, A] =
pure(a)
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class PurePartiallyApplied[F[_]](val dummy: Boolean = true ) extends AnyVal {
def apply[A](value: A)(implicit F: Applicative[F]): OptionT[F, A] =
OptionT(F.pure(Some(value)))
}

/** Creates a `OptionT[A]` from an `A`
*
* {{{
* scala> import cats.implicits._
* scala> OptionT.pure[List](2)
* res0: OptionT[List, Int] = OptionT(List(Some(2)))
* }}}
*
*/
def pure[F[_]]: PurePartiallyApplied[F] = new PurePartiallyApplied[F]

/** An alias for pure
*
* {{{
* scala> import cats.implicits._
* scala> OptionT.some[List](2)
* res0: OptionT[List, Int] = OptionT(List(Some(2)))
* }}}
*
*/
def some[F[_]]: PurePartiallyApplied[F] = pure

def none[F[_], A](implicit F: Applicative[F]) : OptionT[F, A] =
OptionT(F.pure(None))

/**
* Transforms an `Option` into an `OptionT`, lifted into the specified `Applicative`.
*
* Note: The return type is a `FromOptionPartiallyApplied[F]`, which has an apply method
* on it, allowing you to call `fromOption` like this:
* {{{
* scala> import cats.implicits._
* scala> val o: Option[Int] = Some(2)
* scala> OptionT.fromOption[List](o)
* res0: OptionT[List, Int] = OptionT(List(Some(2)))
* }}}
*
* The reason for the indirection is to emulate currying type parameters.
*/
def fromOption[F[_]]: FromOptionPartiallyApplied[F] = new FromOptionPartiallyApplied

final class FromOptionPartiallyApplied[F[_]] private[OptionT] {
/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class FromOptionPartiallyApplied[F[_]](val dummy: Boolean = true ) extends AnyVal {
def apply[A](value: Option[A])(implicit F: Applicative[F]): OptionT[F, A] =
OptionT(F.pure(value))
}
Expand Down
55 changes: 30 additions & 25 deletions core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,36 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable {
object Validated extends ValidatedInstances with ValidatedFunctions{
final case class Valid[+A](a: A) extends Validated[Nothing, A]
final case class Invalid[+E](e: E) extends Validated[E, Nothing]


/**
* Evaluates the specified block, catching exceptions of the specified type and returning them on the invalid side of
* the resulting `Validated`. Uncaught exceptions are propagated.
*
* For example:
* {{{
* scala> Validated.catchOnly[NumberFormatException] { "foo".toInt }
* res0: Validated[NumberFormatException, Int] = Invalid(java.lang.NumberFormatException: For input string: "foo")
* }}}
*
* This method and its usage of [[NotNull]] are inspired by and derived from
* the `fromTryCatchThrowable` method [[https://github.com/scalaz/scalaz/pull/746/files contributed]]
* to Scalaz by Brian McKenna.
*/
def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = new CatchOnlyPartiallyApplied[T]

/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[data] final class CatchOnlyPartiallyApplied[T](val dummy: Boolean = true ) extends AnyVal{
def apply[A](f: => A)(implicit T: ClassTag[T], NT: NotNull[T]): Validated[T, A] =
try {
valid(f)
} catch {
case t if T.runtimeClass.isInstance(t) =>
invalid(t.asInstanceOf[T])
}
}
}

private[data] sealed abstract class ValidatedInstances extends ValidatedInstances1 {
Expand Down Expand Up @@ -401,31 +431,6 @@ private[data] trait ValidatedFunctions {

def valid[A, B](b: B): Validated[A, B] = Validated.Valid(b)

/**
* Evaluates the specified block, catching exceptions of the specified type and returning them on the invalid side of
* the resulting `Validated`. Uncaught exceptions are propagated.
*
* For example:
* {{{
* scala> Validated.catchOnly[NumberFormatException] { "foo".toInt }
* res0: Validated[NumberFormatException, Int] = Invalid(java.lang.NumberFormatException: For input string: "foo")
* }}}
*
* This method and its usage of [[NotNull]] are inspired by and derived from
* the `fromTryCatchThrowable` method [[https://github.com/scalaz/scalaz/pull/746/files contributed]]
* to Scalaz by Brian McKenna.
*/
def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = new CatchOnlyPartiallyApplied[T]

final class CatchOnlyPartiallyApplied[T] private[ValidatedFunctions] {
def apply[A](f: => A)(implicit T: ClassTag[T], NT: NotNull[T]): Validated[T, A] =
try {
valid(f)
} catch {
case t if T.runtimeClass.isInstance(t) =>
invalid(t.asInstanceOf[T])
}
}

def catchNonFatal[A](f: => A): Validated[Throwable, A] =
try {
Expand Down
27 changes: 18 additions & 9 deletions core/src/main/scala/cats/syntax/either.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package syntax
import cats.data.{EitherT, Ior, Validated, ValidatedNel}
import scala.reflect.ClassTag
import scala.util.{Failure, Success, Try}
import EitherSyntax._

trait EitherSyntax {
implicit final def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab)
Expand All @@ -17,6 +18,22 @@ trait EitherSyntax {
implicit final def catsSyntaxEitherId[A](a: A): EitherIdOps[A] = new EitherIdOps(a)
}

object EitherSyntax {

/**
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
private[syntax] final class CatchOnlyPartiallyApplied[T](val dummy: Boolean = true ) extends AnyVal {
def apply[A](f: => A)(implicit CT: ClassTag[T], NT: NotNull[T]): Either[T, A] =
try {
Right(f)
} catch {
case t if CT.runtimeClass.isInstance(t) =>
Left(t.asInstanceOf[T])
}
}
}

final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal {
def foreach(f: B => Unit): Unit = eab match {
case Left(_) => ()
Expand Down Expand Up @@ -288,15 +305,7 @@ final class EitherObjectOps(val either: Either.type) extends AnyVal { // scalast
}
}

final class CatchOnlyPartiallyApplied[T] private[syntax] {
def apply[A](f: => A)(implicit CT: ClassTag[T], NT: NotNull[T]): Either[T, A] =
try {
Right(f)
} catch {
case t if CT.runtimeClass.isInstance(t) =>
Left(t.asInstanceOf[T])
}
}


final class LeftOps[A, B](val left: Left[A, B]) extends AnyVal {
/** Cast the right type parameter of the `Left`. */
Expand Down
53 changes: 50 additions & 3 deletions docs/src/main/tut/guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,64 @@ All guidelines in cats should have clear justifications. There is no room for tr

## Syntax

### Composing Implicit Conversions in Traits
### <a id="implicit-syntax-conversions" href="#implicit-syntax-conversions"></a> Composing Implicit Conversions in Traits

Implicit syntax conversions provided in publicly-exposed traits should be marked final
so that any composition of the traits provides conversions that can all be inlined.

### Ops Classes
### <a id="ops-classes" href="#ops-classes"></a> Ops Classes

Ops classes should be marked final and extend AnyVal, to take full advantage of inlining and prevent unnecessary allocations.

The most notable exception is the case where all of the ops in the class are provided by zero-cost macros anyway,
for example with Simulacrum.

### <a id="partially-applied-type-params" href="#partially-applied-type-params"></a> Partially-Applied Type

In Scala, when there are multiple type parameters in a function, either scalac infers all type parameters or the user has to
specify all of them. Often we have functions where there are one or more types that are inferable but not all of them. For example, there is helper function in `OptionT` that creates an `OptionT[F, A]` from an `A`. It could be written as:

```tut:silent
import cats._
import cats.implicits._
import cats.data.OptionT
```
```tut:book
def pure[F[_], A](a: A)(implicit F: Applicative[F]): OptionT[F, A] =
OptionT(F.pure(Some(a)))
pure[List, Int](1)
```

Note that the type `A` should've been given by the `a: A` argument, but since scalac cannot infer `F[_]`, the user still has to specify all type params.
In cats, we use a technique described in
Rob Norris’s [Kinda-Curried Type Parameters](https://tpolecat.github.io/2015/07/30/infer.html) to overcome this restriction of scala inference. Here is a version of the `pure` using this technique in cats.

```scala
package cats.data

object OptionT {

private[data] final class PurePartiallyApplied[F[_]](val dummy: Boolean = true ) extends AnyVal {
def apply[A](value: A)(implicit F: Applicative[F]): OptionT[F, A] =
OptionT(F.pure(Some(value)))
}

def pure[F[_]]: PurePartiallyApplied[F] = new PurePartiallyApplied[F]
}
```

We introduced an intermediate or, as the name suggested, type parameter partially applied type `PurePartiallyApplied` to divide the function into two steps: the first step is a construction of the partially applied type, for which the type `F[_]` is given by the user; the second step is the `apply` method inside partially applied type, for which the `A` can be inferred from the argument. Now we can write:
```tut:book
OptionT.pure[List](1)
```

The user doesn't need to specify the type `A` which is given by the parameter.

You probably noticed that there is a `val dummy: Boolean` in the `PurePartiallyApplied` class. This is a trick we used
to make this intermediate class a [Value Class](http://docs.scala-lang.org/overviews/core/value-classes.html) so that there is no cost of allocation, i.e. at runtime, it doesn't create an instance of `PurePartiallyApplied`. We also hide this partially applied class by making it package private and placing it inside an object.


#### TODO:
Once we drop 2.10 support, AnyVal-extending class constructor parameters can be marked as private.
Once we drop 2.10 support, AnyVal-extending class constructor parameters can be marked as private.
4 changes: 2 additions & 2 deletions free/src/main/scala/cats/free/Free.scala
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ object Free {
new FreeInjectKPartiallyApplied

/**
* Pre-application of an injection to a `F[A]` value.
* Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics.
*/
final class FreeInjectKPartiallyApplied[F[_], G[_]] private[free] {
private[free] final class FreeInjectKPartiallyApplied[F[_], G[_]](val dummy: Boolean = true ) extends AnyVal {
def apply[A](fa: F[A])(implicit I: InjectK[F, G]): Free[G, A] =
Free.liftF(I.inj(fa))
}
Expand Down

0 comments on commit e5ff7d1

Please sign in to comment.