Partial Application Guide
Create specialized functions by pre-filling arguments.
Overview
Partial application lets you create new functions by "filling in" some arguments of an existing function. This is powerful for creating specialized versions of general functions, building configuration systems, and improving code reusability.
partial - Left-to-Right Partial Application
The partial function pre-fills arguments from the left (beginning).
Signature
function partial<Args extends readonly any[], R>(
fn: (...args: Args) => R,
...prefilledArgs: any[]
): (...args: any[]) => RBasic Usage
import { partial } from '@ontologyio/receta/function'
const greet = (greeting: string, name: string) => `${greeting}, ${name}!`
const sayHello = partial(greet, 'Hello')
sayHello('Alice') // => 'Hello, Alice!'
sayHello('Bob') // => 'Hello, Bob!'Multiple Arguments
const log = (level: string, module: string, message: string) =>
`[${level}] ${module}: ${message}`
// Partial with one argument
const logError = partial(log, 'ERROR')
logError('Auth', 'Invalid token') // => '[ERROR] Auth: Invalid token'
// Partial with two arguments
const logUserError = partial(log, 'ERROR', 'UserModule')
logUserError('Invalid input') // => '[ERROR] UserModule: Invalid input'Real-World Example: API Client
interface ApiConfig {
baseUrl: string
headers: Record<string, string>
}
const fetchApi = async (
config: ApiConfig,
endpoint: string,
options?: RequestInit
): Promise<Response> => {
return fetch(`${config.baseUrl}${endpoint}`, {
...options,
headers: { ...config.headers, ...(options?.headers || {}) }
})
}
// Create specialized API client
const prodConfig: ApiConfig = {
baseUrl: 'https://api.production.com',
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` }
}
const prodFetch = partial(fetchApi, prodConfig)
// Now you can use it without repeating config
await prodFetch('/users')
await prodFetch('/posts', { method: 'POST', body: JSON.stringify(data) })partialRight - Right-to-Left Partial Application
The partialRight function pre-fills arguments from the right (end).
Signature
function partialRight<Args extends readonly any[], R>(
fn: (...args: Args) => R,
...prefilledArgs: any[]
): (...args: any[]) => RBasic Usage
import { partialRight } from '@ontologyio/receta/function'
const divide = (a: number, b: number) => a / b
const divideBy10 = partialRight(divide, 10)
divideBy10(100) // => 10 (100 / 10)
divideBy10(50) // => 5 (50 / 10)When to Use partialRight
Use partialRight when the data you want to vary comes first:
// Data varies, format is fixed
const formatDate = (date: Date, format: string, locale: string) =>
date.toLocaleDateString(locale, { dateStyle: format as any })
const formatInUS = partialRight(formatDate, 'short', 'en-US')
const formatInFR = partialRight(formatDate, 'short', 'fr-FR')
const today = new Date('2026-01-22')
formatInUS(today) // => '1/22/26'
formatInFR(today) // => '22/01/2026'Real-World Example: Event Logging
const logEvent = (
event: string,
userId: string,
timestamp: string,
metadata: Record<string, any>
) => ({
event,
userId,
timestamp,
metadata
})
// Pre-fill timestamp and metadata for a session
const sessionLogger = partialRight(
logEvent,
new Date().toISOString(),
{ sessionId: 'abc-123', source: 'web' }
)
// Now just specify event and user
sessionLogger('page_view', 'user-1')
sessionLogger('button_click', 'user-1')
sessionLogger('form_submit', 'user-1')flip - Reverse Argument Order
The flip function swaps the first two arguments of a function.
Signature
function flip<A, B, R>(
fn: (a: A, b: B) => R
): (b: B, a: A) => RBasic Usage
import { flip } from '@ontologyio/receta/function'
const subtract = (a: number, b: number) => a - b
const subtractFrom = flip(subtract)
subtract(10, 3) // => 7 (10 - 3)
subtractFrom(3, 10) // => 7 (10 - 3, arguments flipped)Use Case: Better Partial Application
const divide = (a: number, b: number) => a / b
// Without flip
const divideBy10 = (n: number) => divide(n, 10)
// With flip + partial (more elegant)
const divideBy = flip(divide)
const divideBy10 = partial(divideBy, 10)
divideBy10(100) // => 10Real-World Example: Data Pipeline
import { pipe, map } from 'remeda'
import { flip, partial } from '@ontologyio/receta/function'
const append = (lines: string[], line: string) => [...lines, line]
const prepend = (lines: string[], line: string) => [line, ...lines]
const addFooter = partial(flip(append), '---End---')
const addHeader = partial(flip(prepend), '---Start---')
const processLines = pipe(
['line 1', 'line 2', 'line 3'],
addHeader,
addFooter
)
// => ['---Start---', 'line 1', 'line 2', 'line 3', '---End---']Arity Control
unary - Limit to One Argument
Forces a function to accept only its first argument.
import { unary } from '@ontologyio/receta/function'
// The classic parseInt bug
const digits = ['1', '2', '3']
digits.map(parseInt) // => [1, NaN, NaN]
digits.map(unary(parseInt)) // => [1, 2, 3]Why does this happen?
// Array.map passes (element, index, array)
// parseInt reads the first two as (string, radix)
// Without unary:
parseInt('1', 0) // => 1 (radix 0 defaults to 10)
parseInt('2', 1) // => NaN (radix 1 is invalid)
parseInt('3', 2) // => NaN (3 is invalid in base 2)
// With unary:
parseInt('1') // => 1
parseInt('2') // => 2
parseInt('3') // => 3binary - Limit to Two Arguments
import { binary } from '@ontologyio/receta/function'
const sum = (...nums: number[]) => nums.reduce((a, b) => a + b, 0)
sum(1, 2, 3, 4) // => 10
// reduce also passes the index and the array, which binary drops
const numbers = [1, 2, 3, 4]
numbers.reduce(binary(sum)) // => 10nAry - Limit to N Arguments
import { nAry } from '@ontologyio/receta/function'
const log = (...args: any[]) => console.log(...args)
const logOne = nAry(1, log)
const logThree = nAry(3, log)
logOne('a', 'b', 'c') // Logs: a
logThree('a', 'b', 'c', 'd') // Logs: a b cReal-World Example: Callback Adapters
// Problem: Library passes extra arguments you don't want
const onClick = (event: MouseEvent, index: number, element: HTMLElement) => {
// But you only care about the event
handleClick(event)
}
// Solution: Use unary
const onClick = unary((event: MouseEvent) => {
handleClick(event)
})
// Now extra arguments are ignored
element.addEventListener('click', onClick)Combining Partial Application
You can chain partial applications:
const formatLog = (timestamp: string, level: string, module: string, msg: string) =>
`${timestamp} [${level}] ${module}: ${msg}`
const withTimestamp = partial(formatLog, '2026-01-22T10:00:00Z')
const errorLog = partial(withTimestamp, 'ERROR')
const userErrorLog = partial(errorLog, 'UserModule')
userErrorLog('Failed authentication')
// => '2026-01-22T10:00:00Z [ERROR] UserModule: Failed authentication'Best Practices
1. Design Functions for Partial Application
// ✅ Good: Config/context first, data last
const query = (config: Config, sql: string, params: any[]) => runQuery(config, sql, params)
const dbQuery = partial(query, productionConfig)
// ❌ Bad: Data first makes partial application awkward
const query = (sql: string, params: any[], config: Config) => runQuery(config, sql, params)2. Name Partially Applied Functions Clearly
// ❌ Unclear
const fn = partial(log, 'ERROR')
// ✅ Clear intent
const logError = partial(log, 'ERROR')
const logUserError = partial(logError, 'UserModule')3. Use flip When Data Should Come First
// If you have: fn(config, data)
// But need: fn(data, config)
const flipped = flip(fn)
const withConfig = partial(flipped, config)
// Now: withConfig(data)4. Prefer unary for Common Callbacks
// ✅ Clean
numbers.map(unary(parseInt))
items.forEach(unary(processItem))
// ❌ Verbose
numbers.map((x) => parseInt(x))
items.forEach((item) => processItem(item))Type Safety
Partial application keeps the return type. The remaining arguments are typed any[], so TypeScript won't check them:
const greet = (greeting: string, name: string): string =>
`${greeting}, ${name}!`
const sayHello = partial(greet, 'Hello')
// TypeScript knows sayHello returns string
const result: string = sayHello('Alice')
// ❌ Type error
const wrong: number = sayHello('Bob')Next Steps
- Control Flow Guide - Learn tap, tryCatch, memoize
- Integration Guide - Combine with Result and Option
- Real-World Patterns - Production examples