From 157cbaed12db0973b9662ccc03aa6b19c27092cb Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Sun, 15 Mar 2020 09:42:09 +0100 Subject: [PATCH] Add instances for functions of all arities `Monad` and `Distributive` instances. `Defer` was not stack safe. --- .../main/scala/cats/derived/function.scala | 42 +++++++++++++ .../scala/cats/derived/util/fnGeneric.scala | 23 +++++++ .../test/scala/cats/derived/function.scala | 60 +++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 core/src/main/scala/cats/derived/function.scala create mode 100644 core/src/main/scala/cats/derived/util/fnGeneric.scala create mode 100644 core/src/test/scala/cats/derived/function.scala diff --git a/core/src/main/scala/cats/derived/function.scala b/core/src/main/scala/cats/derived/function.scala new file mode 100644 index 00000000..ac9cacad --- /dev/null +++ b/core/src/main/scala/cats/derived/function.scala @@ -0,0 +1,42 @@ +package cats.derived + +import cats.derived.util.FnGeneric +import cats.instances.function._ +import cats.{Distributive, Functor, Monad} +import shapeless._ + +object function extends FunctionInstances { + + implicit def kittensMkMonadForFunctionN[F[_], L <: _ :: _ :: _]( + implicit gen: FnGeneric.Aux[F, L] + ): Monad[F] = new Monad[F] { + private[this] val F = Monad[L => *] + + def pure[A](x: A): F[A] = + gen.from(F.pure(x)) + + override def map[A, B](fa: F[A])(f: A => B): F[B] = + gen.from(F.map(gen.to(fa))(f)) + + def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = + gen.from(F.flatMap(gen.to(fa))(a => gen.to(f(a)))) + + def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = + gen.from(F.tailRecM(a)(a => gen.to(f(a)))) + } +} + +private[derived] abstract class FunctionInstances { + + implicit def kittensMkDistributiveForFunctionN[F[_], L <: _ :: _ :: _]( + implicit gen: FnGeneric.Aux[F, L] + ): Distributive[F] = new Distributive[F] { + private[this] val F = Distributive[L => *] + + def map[A, B](fa: F[A])(f: A => B): F[B] = + gen.from(F.map(gen.to(fa))(f)) + + def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => F[B]): F[G[B]] = + gen.from(F.distribute(ga)(a => gen.to(f(a)))) + } +} diff --git a/core/src/main/scala/cats/derived/util/fnGeneric.scala b/core/src/main/scala/cats/derived/util/fnGeneric.scala new file mode 100644 index 00000000..b0cb2b5a --- /dev/null +++ b/core/src/main/scala/cats/derived/util/fnGeneric.scala @@ -0,0 +1,23 @@ +package cats.derived.util + +import shapeless.HList +import shapeless.ops.function.{FnFromProduct, FnToProduct} + +trait FnGeneric[F[_]] { + type A <: HList + def to[B](f: F[B]): A => B + def from[B](f: A => B): F[B] +} + +object FnGeneric { + type x + type Aux[F[_], L <: HList] = FnGeneric[F] { type A = L } + + implicit def instance[F[_], L <: HList]( + implicit toP: FnToProduct.Aux[F[x], L => x], fromP: FnFromProduct.Aux[L => x, F[x]] + ): Aux[F, L] = new FnGeneric[F] { + type A = L + def to[B](f: F[B]): A => B = toP(f.asInstanceOf[F[x]]).asInstanceOf[A => B] + def from[B](f: A => B): F[B] = fromP(f.asInstanceOf[A => x]).asInstanceOf[F[B]] + } +} diff --git a/core/src/test/scala/cats/derived/function.scala b/core/src/test/scala/cats/derived/function.scala new file mode 100644 index 00000000..45c5acb7 --- /dev/null +++ b/core/src/test/scala/cats/derived/function.scala @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 Miles Sabin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.derived + +import cats.Eq +import cats.laws.discipline.{DistributiveTests, ExhaustiveCheck, MonadTests} +import cats.derived.function._ +import cats.instances.all._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ + +class FunctionSuite extends KittensSuite { + type B = Boolean + type F0[R] = () => R + type F1[R] = B => R + type F2[R] = (B, B) => R + type F4[R] = (B, B, B, B) => R + type F8[R] = (B, B, B, B, B, B, B, B) => R + + implicit val exhaustiveCheck4: ExhaustiveCheck[(B, B, B, B)] = + ExhaustiveCheck.instance(for { + (a, b) <- ExhaustiveCheck[(B, B)].allValues + (c, d) <- ExhaustiveCheck[(B, B)].allValues + } yield (a, b, c, d)) + + implicit val exhaustiveCheck8: ExhaustiveCheck[(B, B, B, B, B, B, B, B)] = + ExhaustiveCheck.instance(for { + (a, b, c, d) <- ExhaustiveCheck[(B, B, B, B)].allValues + (e, f, g, h) <- ExhaustiveCheck[(B, B, B, B)].allValues + } yield (a, b, c, d, e, f, g, h)) + + implicit def eqFn4[R: Eq]: Eq[F4[R]] = Eq.by(_.tupled) + implicit def eqFn8[R: Eq]: Eq[F8[R]] = Eq.by(_.tupled) + + checkAll("Monad[Function0]", MonadTests[F0].monad[B, B, B]) + checkAll("Monad[Function1]", MonadTests[F1].monad[B, B, B]) + checkAll("Monad[Function2]", MonadTests[F2].monad[B, B, B]) + checkAll("Monad[Function4]", MonadTests[F4].monad[B, B, B]) + checkAll("Monad[Function8]", MonadTests[F8].monad[B, B, B]) + + checkAll("Distributive[Function0]", DistributiveTests[F0].distributive[B, B, B, Option, Function0]) + checkAll("Distributive[Function1]", DistributiveTests[F1].distributive[B, B, B, Option, Function0]) + checkAll("Distributive[Function2]", DistributiveTests[F2].distributive[B, B, B, Option, Function0]) + checkAll("Distributive[Function4]", DistributiveTests[F4].distributive[B, B, B, Option, Function0]) + checkAll("Distributive[Function8]", DistributiveTests[F8].distributive[B, B, B, Option, Function0]) +}