2

I often find myself having to perform what is pretty much the same operation on both a value and also a functor of that value. I usually achieve this with two implicit classes, like this:

implicit class Apimped(a: A) {
  def doSomething: B = ???
}

implicit class FApimped[F[_]: Functor](fa: F[A]) {
  def doSomething: F[B] = Functor[F].map(fa)(a => a.doSomething)
}

So then I can do this, for example:

a.doSomething //B
Option(a).doSomething //Option[B]

However, it seems a bit unwieldy having to write two implicit classes (often for each value type) to do this. My question is, is there anyway to achieve the above with only a single implicit class? That is, the map operation would be implicit in cases when you call doSomething on a functor of the value. Thanks.

Lasf
  • 2,536
  • 1
  • 16
  • 35
  • What is the `doSomething`? – Nagarjuna Pamu Aug 29 '18 at 18:48
  • @pamu any method that returns a value of type `B` – Lasf Aug 29 '18 at 18:49
  • does `doSomething` use `map` function in any way? – Nagarjuna Pamu Aug 29 '18 at 18:58
  • @pamu Not necessarily. You could assume `type A = Int` (or whatever) and `type B = String` and then `def doSomething: B = a.toString`. Or `doSomething` could hit seven different databases, mapping over each result, and then return a string :) – Lasf Aug 29 '18 at 19:00
  • 2
    1) Why do you call those things "typeclasses", they seem to be just implicit wrappers with additional pimped methods? 2) What does your `ATypeClass` achieve what `FAtypeClass[Id]` doesn't do anyway? – Andrey Tyukin Aug 29 '18 at 19:28
  • @AndreyTyukin 1) My bad. 2) You may have answered my question. Is there something to import that will allow me to call `a.doSomething` where `a` is of type `A`? – Lasf Aug 29 '18 at 19:34
  • @Lasf An explicit type ascription `(a: Id[A]).doSomething` definitely does the job, but it looks a bit clunky. I'm currently experimenting with a chain of implicits of different priorities that could be passed to `doSomething` so it wraps the input into an `Id[_]` automatically. – Andrey Tyukin Aug 29 '18 at 19:44
  • [this answer](https://stackoverflow.com/questions/48720903/scala-and-cats-implicit-conversion-to-identity-monad) seems also vaguely related... – Andrey Tyukin Aug 29 '18 at 20:24

1 Answers1

2

I don't know whether it's in Scalaz/Cats (maybe, cannot guarantee that it's not there), but in principle, it does work. Here is a little demo without any dependencies that demonstrates the principle.

Assume you have these typeclasses from either Scalaz or Cats:

import scala.language.higherKinds

trait Functor[F[_]] {
  def map[A, B](a: F[A])(f: A => B): F[B]
}

type Id[X] = X
implicit object IdFunctor extends Functor[Id] {
  def map[A, B](a: A)(f: A => B): B = f(a)
}
implicit object OptionFunctor extends Functor[Option] {
  def map[A, B](a: Option[A])(f: A => B) = a map f
}

you can then either write or find in the library a typeclass that works like the following contraption:

trait EverythingIsAlwaysAFunctor[A, B, F[_]] {
  def apply(a: A): F[B]
  def functor: Functor[F]
}

object EverythingIsAlwaysAFunctor {
  implicit def functorIsFunctor[A, F[_]](implicit f: Functor[F])
  : EverythingIsAlwaysAFunctor[F[A], A, F] = {
    new EverythingIsAlwaysAFunctor[F[A], A, F] {
      def apply(fa: F[A]): F[A] = fa
      def functor: Functor[F] = f
    }
  }

  implicit def idIsAlsoAFunctor[A]
  : EverythingIsAlwaysAFunctor[A, A, Id] = {
    new EverythingIsAlwaysAFunctor[A, A, Id] {
      def apply(a: A): Id[A] = a
      def functor: Functor[Id] = implicitly[Functor[Id]]
    }
  }
}

This thing does the following:

  1. If the value A is already of shape F[B] for some functor F, then uses this functor
  2. In all other cases, it pretends that A is actually Id[A]

Now you can write your DoSomething-pimp-my-library-syntax thing with a single doSomething method:

implicit class DoSomething[A, F[_]](a: A)(
  implicit eiaaf: EverythingIsAlwaysAFunctor[A, Int, F]
) {
  def doSomething: F[String] = eiaaf.functor.map(eiaaf(a))("*" * _)
}

and then it just works in all cases:

val x = Option(42).doSomething
val y = 42.doSomething

println(x)
println(y)

prints:

Some(******************************************)
******************************************
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • 2
    This is impressive, but feels like there should be a simpler way (though I don't know what it would be). `(42: Id[Int]).doSomething` like you suggested in the comments doesn't seem that bad in comparison. – Joe K Aug 29 '18 at 20:03
  • Thanks a ton for this. I adapted this to work with `Monad` also and it's really helpful. I have a question for curiosity's sake: How is the compiler prioritizing using `functorIsFunctor` over `idIsAlsoAFunctor` (since I assume everything qualifies as `Id`)? – Lasf Aug 30 '18 at 01:12
  • 1
    @Lasf I was already prepared to make a hierarchy of classes for the implicits, but then I tried the obvious solution, and it worked immediately... I can't pinpoint the exact part of the spec right now that guarantees that it must work this way. If in doubt, do it [like Predef with LowPriorityImplicits](https://www.scala-lang.org/api/current/scala/Predef$.html). – Andrey Tyukin Aug 30 '18 at 12:12