Reputation: 53
Say that a Predicate is a function A => Boolean, I want to implement an instance of Cats's "Contravariant Functor" type class for predicates. I've also got an implicit class PredicateOps that defines union and intersect operators for predicates.
I have been able to get the instance to work using a type alias:
type Predicate[A] = A => Boolean
implicit val predicateContra = new Contravariant[Predicate] {
override def contramap[A, B](fa: Predicate[A])(f: B => A): Predicate[B] =
(b: B) => fa(f(b))
}
But when I do that, I have to coerce all my predicate functions to the alias like this:
val even: Predicate[Int] = (i: Int) => i % 2 == 0
Which I find annoying. So I wondered whether, rather than use the type alias, I could define predicateContra directly for a Function1 from a type variable A to Boolean, but I couldn't get it to work. Both of the following ideas give me a compiler error:
implicit val predicateContra = new Contravariant[Function1[_, Boolean]] {
// "Function1[_, Boolean] takes no type parameters, expected: one"
implicit def predicateContra[A] = new Contravariant[Function1[A, Boolean]] {
// "A => Boolean takes no type parameters, expected: one"
How can I tell the compiler that the first parameter of my Function1 should remain a "hole", while the second should be fixed to boolean? Is that even possible? Looking at the source code for cats, I found asterisks as type parameters in a bunch of places, but that too didn't work for me.
Upvotes: 1
Views: 434
Reputation: 9698
You can use kind projector, which allows you to refer to the "type hole" with an asterisk (*
).
This enables very simple syntax for defining a type of kind * -> *
, that is, a unary type constructor (takes a single type to produce type). For example, a type that takes some type A
to produce the type Map[A, Int]
can be written simply as Map[*, Int]
.
Then your code becomes:
val strToBool: String => Boolean = _.contains("1")
val intToStr: Int => String = _.toString
def predicateContra =
new Contravariant[Function1[*, Boolean]] {
override def contramap[A, B](fa: A => Boolean)(f: B => A): B => Boolean =
(b: B) => fa(f(b))
}
predicateContra.contramap(strToBool)(intToStr)(42) // false
predicateContra.contramap(strToBool)(intToStr)(41) // true
If you don't want to use extra libraries, you can do it in plain Scala in a somewhat uglier way by using a type lambda:
def predicateContra =
new Contravariant[({ type lambda[A] = Function1[A, Boolean] })#lambda] {
...
}
Upvotes: 5