The wonder of context functions
Tags: scala, programming, architecture
Published: 2021-12-21 16:11:33

There have been many new features released in Scala 3, but one of the ones that had seemingly little use to me was context functions.

Context functions in Scala 3 are the ability to express a function that needs context (not direct input) in order to evaluate. This probably doesn't mean a whole lot in black and white, so I'll demonstrate with an example:

```scala
//a method that needs two Int inputs
def add(a: Int, b: Int) = a + b

//a function that needs two Int inputs
val add = (a: Int, b: Int) = a + b

//a method in scala 2 style that requires context
def getExecutionContext(implicit ec: ExecutionContext): Unit = ec.reportFailure(new Exception("foo"))

//a function that requires context
val getExecutionContext = (ec: ExecutionContext) ?=> ec.reportFailure(new Exception("foo"))
```

Basically, context functions are to methods that require context what functions are to methods. You can capture and pass around a context function, and even assign it to a val. This does not seem to be much to write home about, but it's actually a very powerful concept.

## Dependency Injection

Dependency injection is something that's frequently needed in Scala, and the community has produced a number of solutions for it. Some examples are 
* Macwire - Stitches together classes based on their constructors to provide compile time dependency injection
* Reader monad and Kleisli - The reader monad is a category theory compatible way to build up and inject dependencies.
* ZIO's RIO and URIO types - These effect types carry around context with them and can be used for dependency injection purposes
* Implicits - these have been suggested as a method of dependency injection

With context functions, implicits become incredibly suitable for dependency injection, and may be the best option for the pattern. This is because the parameters of context functions:

* Can curry: `(A,B) ?=> C` can become `A ?=> B ?=> C` automatically
* Can uncurry: `A ?=> B ?=> C` is equivalent to `(A,B) ?=> C`
* Don't care about order: `A ?=> B ?=> C` is equivalent to `B ?=> A ?=> C`
* Can be inferred: `val getExecutionContext: ExecutionContext ?=> Unit = summon[ExecutionContext].report(new Exception("foo"))` is equivalent to our previous implementation.

These four properties add up to a powerful dependency injection mechanism:

```scala
trait Fooer:
  def foo(a: Int): Int

type Fooed[A] = Fooer ?=> A

val fooer: Fooed[Fooer] = summon[Fooer]

def fn(i: Int): Fooed[String] = 
  fooer.foo(i)

val value: Fooed[Double] = fn(3).toDouble

@main
def run = 
  given Fooer with
    def foo(a: Int) = a * 3

  println(value)
```

As you can see above, `fn` needed a `Fooer`, which needed to be passed into `value` if `value` wanted to use `fn`. In Scala 2, this would've necessitated that `value` be a method, but now it can be a val. But what about mixing dependencies?

```scala
trait Fooer:
  def foo(a: Int): Int

type Fooed[A] = Fooer ?=> A

val fooer: Fooed[Fooer] = summon[Fooer]

trait Barrer:
  def bar(d: Double): String

type Barred[A] = Barrer ?=> A

val barrer: Barred[Barrer] = summon[Barrer]

def method(i: Int): Fooed[String] = 
  fooer.foo(i).toString

def fn(d: Double): Barred[Short] = 
  barrer.bar(d).toShort
```

These dependencies can be composed

```scala
val value1: Barred[Fooed[String]] = 
  (method(3)*fn(2.0)).toString
```

Reordered
```scala
val value2: Fooed[Barred[String]] = value1
```

And eliminated piece by piece:
```scala
val value3: Fooed[String] = 
  given Barrer with
    def bar(d: Double) = d.toString
  value2
```

As you can see, context functions make for a powerful dependency injection mechanism. They also don't require mapping, flatmapping, etc like the Reader monad requires. Best of all, this abstraction doesn't have the runtime overhead of the Reader monad or Kleisli, and so can be used to add context to any effect type without paying a price for it.

Regarding real-world uses of this concept, I used it today to put [natchez](https://github.com/tpolecat/natchez) tracing in my http4s project. While the project is still small, I was shocked at the lack of invasiveness of this approach compared to usage of Kleisli to achieve the same effect. 

While Scala 3 has introduced a massive amount of new, helpful concepts, the introduction of context functions may well be one of the most helpful, and yet most underrated of these.