receta
Function

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[]) => R

Basic 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[]) => R

Basic 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) => R

Basic 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) // => 10

Real-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') // => 3

binary - 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))  // => 10

nAry - 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 c

Real-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

On this page