0

Learning idiomatic FP with Scala and Cats. I have the following code...

package org.economicsl.mechanisms

import cats._
import cats.implicits._

trait Preference[-A] {
  self =>

  def compare(a1: A, a2: A): Int

  final def ordering[A1 <: A]: Ordering[A1] = {
    new Ordering[A1] {
      def compare(a1: A1, a2: A1): Int = {
        self.compare(a1, a2)
      }
    }
  }

}


object Preference {

  implicit val contravariant: Contravariant[Preference] = {
    new Contravariant[Preference] {
      def contramap[A, B](fa: Preference[A])(f: B => A): Preference[B] = {
        new Preference[B] {
          def compare(b1: B, b2: B): Int = {
            fa.compare(f(b1), f(b2))
          }
        }
      }
    }
  }

  /** Defines a preference for a particular alternative. */
  def particular[A](alternative: A): Preference[A] = {
    new Preference[A] {
      def compare(a1: A, a2: A): Int = {
        if ((a1 != alternative) & (a2 == alternative)) {
          -1
        } else if ((a1 == alternative) & (a2 != alternative)) {
          1
        } else {
          0
        }
      }
    }
  }

}

/** Base trait defining a generic social welfare function.
  *
  * A social welfare function aggregates the preferences of individual agents
  * into a common preference ordering.
  */
trait SocialWelfareFunction[-CC <: Iterable[P], +P <: Preference[A], A]
  extends (CC => P)


/** Companion object for the `SocialWelFareFunction` trait. */
object SocialWelfareFunction {

  val setInvariant: Invariant[({ type F[A] = SocialWelfareFunction[Set[Preference[A]], Preference[A], A] })#F] = {
    new Invariant[({ type F[A] = SocialWelfareFunction[Set[Preference[A]], Preference[A], A] })#F] {
      def imap[A, B](fa: SocialWelfareFunction[Set[Preference[A]], Preference[A], A])(f: A => B)(g: B => A): SocialWelfareFunction[Set[Preference[B]], Preference[B], B] = {
        new SocialWelfareFunction[Set[Preference[B]], Preference[B], B] {
          def apply(preferences: Set[Preference[B]]): Preference[B] = {
            fa(preferences.map(pb => pb.contramap(f))).contramap(g)
          }
        }
      }
    }
  }

  val seqInvariant: Invariant[({ type F[A] = SocialWelfareFunction[Seq[Preference[A]], Preference[A], A] })#F] = {
    new Invariant[({ type F[A] = SocialWelfareFunction[Seq[Preference[A]], Preference[A], A] })#F] {
      def imap[A, B](fa: SocialWelfareFunction[Seq[Preference[A]], Preference[A], A])(f: A => B)(g: B => A): SocialWelfareFunction[Seq[Preference[B]], Preference[B], B] = {
        new SocialWelfareFunction[Seq[Preference[B]], Preference[B], B] {
          def apply(preferences: Seq[Preference[B]]): Preference[B] = {
            fa(preferences.map(pb => pb.contramap(f))).contramap(g)
          }
        }
      }
    }
  }
}

In the companion object for SocialWelfareFunction I define two invariant functors. The implementations are nearly identical: the first functor uses a Set to store Preference[A] instances and the second functor uses a Seq.

Is there a way to abstract over the type of collection in which the preferences are stored? I am open to including other Typelevel dependencies if required for a solution.

davidrpugh
  • 4,363
  • 5
  • 32
  • 46
  • What's `Preference`, and why does it have `contramap` on it? Could one maybe add some stub implementations to make the snippet compilable? – Andrey Tyukin Jul 10 '18 at 19:04

1 Answers1

0

You could do something like this:

  def invariant[C[X] <: Iterable[X] : Functor]: Invariant[({ type F[A] = SocialWelfareFunction[C[Preference[A]], Preference[A], A] })#F] = {
    new Invariant[({ type F[A] = SocialWelfareFunction[C[Preference[A]], Preference[A], A] })#F] {
      def imap[A, B](fa: SocialWelfareFunction[C[Preference[A]], Preference[A], A])(f: A => B)(g: B => A): SocialWelfareFunction[C[Preference[B]], Preference[B], B] = {
        new SocialWelfareFunction[C[Preference[B]], Preference[B], B] {
          def apply(preferences: C[Preference[B]]): Preference[B] = {
            fa(Functor[C].map(preferences)(pb => pb.contramap(f))).contramap(g)
          }
        }
      }
    }
  }

Then for types that cats has a Functor for, you can just do:

  val listInvariant = invariant[List]

Annoyingly, it seems they don't have built-in Functors for Set and Seq, but they're pretty easy to define yourself:

  private[this] implicit val setFunctor = new Functor[Set] {
    override def map[A, B](s: Set[A])(f: A => B): Set[B] = s.map(f)
  }

  val setInvariant = invariant[Set]
Joe K
  • 18,204
  • 2
  • 36
  • 58
  • Can you explain the type parameter restrictions `[C[X] <: Iterable[X] : Functor]`? Particularly the need for `: Functor`? – davidrpugh Jul 11 '18 at 02:54
  • `C` must be a subclass of `Iterable` because of the type restrictions on `SocialWelfareFunction`. Then `: Functor` ensures you have an implicit `Functor[C]`, which gives you the ability to map over that `C` type. Without it, you can try using `Iterable`'s `map` method, but then you get an `Iterable` back, not a `C`. There may be a way to do it using the standard library's `CanBuildFrom` stuff, but since you're already deep into cats, I think it's easier/cleaner to do it this way. – Joe K Jul 11 '18 at 06:50
  • Understood. I prefer your solution to using an `implicit cbf: CanBuildFrom` construct as I think that the new collections API is going to move away from using `CanBuildFrom` in the future. – davidrpugh Jul 11 '18 at 13:26