Skip to content

Commit 375144b

Browse files
committed
Add Functor.widen
I have implemented this via `map` and `Liskov.apply`. In many cases, we could probably cheat by using `Liskov.subst` along with a variance hack (such as in `IList.widen`) or `asInstanceOf`, which may be more performant than using `map`. However, this would be a dirty hack, and usually those are accompanied with bugs. Can anyone recommend a safe yet more performant way to write this generally? If not, we probably will want to consider overriding the implementation for more instances for the sake of performance. At this point I have only overridden the implementation for `IList`.
1 parent 35e935a commit 375144b

File tree

4 files changed

+19
-0
lines changed

4 files changed

+19
-0
lines changed

core/src/main/scala/scalaz/Functor.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package scalaz
1515
////
1616
trait Functor[F[_]] extends InvariantFunctor[F] { self =>
1717
////
18+
import Liskov.<~<
1819

1920
/** Lift `f` into `F` and apply to `F[A]`. */
2021
def map[A, B](fa: F[A])(f: A => B): F[B]
@@ -87,6 +88,13 @@ trait Functor[F[_]] extends InvariantFunctor[F] { self =>
8788
implicit def G = G0
8889
}
8990

91+
/**
92+
* Functors are covariant by nature, so we can treat an `F[A]` as
93+
* an `F[B]` if `A` is a subtype of `B`.
94+
*/
95+
def widen[A, B](fa: F[A])(implicit ev: A <~< B): F[B] =
96+
map(fa)(ev.apply)
97+
9098
trait FunctorLaw extends InvariantFunctorLaw {
9199
/** The identity function, lifted, is a no-op. */
92100
def identity[A](fa: F[A])(implicit FA: Equal[F[A]]): Boolean = FA.equal(map(fa)(x => x), fa)

core/src/main/scala/scalaz/IList.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,9 @@ sealed abstract class IListInstances extends IListInstance0 {
603603
}
604604
loop(fa)
605605
}
606+
607+
override def widen[A, B](fa: IList[A])(implicit ev: A <~< B): IList[B] =
608+
fa.widen[B]
606609
}
607610

608611

core/src/main/scala/scalaz/syntax/FunctorSyntax.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package syntax
55
final class FunctorOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Functor[F]) extends Ops[F[A]] {
66
////
77
import Leibniz.===
8+
import Liskov.<~<
89

910
final def map[B](f: A => B): F[B] = F.map(self)(f)
1011
final def distribute[G[_], B](f: A => G[B])(implicit D: Distributive[G]): G[F[B]] = D.distribute(self)(f)
@@ -19,6 +20,7 @@ final class FunctorOps[F[_],A] private[syntax](val self: F[A])(implicit val F: F
1920
final def fpoint[G[_]: Applicative]: F[G[A]] = F.map(self)(a => Applicative[G].point(a))
2021
final def >|[B](b: => B): F[B] = F.map(self)(_ => b)
2122
final def as[B](b: => B): F[B] = F.map(self)(_ => b)
23+
final def widen[B](implicit ev: A <~< B): F[B] = F.widen(self)
2224
////
2325
}
2426

tests/src/test/scala/scalaz/FunctorTest.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,10 @@ object FunctorTest extends SpecLite {
2323
"fpair" in {
2424
some(1).fpair must_===(some((1, 1)))
2525
}
26+
27+
"widen" ! forAll { ola: Option[List[Int]] =>
28+
import std.iterable._
29+
val oia: Option[Iterable[Int]] = ola
30+
ola.widen[Iterable[Int]] must_===(oia)
31+
}
2632
}

0 commit comments

Comments
 (0)