Skip to content

Commit

Permalink
Add instances for functions of all arities (#223)
Browse files Browse the repository at this point in the history
`Monad` and `Distributive` instances.
`Defer` was not stack safe.
  • Loading branch information
joroKr21 authored Apr 21, 2020
1 parent 6159d8a commit da9facf
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 0 deletions.
42 changes: 42 additions & 0 deletions core/src/main/scala/cats/derived/function.scala
Original file line number Diff line number Diff line change
@@ -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))))
}
}
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/derived/util/fnGeneric.scala
Original file line number Diff line number Diff line change
@@ -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]]
}
}
60 changes: 60 additions & 0 deletions core/src/test/scala/cats/derived/function.scala
Original file line number Diff line number Diff line change
@@ -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])
}

0 comments on commit da9facf

Please sign in to comment.