---
name: kindlings-factory-instance
description: >
  Factory instance pattern for generating final type class instances. Lambda factories for
  monomorphic (kind *), erasure-based factories with Witness types for polymorphic (kind * -> *).
  Benchmark data showing lambda vs anonymous class performance.
paths:
  - "**/runtime/*Factories*.scala"
  - "**/runtime/*Factory*.scala"
user-invocable: false
---

# Skill: Factory Instance Pattern

Use this skill when generating the final type class instance in macro code. Instead of emitting
`new TypeClass[A] { ... }` anonymous classes, use factory methods that accept lambdas.

**Reference benchmark:** `docs/research/anon-vs-lambda/analysis.md`

## Why factory methods instead of anonymous classes

When a macro generates `new TypeClass[A] { def method(x: A) = ... }`, the Scala compiler creates
a **separate `.class` file** for every expansion. With N derived types, this produces N class files
that the JVM must load, verify, and intern.

The factory pattern (`TypeClassFactory.instance[A](lambda)`) avoids this:

- The factory's inner class is defined **once** (1 `.class` file, shared)
- Per-type lambdas compile to `invokedynamic` + `LambdaMetafactory` — **no `.class` files on disk**
- The JVM generates lightweight hidden classes at runtime for lambdas

### Measured impact (JDK 25 GraalCE, JMH, Scala 2.13 + 3.8.2)

| Metric | Anonymous class | Factory + lambda |
|---|---|---|
| Steady-state throughput | baseline | tied (0.95-1.05x, within error) |
| Instance creation speed | baseline | **2.5-3.2x faster** |
| Per-type `.class` files | 1 per type | **0 per type** |
| Total `.class` files (N types) | 2 + N | **constant** |

See `docs/research/anon-vs-lambda/` for full benchmark data, bytecode dumps, and JFR recordings.

## Pattern: Monomorphic type classes (kind `*`)

For type classes with a fixed type parameter (`Show[A]`, `Eq[A]`, `Hash[A]`, `Encoder[A]`, etc.),
the factory accepts ordinary `Function1`/`Function2` lambdas.

### Factory definition

Place in the module's `internal/runtime/` package (alongside existing runtime helpers):

```scala
// cats-derivation/internal/runtime/CatsDerivationFactories.scala
object CatsDerivationFactories {

  def showInstance[A](f: A => String): cats.Show[A] = new cats.Show[A] {
    def show(a: A): String = f(a)
  }

  def eqInstance[A](f: (A, A) => Boolean): cats.kernel.Eq[A] = new cats.kernel.Eq[A] {
    def eqv(x: A, y: A): Boolean = f(x, y)
  }

  def hashInstance[A](hashFn: A => Int, eqvFn: (A, A) => Boolean): cats.kernel.Hash[A] =
    new cats.kernel.Hash[A] {
      def hash(x: A): Int = hashFn(x)
      def eqv(x: A, y: A): Boolean = eqvFn(x, y)
    }
}
```

### Macro codegen change

**Before** (current — anonymous class per type):
```scala
cacheState.toValDefs.use { _ =>
  Expr.quote {
    new cats.kernel.Hash[A] {
      def hash(value: A): Int = Expr.splice(hashCallFor(Expr.quote(value)))
      def eqv(x: A, y: A): Boolean = Expr.splice(eqCallFor(Expr.quote(x), Expr.quote(y)))
    }
  }
}
```

**After** (factory + lambda):
```scala
cacheState.toValDefs.use { _ =>
  Expr.quote {
    CatsDerivationFactories.hashInstance[A](
      (value: A) => Expr.splice(hashCallFor(Expr.quote(value))),
      (x: A, y: A) => Expr.splice(eqCallFor(Expr.quote(x), Expr.quote(y)))
    )
  }
}
```

The generated bytecode uses `invokedynamic` for the lambdas instead of creating a new `.class` file.

## Pattern: Polymorphic type classes (kind `* -> *`)

Polymorphic type classes like `Functor[F[_]]` have methods with their own type parameters
(`def map[A, B](fa: F[A])(f: A => B): F[B]`). A plain `Function2` cannot represent this.

### The erasure-based factory approach

Since the JVM erases type parameters, `F[A]` and `F[B]` have the same runtime representation.
The factory accepts a function typed for arbitrary witness types and casts at the boundary:

```scala
object CatsDerivationFactories {

  // Arbitrary witness types for erasure-based polymorphic factories.
  // These are never instantiated — they exist only to give the lambda a concrete type signature
  // that the JVM erases to the same representation as any other type argument.
  sealed trait W1
  sealed trait W2

  def functorInstance[F[_]](
    mapFn: (F[W1], W1 => W2) => F[W2]
  ): cats.Functor[F] = new cats.Functor[F] {
    def map[A, B](fa: F[A])(f: A => B): F[B] =
      mapFn.asInstanceOf[(F[A], A => B) => F[B]].apply(fa, f)
  }
}
```

**Why this is safe:** At the JVM level, `F[W1]` and `F[A]` are both `Object` (or the
erasure of `F`). The `asInstanceOf` cast is a no-op at runtime — it only satisfies the compiler's
type checker. This is the same erasure trick used throughout the polymorphic derivation code
(see `ConsKMacrosImpl`: `hd.asInstanceOf[Any]`, `tl.asInstanceOf[F[Any]]`).

### Macro codegen for polymorphic types — complete worked example

**Before** (anonymous class with method-level type params `A`, `B`):
```scala
protected def deriveFunctorForCaseClass[F[_]](
    result: FunctorCaseClassResult[F],
    runSafe: RunSafe[MIO]
)(implicit FCtor: Type.Ctor1[F]): Expr[cats.Functor[F]] =
  Expr.quote {
    new cats.Functor[F] {
      def map[A, B](fa: F[A])(f: A => B): F[B] =
        Expr.splice {
          runSafe(deriveFunctorMapBody[F, A, B](
            result.FCtor, result.directFieldSet,
            Expr.quote(fa), Expr.quote(f)
          )(Type.of[A], Type.of[B]))
        }
    }
  }
```

**After** (factory + lambda with witness types `W1`, `W2`):
```scala
protected def deriveFunctorForCaseClass[F[_]](
    result: FunctorCaseClassResult[F],
    runSafe: RunSafe[MIO]
)(implicit FCtor: Type.Ctor1[F]): Expr[cats.Functor[F]] = {
  import hearth.kindlings.catsderivation.internal.runtime.CatsDerivationFactories
  Expr.quote {
    CatsDerivationFactories.functorInstance[F] {
      (fa: F[CatsDerivationFactories.W1], f: CatsDerivationFactories.W1 => CatsDerivationFactories.W2) =>
        val _ = fa
        val _ = f
        Expr.splice {
          runSafe(deriveFunctorMapBody[F, CatsDerivationFactories.W1, CatsDerivationFactories.W2](
            result.FCtor, result.directFieldSet,
            Expr.quote(fa), Expr.quote(f)
          )(Type.of[CatsDerivationFactories.W1], Type.of[CatsDerivationFactories.W2]))
        }
    }
  }
}
```

Key changes:
1. Method-level type params `A`, `B` are replaced by witness types `W1`, `W2` everywhere
2. `Type.of[A]` / `Type.of[B]` become `Type.of[CatsDerivationFactories.W1]` / `Type.of[CatsDerivationFactories.W2]`
3. The derive method (e.g. `deriveFunctorMapBody`) is called with `W1`/`W2` — it generates code
   for `F[W1]` which is a valid case class. Due to erasure, the bytecode works for any `A`/`B`.

### Three rules for polymorphic factories

#### Rule 1: `Type.Ctor1[F]` must be an implicit in scope

Hearth's `Expr.quote` on Scala 2 substitutes `F[_]` type constructors inside lambda parameter
type annotations **only when `Type.Ctor1[F]` is available as an implicit**. All polymorphic
macro methods already have `implicit FCtor: Type.Ctor1[F]`, so the factory pattern works.

Without this implicit, Scala 2 generates a raw tree with `Ident(TypeName("F"))` that fails
at the macro expansion site with `not found: type F`.

#### Rule 2: `W1`/`W2` must never be direct lambda parameter types

On Scala 3, sealed traits used as direct lambda parameter types generate JVM `checkcast`
instructions in lambda bridge methods. These fail at runtime when the actual argument is e.g.
`Integer` instead of `W1`.

**Safe** — `W1`/`W2` as type arguments (erased to `Object`/`Function1`):
```scala
(fa: F[W1], f: W1 => W2) => ...       // F[W1] erases to F, W1 => W2 erases to Function1
```

**Unsafe** — `W1`/`W2` as direct parameter types (generates checkcast):
```scala
(a: W1) => ...                         // FAILS: checkcast to W1 at runtime
(b: W2, a: W1) => ...                  // FAILS: checkcast to W2, W1
```

**Fix:** Use `Any` for positions where values pass through as raw `A`/`B`:

```scala
// Factory definition — pure takes a raw value, so use Any:
def pureInstance[F[_]](pureFn: Any => F[W1]): alleycats.Pure[F] =
  new alleycats.Pure[F] {
    def pure[A](a: A): F[A] = pureFn.asInstanceOf[Any => F[A]].apply(a)
  }

// foldLeft passes raw A and B values, so use Any:
def foldableInstance[F[_]](
    foldLeftFn: (F[W1], Any, (Any, Any) => Any) => Any,
    foldRightFn: (F[W1], Eval[Any], (Any, Eval[Any]) => Eval[Any]) => Eval[Any]
): cats.Foldable[F] = ...

// consK passes a raw A as head element:
def consKInstance[F[_]](consFn: (Any, F[W1]) => F[W1]): alleycats.ConsK[F] = ...
```

In the macro's `Expr.quote` block, the lambda parameter is `Any` and the splice body
builds the erased code accordingly.

#### Rule 3: Extra `G[_]` type constructors use `type AnyF[A] = Any`

Methods like `traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]`
have an additional type constructor `G[_]` that the factory doesn't know about.

Use `type AnyF[A] = Any` for kind-correct erasure — it has kind `* -> *` so it can be used
with `Applicative[AnyF]` (unlike `Applicative[Any]` which fails on Scala 3 due to kind mismatch):

```scala
type AnyF[A] = Any  // kind * -> *, erases G[_]

def traverseInstance[F[_]](
  traverseFn: (F[W1], Any, Any) => Any,  // erased: (F[A], A => G[B], Applicative[G]) => G[F[B]]
  foldLeftFn: (F[W1], Any, (Any, Any) => Any) => Any,
  foldRightFn: (F[W1], Eval[Any], (Any, Eval[Any]) => Eval[Any]) => Eval[Any]
): cats.Traverse[F] = new cats.Traverse[F] {
  def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: cats.Applicative[G]): G[F[B]] =
    traverseFn.asInstanceOf[(F[A], A => G[B], cats.Applicative[G]) => G[F[B]]].apply(fa, f, G)
  // ...
}
```

Inside the macro splice, use `cats.Applicative[CatsDerivationFactories.AnyF]` when the
original code used `cats.Applicative[G]`:

```scala
// In the macro splice, the Applicative[G] implicit becomes Applicative[AnyF]:
implicit val GAnyF: Type[cats.Applicative[CatsDerivationFactories.AnyF]] = ...
```

### Deciding which parameter positions need `Any` vs `W1`/`W2`

| Position | Use `W1`/`W2` | Use `Any` |
|---|---|---|
| `F[_]` applied: `fa: F[W1]` | Yes | — |
| Function args: `f: W1 => W2` | Yes | — |
| `F[_ => _]` applied: `ff: F[W1 => W2]` | Yes | — |
| Raw value: `a: A` (pure, cons) | — | Yes (`a: Any`) |
| Accumulator: `b: B` (foldLeft) | — | Yes (`b: Any`) |
| Fold function: `(B, A) => B` | — | Yes (`(Any, Any) => Any`) |
| Extra HK: `G[B]`, `Applicative[G]` | — | Yes (`Any`, use `AnyF` for kind) |

### Alternative: Scala 3 polymorphic functions (NOT recommended)

Scala 3 supports polymorphic function types:
```scala
def functorInstance[F[_]](mapFn: [A, B] => (F[A], A => B) => F[B]): Functor[F]
```

This is type-safe but:
- Not available on Scala 2 (kindlings cross-compiles to 2.13)
- Requires different factory signatures per platform
- The erasure-based approach works identically on both platforms

Use the erasure-based approach for cross-compiled code. Reserve polymorphic functions for
Scala 3-only modules if they exist.

### Alternative: Scala 2 macro-based FunctionK (NOT recommended for this use case)

Cats uses a macro to implement `FunctionK` (`~>`) that generates a SAM-compatible anonymous class.
See `cats.arrow.FunctionKMacroMethods`. This approach:
- Requires a separate macro per type class shape
- Adds macro complexity without measurable benefit over the erasure approach
- The erasure approach is simpler and proven safe by JVM specification

## Inventory of migrated sites

All sites below have been migrated. Listed for reference when adding new type classes.

### Monomorphic (kind `*`) — straightforward lambda factories

| Module | Type Class | Methods | File |
|---|---|---|---|
| cats-derivation | Show | 1: show | ShowMacrosImpl.scala:33 |
| cats-derivation | Eq | 1: eqv | EqMacrosImpl.scala:29 |
| cats-derivation | Order | 1: compare | OrderMacrosImpl.scala:29 |
| cats-derivation | PartialOrder | 1: partialCompare | PartialOrderMacrosImpl.scala:20 |
| cats-derivation | Hash | 2: hash, eqv | HashMacrosImpl.scala:151 |
| cats-derivation | Semigroup | 1: combine | SemigroupMacrosImpl.scala:25 |
| cats-derivation | CommutativeSemigroup | 1: combine | CommutativeSemigroupMacrosImpl.scala:16 |
| cats-derivation | Monoid | 2: combine, empty | MonoidMacrosImpl.scala:70 |
| cats-derivation | CommutativeMonoid | 2: combine, empty | CommutativeMonoidMacrosImpl.scala:16 |
| cats-derivation | Empty | 1: empty | EmptyMacrosImpl.scala:26 |
| circe-derivation | Encoder | 1: apply | EncoderMacrosImpl.scala:57 |
| circe-derivation | EncoderAsObject | 1: encodeObject | EncoderMacrosImpl.scala:95 |
| circe-derivation | Decoder | 1: apply | DecoderMacrosImpl.scala:130 |
| jsoniter-derivation | JsonValueCodec | 3: nullValue, decode, encode | CodecMacrosImpl.scala:115 |
| jsoniter-derivation | JsonCodec | 2+ | CodecMacrosImpl.scala:411 |
| fast-show-pretty | FastShowPretty | 1: render | FastShowPrettyMacrosImpl.scala:61 |
| pureconfig-derivation | ConfigWriter | 1: to | WriterMacrosImpl.scala:45 |
| pureconfig-derivation | ConfigReader | 1: from | ReaderMacrosImpl.scala:48 |
| yaml-derivation | YamlEncoder | 1: encode | EncoderMacrosImpl.scala:77 |
| yaml-derivation | YamlDecoder | 1: decode | DecoderMacrosImpl.scala:104 |
| xml-derivation | XmlEncoder | 1: encode | EncoderMacrosImpl.scala:91 |
| xml-derivation | XmlDecoder | 1-2: decode | DecoderMacrosImpl.scala:131,151 |
| tapir-schema-derivation | Schema | 1: schemaType | SchemaMacrosImpl.scala:75 |
| ubjson-derivation | UBJsonValueCodec | 3: nullValue, decode, encode | CodecMacrosImpl.scala:50 |
| avro-derivation | AvroSchemaFor | 1: schema | SchemaForMacrosImpl.scala:69 |
| avro-derivation | AvroDecoder | 1: decode | DecoderMacrosImpl.scala:92,411 |
| avro-derivation | AvroEncoder | 1: encode | EncoderMacrosImpl.scala:88 |
| sconfig-derivation | ConfigReader | 1: from | ReaderMacrosImpl.scala:40 |
| sconfig-derivation | ConfigWriter | 1: to | WriterMacrosImpl.scala:38 |

### Polymorphic (kind `* -> *`) — erasure-based factories

| Module | Type Class | Methods | File |
|---|---|---|---|
| cats-derivation | Functor | 1: map | FunctorMacrosImpl.scala:46 |
| cats-derivation | Contravariant | 1: contramap | ContravariantMacrosImpl.scala:103 |
| cats-derivation | Invariant | 1: imap | InvariantMacrosImpl.scala:99 |
| cats-derivation | Apply | 2: map, ap | ApplyMacrosImpl.scala:86 |
| cats-derivation | Applicative | 3: pure, map, ap | ApplicativeMacrosImpl.scala:57 |
| cats-derivation | Foldable | 2: foldLeft, foldRight | FoldableMacrosImpl.scala:74 |
| cats-derivation | Traverse | 3: traverse, foldLeft, foldRight | TraverseMacrosImpl.scala:120 |
| cats-derivation | Reducible | 3: reduceLeftTo, foldLeft, foldRight | ReducibleMacrosImpl.scala:84 |
| cats-derivation | NonEmptyTraverse | 4+: nonEmptyTraverse, reduceLeftTo, ... | NonEmptyTraverseMacrosImpl.scala:141 |
| cats-derivation | SemigroupK | 1: combineK | SemigroupKMacrosImpl.scala:38 |
| cats-derivation | MonoidK | 2: empty, combineK | MonoidKMacrosImpl.scala:45 |
| cats-derivation | Pure | 1: pure | PureMacrosImpl.scala:78 |
| cats-derivation | EmptyK | 1: empty | EmptyKMacrosImpl.scala:34 |
| cats-derivation | NonEmptyAlternative | 3+: pure, ap, combineK | NonEmptyAlternativeMacrosImpl.scala:97 |
| cats-derivation | Alternative | 4+: pure, ap, empty, combineK | AlternativeMacrosImpl.scala:102 |
| cats-derivation | ConsK | 1: cons | ConsKMacrosImpl.scala:99 |

## Implementation checklist

When migrating a type class to the factory pattern:

1. **Define the factory method** in the module's `internal/runtime/` package
   - For monomorphic: accept `FunctionN` parameters matching method signatures
   - For polymorphic: use `Witness1`/`Witness2` erasure approach
   - Add `@publicInBinary` annotation if the factory is in a different module than the macro call site

2. **Ensure the factory's `Type` is available** in the macro context
   - Add the factory type to the `Types` object in the macro impl

3. **Change the macro codegen** from `new TypeClass[A] { ... }` to `Factory.instance[A](lambdas)`
   - The lambda bodies contain the same `Expr.splice(...)` calls as before
   - For polymorphic types, bind `A`->`Witness1`, `B`->`Witness2` at the factory call site

4. **Clean and recompile** — macros require clean rebuild after changes

5. **Run tests** — existing tests should pass without modification since runtime behavior is identical

6. **Verify bytecode** — confirm no per-type anonymous classes in `javap` output

## Integration with existing patterns

### Def-caching

The factory pattern works seamlessly with def-caching (`ValDefsCache`). The cached `def`s are
emitted by `cacheState.toValDefs.use { _ => ... }` outside the factory call, and the lambdas
reference them by name:

```scala
cacheState.toValDefs.use { _ =>
  Expr.quote {
    CatsDerivationFactories.hashInstance[A](
      (value: A) => Expr.splice(hashCallFor(Expr.quote(value))),
      (x: A, y: A) => Expr.splice(eqCallFor(Expr.quote(x), Expr.quote(y)))
    )
  }
}
```

### Runtime helpers

Modules that already use runtime helpers (like `CirceDerivationUtils.decoderFromFn`) are already
partially using this pattern. The factory methods should be consolidated alongside those helpers.

### `@publicInBinary`

If the factory is in a library module and called from macro-expanded code in user projects,
annotate it with `@publicInBinary` to prevent binary compatibility warnings. Since the factory
is part of the `internal.runtime` package, this signals it's an implementation detail that
must remain accessible.

## Related skills

- [`../hearth-def-caching/`](../hearth-def-caching/SKILL.md) — def-caching pattern that works alongside factory instances
- [`../kindlings-runtime-perf/`](../kindlings-runtime-perf/SKILL.md) — runtime performance optimization techniques
- [`../hearth-api-reference/`](../hearth-api-reference/SKILL.md) — Expr.quote, ValDefsCache API signatures
