Closed
The Closed class extends Profunctor class to work with functions.
trait Closed[P[_, _]] extends Profunctor[P] {
  def closed[A, B, C](pab: P[A, B]): P[C => A, C => B]
}
In order to understand the Closed profunctor, we will specialize the P[_, _] type constructor to the Function type. Now the type signature
of the closed method is:
def closed[A, B, C](pab: A => B): (C => A) => C => B
Consider the A an input and the B an output. We can think of the closed method as if we get an argument that can map from A to B,
then we would return a function given a mapping from another input C to the initial input A, returns a function from C to B, meaning
the closed method lets us pre compose the
  A -> B with another function. Another way to think about it is, if we consider the A -> B as our
main business logic the closed method lets us run some initialization logic before our main function.
Function as Closed profunctor
Let's implement an instance of Closed for Function
import proptics.profunctor.Closed
// import proptics.profunctor.Closed
implicit def closedFunction: Closed[* => *] = new Closed[* => *] {
  override def closed[A, B, C](pab: A => B): (C => A) => C => B = c2a => pab compose c2a
  override def dimap[A, B, C, D](fab: A => B)(f: C => A)(g: B => D): C => D = g compose fab compose f
}
// closedFunction: proptics.profunctor.Closed[[α$0$, β$1$]α$0$ => β$1$]
val currentYear: Int = 2020 
// currentYear: Int = 2020
def calculatePassedYears(year: Int): Int = currentYear - year
// calculatePassedYears: (year: Int)Int
Consider the calculatePassedYears our main business logic. The method takes an Int input, which represent a year, and returns how many years
have passed since the current one. Using the closed we can add an initialization logic in order to extract an Int from another type.
final case class Person(id: String, name: String, yearOfBirth: Int)
// defined class Person
val calculateAge: Person => Int = 
  closedFunction.closed[Int, Int, Person](calculatePassedYears)(_.yearOfBirth)
// calculateAge: Person => Int = scala.Function1$$Lambda$11142/0x00000008028678e8@49f4dc14
calculateAge(Person("123", "Samuel Eilenberg", 1913))
// res0: Int = 107
We extracted an Int using (_.yearOfBirth) which is our initialization function and then calculate the age
using our main logic calculatePassedYears.
Consider a UserRegistration class that represents a domain entity for a registration form, we can use closed method
to add an initialization logic that converts the raw data into a domain user object using pre composition, but we can also
use post composition in order to return a type different from the return type of our main business logic
import java.util.UUID
// import java.util.UUID
final case class User(id: String, name: String, age: Int)
// defined class User
final case class UserRegistration(name: String, yearOfBirth: Int)
// defined class UserRegistration
def generateUniqueId: String = UUID.randomUUID.toString
// generateUniqueId: String
def registerUser(registration: UserRegistration): User = {
  val user = User(generateUniqueId, registration.name, calculatePassedYears(registration.yearOfBirth))
  // ... persist to db
  user
}
// registerUser: (registration: UserRegistration)User
val submitForm: ((String, Int)) => User = 
  closedFunction.closed[UserRegistration, User, (String, Int)](registerUser)(UserRegistration.tupled)
// submitForm: ((String, Int)) => User = scala.Function1$$Lambda$11142/0x00000008028678e8@9590db8
submitForm(("Samuel Eilenberg", 1913))
res1: User = User(1b016d65-bbe8-419e-8cfa-7f26c82e01e3,Samuel Eilenberg,107)