https://github.com/polyvariant/named-functions
Micro-library for working with functions with named parameters.
https://github.com/polyvariant/named-functions
macros scala scala3
Last synced: about 1 month ago
JSON representation
Micro-library for working with functions with named parameters.
- Host: GitHub
- URL: https://github.com/polyvariant/named-functions
- Owner: polyvariant
- License: other
- Created: 2026-04-10T23:48:41.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-23T18:07:45.000Z (about 2 months ago)
- Last Synced: 2026-04-23T20:10:07.142Z (about 2 months ago)
- Topics: macros, scala, scala3
- Language: Scala
- Homepage:
- Size: 66.4 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# named-functions
Scala 3 macros for converting multi-parameter functions into functions with named parameters or named-tuple arguments, preserving the original parameter names.
## Quick overview
```scala
import namedfunctions.syntax.*
def greet(name: String, age: Int): String = s"$name is $age"
// namedTuple(...) — build a named tuple from local names
val age = 30
val name = "Alice"
val nt = namedTuple(name, age)
// .named — named parameters at call site
val f = greet.named
f(name = "Alice", age = 30) // "Alice is 30"
// .namedTupled — function from named tuple
val g = greet.namedTupled
g((name = "Alice", age = 30)) // "Alice is 30"
// .namedUntupled — reverse of tupling
val h: ((name: String, age: Int)) => String = t => s"${t.name} is ${t.age}"
h.namedUntupled(name = "Alice", age = 30) // "Alice is 30"
// .nameChecked — compile-time name validation, order-independent
val age = 30
val name = "Alice"
greet.nameChecked(age, name) // "Alice is 30" — reordered automatically
// greet.nameChecked(age, x) // compile error: unexpected: x; missing: name
// .applyProduct — apply from case class fields by name
case class Person(age: Int, name: String, email: String)
greet.applyProduct(Person(30, "Alice", "a@b.com")) // "Alice is 30" — extra fields ignored
```
## Installation
Available on Maven Central as `org.polyvariant::named-functions`.
In scala-cli:
```scala
//> using dep org.polyvariant::named-functions::
```
In sbt:
```scala
libraryDependencies += "org.polyvariant" %% "named-functions" % ""
```
In Mill:
```scala
ivy"org.polyvariant::named-functions:"
```
## Usage
```scala
import namedfunctions.syntax.*
def foo(entityId: Int, userId: String): Boolean = ???
val entityId = 1
val userId = "hello"
val params = namedTuple(entityId, userId)
// Wrap a function so that its parameters are named at the call site
val f = foo.named
f(entityId = 1, userId = "hello")
// Convert a function into a Function1 from a named tuple
val g = foo.namedTupled
g((entityId = 1, userId = "hello"))
// Convert a Function1 from a named tuple back into a named-parameter function
val h: ((entityId: Int, userId: String)) => Boolean = ???
val f2 = h.namedUntupled
f2(entityId = 1, userId = "hello")
```
### Multiple parameter lists
Methods with multiple parameter lists are supported — the result is a curried function with named parameters at each level:
```scala
def bar(entityId: Int)(userId: String): Boolean = ???
val f = bar.named
f(entityId = 1)(userId = "hello")
val g = bar.namedTupled
g((entityId = 1))((userId = "hello"))
```
### `NamedFunctions.of` / `NamedFunctions.apply` / `.named`
Converts a multi-parameter function `(a: A, b: B, ...) => R` into a function with named parameters, preserving the original parameter names.
### `NamedFunctions.tupled` / `.namedTupled`
Like `.tupled` but the resulting tuple type carries the parameter names from the original method. Converts a multi-parameter function into a `Function1` from a named tuple: `((a: A, b: B, ...)) => R`.
### `syntax.namedTuple`
Builds a named tuple directly from variable references or field accesses by using the final name segment as the label:
```scala
import namedfunctions.syntax.*
val a = 42
val b = "hello"
namedTuple(a, b) // (a = 42, b = "hello")
```
### `NamedFunctions.untupled` / `.namedUntupled`
The reverse of `tupled`. Converts a `Function1` from a named tuple into a multi-parameter function with named parameters.
### `.nameChecked`
Compile-time check that argument variable names exactly match the function's parameter names. Arguments are matched by name and automatically reordered, so order doesn't matter — only that the names are correct:
```scala
def foo(a: Int, b: String): String = s"$a-$b"
val a = 42
val b = "hello"
foo.nameChecked(a, b) // compiles — "42-hello"
foo.nameChecked(b, a) // also compiles — reordered to "42-hello"
foo.nameChecked(b, x) // compile error: unexpected: x; missing: a
foo.nameChecked("hello") // compile error: requires variable references or field accesses
```
Arguments can be plain variable references or field accesses (e.g. `obj.field`). The last segment of the access is used as the name. Multiple parameter lists are supported — all arguments are passed flat:
```scala
def bar(entityId: Int)(userId: String): Boolean = ???
val entityId = 1
val userId = "hello"
bar.nameChecked(entityId, userId)
```
Field accesses work too — the field name is what matters:
```scala
case class Source(a: Int, b: String)
val src = Source(42, "hello")
foo.nameChecked(src.a, src.b) // compiles — "42-hello"
```
### `.applyProduct`
Applies a function using fields from a case class, matched by name (not position). The case class may have extra fields, but all function parameters must be present:
```scala
def foo(a: Int, b: String): String = s"$a-$b"
case class Params(b: String, a: Int)
foo.applyProduct(Params("hello", 42)) // "42-hello" — fields matched by name
case class Extended(a: Int, b: String, extra: Boolean)
foo.applyProduct(Extended(1, "hi", true)) // works — extra fields ignored
```
Multiple parameter lists are supported:
```scala
def bar(entityId: Int)(userId: String): Boolean = ???
case class Params(entityId: Int, userId: String)
bar.applyProduct(Params(1, "hello"))
```
## Chaining
Features can be composed — the output of one transformation is a valid input for another:
```scala
def foo(a: Int, b: String): String = s"$a-$b"
case class Params(b: String, a: Int)
foo.named.applyProduct(Params("hello", 42)) // "42-hello"
val a = 42
val b = "hello"
foo.named.nameChecked(a, b) // "42-hello"
// Round-trip through tupling and back
foo.namedTupled.namedUntupled.applyProduct(Params("hello", 42)) // "42-hello"
```
## Limitations
All features require the macro to extract parameter names from the call site's AST. This works with method references (`obj.method`), eta-expanded methods, and case class constructors (`Foo.apply`), but **not with function values stored in a `val`**:
```scala
val f = (a: Int, b: String) => s"$a-$b"
f.named // compile error: Could not extract parameter names
f.nameChecked(a, b) // same
f.applyProduct(p) // same
```
The parameter names exist in the lambda at the definition site, but are erased by the time the `val` reference reaches the macro.