Profunctor

intermediate

Before reading this typeclass we recommend you to understand Contravariance first. But for making things easier we will consider Contravariance as the ability to flip composition.

Profunctors are Bifunctors that are contravariant in their first argument and covariant in the second one.

The core operation of the Profunctor typeclass is dimap (as bimap was already taken for Bifunctor).

fun <A, B, C, D> Kind2<F, A, B>.dimap(fl: (C) -> A, fr: (B) -> D): Kind2<F, C, D>

The main difference between bimap and dimap is the function they accept as their first argument:

• bimap: fl: (A) -> C
• dimap: fl: (C) -> A

And how this works? Well, if we think in terms of function composition, functions can be composed in both directions:

import arrow.core.*

val sum2: (Int) -> Int = { x -> x + 2 }
val str: (Int) -> String = { x -> x.toString() }

val f = str compose sum2
val g = sum2 andThen str
f(4) == g(4)
// true

Functions are a binary type constructor of an input type and an output type. It is implemented in Function1.

So, if we have a function (A) -> B and a Profunctor instance for it, we can make the following transformation with dimap: ((C) -> A) -> ((A) -> B) -> ((B) -> D).

Example:

import arrow.core.*
import arrow.data.extensions.*
import arrow.core.extensions.function1.profunctor.*

val fab: Function1<Double, Double> = { x: Double -> x * 3 }.k()
val f: (Int) -> Double = { x -> x.toDouble() / 2 }
val g: (Double) -> String = { x -> "Result: \$x" }

val h = Function1.profunctor().run { fab.dimap(f, g) }
h(4)
// Result: 6.0

Main Combinators

Kind2<F, A, B>#bimap

Contramap on the first type parameter and map on the second type parameter

fun Kind2<F, A, B>.dimap(fl: (C) -> A, fr: (B) -> D): Kind2<F, C, D>

import arrow.core.*
import arrow.data.extensions.*

val f: Function1<Int, Int> = { x: Int -> x + 10 }.k()
val fl: (String) -> Int = { x -> x.toInt() }
val fr: (Int) -> List<Int> = { x -> List(x) { x } }

val g: Function1<String, List<Int>> = Function1.profunctor().run { f.dimap(fl, fr) }
g("6")
// [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]

Other combinators

For a full list of other useful combinators available in Profunctor see the Source

Laws

Arrow provides ProfunctorLaws in the form of test cases for internal verification of lawful instances and third party apps creating their own Profunctor instances.

Data types

 Module Data types arrow.core Function1 arrow.data AndThen, Cokleisli