Skip to content

Introduction ​

INFO

This is an introduction to function composition concept itself. For package documentation, see the Getting Started guide.

What is Function Composition? ​

Function composition is a programming concept that allows you to combine multiple simple functions to create more complex operations. Think of it like an assembly line where data flows through a series of transformations, with each function performing a specific operation before passing the result to the next function.

Instead of writing complex, nested function calls that are hard to read and maintain, composition lets you create clear, step-by-step pipelines that transform data from input to output.

typescript
import { 
flow
} from '@uni-ts/composition';
const
trim
= (
str
: string) =>
str
.
trim
();
const
toLowerCase
= (
str
: string) =>
str
.
toLowerCase
();
const
upperFirst
= (
str
: string) =>
str
.
charAt
(0).
toUpperCase
() +
str
.
slice
(1);
const
addGreeting
= (
name
: string) => `Hello, ${
name
}!`;
// Compose simple functions into a complex operation const
greet
=
flow
(
trim
,
toLowerCase
,
upperFirst
,
addGreeting
);
console
.
log
(
greet
(' ALICE ')); // "Hello, Alice!"

Why Use Function Composition? ​

Function composition transforms how you structure and think about your code, by encouraging you to break things down into smaller, reusable functions. Let's explore why this matters through practical examples.

Readability: From Nested Chaos to Clear Pipelines ​

Traditional nested function calls read from inside-out (right to left), making it difficult to follow the data transformation flow.

typescript
const 
finalPrice
=
formatPrice
(
calculateTax
(
applyDiscount
(
getBasePrice
(
product
),
discount
),
taxRate
),
);

The composed version reads like a step-by-step recipe.

typescript
const 
finalPrice
=
pipe
(
product
,
getBasePrice
,
(
price
) =>
applyDiscount
(
price
,
discount
),
(
price
) =>
calculateTax
(
price
,
taxRate
),
formatPrice
,
);

Reusability: Build Once, Use Everywhere ​

Composition encourages you to create small functions that can be easily reused in multiple pipelines.

typescript
function 
getUserDisplayName
(
name
: string) {
const
result
=
name
.
trim
() || 'Anonymous';
return
result
.
charAt
(0).
toUpperCase
() +
result
.
slice
(1);
} function
getAdminDisplayName
(
name
: string) {
const
result
=
name
.
charAt
(0).
toUpperCase
() +
name
.
slice
(1);
return `${
result
} (Admin)`;
} function
getNewBlogpostTitle
(
title
: string) {
const
result
=
title
.
trim
() || 'Untitled';
return `${
result
} (New)`;
}
typescript
import { 
flow
} from '@uni-ts/composition';
const
suffix
= (
suffix
: string) => (
str
: string) =>
str
+
suffix
;
const
upperFirst
= (
str
: string) =>
str
.
charAt
(0).
toUpperCase
() +
str
.
slice
(1);
const
fallback
= (
fallback
: string) => (
str
: string) =>
str
.
trim
() ||
fallback
;
export const
getUserDisplayName
=
flow
(
fallback
('Anonymous'),
upperFirst
);
export const
getAdminDisplayName
=
flow
(
upperFirst
,
suffix
(' (Admin)'));
export const
getNewBlogpostTitle
=
flow
(
fallback
('Untitled'),
suffix
(' (New)'));

Testability: Small Functions, Big Confidence ​

By writing small, focused functions, testing becomes a breeze. It's also far less likely you'll forget to test some part of a function.

typescript
// We need to think about all test cases for this function

function 
processOrder
(
order
:
Order
) {
const
total
=
order
.
items
.
reduce
(
(
sum
,
item
) =>
sum
+
item
.
price
*
item
.
quantity
,
0, ); const
discountedTotal
=
total
>
order
.
discount
.
from
?
total
* (1 -
order
.
discount
.
rate
) :
total
;
const
totalAfterTax
=
discountedTotal
*
order
.
taxRate
;
return `Total price: ${
order
.
currency
}${
totalAfterTax
.
toFixed
(2)}`;
}
typescript
// We can test each price calculation step independently

const 
getProductsCost
= (
products
:
Product
[]) =>
products
.
reduce
((
sum
,
item
) =>
sum
+
item
.
price
*
item
.
quantity
, 0);
const
applyDiscount
= (
discount
:
Discount
) => (
price
: number) =>
price
>
discount
.
from
?
price
* (1 -
discount
.
rate
) :
price
;
const
addTax
= (
taxRate
: number) => (
price
: number) =>
price
* (1 +
taxRate
);
const
formatTotal
= (
currency
: string) => (
price
: number) =>
`Total price: ${
currency
}${
price
.
toFixed
(2)}`;
const
processOrder
= (
order
:
Order
) =>
pipe
(
order
.
items
,
getProductsCost
,
applyDiscount
(
order
.
discount
),
addTax
(
order
.
taxRate
),
formatTotal
(
order
.
currency
),
);

Getting Started ​

Ready to start using function composition in your project? Check out the Getting Started guide to learn how to install and use the @uni-ts/composition package.