Skip to content
CodeSook
CodeSook

Curry and Pipe 『Functional in Typescript』


25 cli tools logo
#Curried function#Pipe#Typescript#FunctionalProgramming#Effect
CodeSookPublish: 18th January 2025

blog นี้ผมจะชวนมารู้จักกับ Curry และ pipe() ใน Functional programming in Typescript

ผมคาดหวังว่าหลังจากอ่านจบเพื่อนๆน่าจะนำเอา Curry ไปประยุคใช้กับ pipe function ใน Typescript ได้
โดยจะสร้าง Curried function เอง หรือสร้างโดยใช้ helper function จาก lib ผมจะพาทำทั้งหมดครับ


Curried function

เรามี function ที่รับ parameters หลายๆตัว แล้วเราแปลง function นั้นให้กลายเป็น function ที่รับ parameter แค่ 1 ตัว แล้ว return function ซึ่ง function นี้จะรับ parameters ตัวที่เหลือ
ซึ่ง function ตัวที่ return นั้นจะรับ parameters ตัวที่เหลือทั้งหมดเลยก็ได้ หรือจะ รับ parameter แค่ตัวเดียวแล้ว return function ที่รับ parameter อีกทีนึงก็ได้
พอทำแบบนี้ Curry ก็จะเป็น Higher Order Function ไปด้วย
ส่วน Higher Order Function คืออะไร ผมเคยทำคลิปไว้ อาจจะยาวหน่อยไปตามดูกันได้ CodeSook Youtube: Higher Order Function

Play

ทำไมเราต้องใช้ Curry ด้วยละ

Curry จะช่วยให้เรา

  1. Function composition เอา function หลายตัวมาใช้งานต่อกันได้ง่ายขึ้น ใน blog นี้ผมจะแสดงให้เห็นตรงจุดนี้แหละ โดยจะใช้ร่วมกับ pipe()
  2. Partial Application สร้าง function ที่รับ parameter ไปก่อนตัวนึง แล้วเราจะได้ function กลับมา function นั้นจะต้องรับ parameter ที่เหลือ ทำให้เราสามารถเอา function ที่ต้องการ parameter บางตัวร่วมกัน เอาไปใช้งานได้สะดวกมากขึ้น
  3. Point-Free Style ใช้งาน function โดยที่ไม่ต้องใส่ parameter ทำให้ code กระชับมากขึ้น
  4. Memoization & caching ทำให้ function ของเราที่อาจจะเคยคำนวนงานไปแล้ว ไม่ต้องคำนวนอีก เป็นการเอา Memory มาแลกกับ Compute

ใน blog นี้ผมคงไม่ได้แสดงการใช้งานที่แสดงถึงประโชยน์จากด้านบนให้ทั้งหมดนะครับ ผมเองก็ใช้แค่ไม่กี่ข้อ ฮ่าๆ 😆😆


Example 1

มาดูตัวอย่างการทำ Curried function แบบง่ายๆกัน

function
function add(a: number, b: number): number
add
(
a: number
a
: number,
b: number
b
: number): number {
return
a: number
a
+
b: number
b
}
function add(a: number, b: number): number
add
(1,2) // result = 3

เรามี function add() แบบด้านบน ซึ่งรับ parameters 2 ตัว

แปลงเป็น Curried function ก็จะเขียนแบบนี้

function
function add(a: number): (b: number) => number
add
(
a: number
a
: number): (
b: number
b
: number) => number {
return (
b: number
b
: number) =>
a: number
a
+
b: number
b
}
function add(a: number): (b: number) => number
add
(1)(2) // result = 3

การเรียกใช้งานก็จะเปลี่ยนไปนิดหน่อย

ถ้าเรามี logic ที่ต้องการ function ที่เรียกปุ๊ป บวก 1 ให้เลย
เราก็สามารถทำ Partial applied จาก function add() ไปก่อน แบบนี้

3 collapsed lines
function
function add(a: number): (b: number) => number
add
(
a: number
a
: number): (
b: number
b
: number) => number {
return (
b: number
b
: number) =>
a: number
a
+
b: number
b
}
const incrementOne =
function add(a: number): (b: number) => number
add
(1)
const incrementOne: (b: number) => number
const
const couter: number
couter
=
const incrementOne: (b: number) => number
incrementOne
(5) // result = 6
const
const tick: number
tick
=
const incrementOne: (b: number) => number
incrementOne
(1) // result = 2

จากตัวอย่างด้านบนเราสร้าง function incrementOne() ขึ้นมาจาก function add() โดยเราส่ง 1 เข้าไปเป็น parameter แล้วเราก็จะได้ function กลับมา
จากเดิม function add() ต้องใส่ parameters 2 ตัวในทันที พอทำ Partial applied แล้ว เราก็ส่ง parameter เข้าไปรอก่อน 1 ตัว
พอเรามี data สำหรับ parameter ตัวที่ 2 ก็ค่อยส่งตามเข้ามา

การทำแบบนี้จะทำให้เราสร้าง function ต่อยอดจาก function ที่มีอยู่เดิมแล้ว ได้สะดวกขึ้น
นำไปใช้งานก็สะดวก และสวยงานมากขึ้น


Example 2 (เพิ่มความซับซ้อน)

มาดูตัวอย่างที่ซับซ้อนมากขึ้น

มาสร้าง function ที่เอาไว้คำนวน ราคาสินค้าที่เป็นดอลลาร์ ผ่านการลดราคาเป็นเปอร์เซ็นต์ คำนวน tax สุดท้ายแปลงเป็นเงินบาท

type Item = {
price: number
quantity: number
}
const shoppingCart = [
{ price: 29.99, quantity: 2 }, // Two t-shirts
{ price: 49.99, quantity: 1 }, // One pair of jeans
{ price: 9.99, quantity: 3 } // Three pairs of socks
]
function calculatePriceInCart(cart: Item[]): number {
return cart.reduce((total, item) => total + (item.price * item.quantity), 0)
}

โค้ดเริ่มต้นในตัวอย่างด้านบน เรามีสินค้าในตะกร้า แล้วมี function calculatePriceInCart ที่เราไว้รวมราคาสินค้า

เรามาสร้าง functions คำนวณการลดราคา คำนวณ tax

11 collapsed lines
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: {
price: number;
quantity: number;
}[]
shoppingCart
= [
{
price: number
price
: 29.99,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 49.99,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 9.99,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(cartPrice: number, discountPercent: number): number
applyDiscount
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number): number {
return
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
function
function applyTax(discountedPrice: number, taxRate: number): number
applyTax
(
discountedPrice: number
discountedPrice
: number,
taxRate: number
taxRate
: number): number {
return
discountedPrice: number
discountedPrice
+ (
discountedPrice: number
discountedPrice
* (
taxRate: number
taxRate
/ 100))
}
const
const price: number
price
=
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
const shoppingCart: {
price: number;
quantity: number;
}[]
shoppingCart
)
const
const priceAfterDiscount: number
priceAfterDiscount
=
function applyDiscount(cartPrice: number, discountPercent: number): number
applyDiscount
(
const price: number
price
, 10)
const
const priceAfterTax: number
priceAfterTax
=
function applyTax(discountedPrice: number, taxRate: number): number
applyTax
(
const priceAfterDiscount: number
priceAfterDiscount
, 7)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
({
priceAfterTax: number
priceAfterTax
}) // { priceAfterTax: 57.673 }

ทีนี้เราก็เอามาแปลงให้เป็น Curried function
จะได้แบบนี้

11 collapsed lines
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(cartPrice: number): (discountPercent: number) => number
applyDiscount
(
cartPrice: number
cartPrice
: number): (
discountPercent: number
discountPercent
: number) => number {
return (
discountPercent: number
discountPercent
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
function
function applyTax(discountedPrice: number): (taxRate: number) => number
applyTax
(
discountedPrice: number
discountedPrice
: number,): (
taxRate: number
taxRate
: number) => number {
return (
taxRate: number
taxRate
: number) =>
discountedPrice: number
discountedPrice
+ (
discountedPrice: number
discountedPrice
* (
taxRate: number
taxRate
/ 100))
}
const
const price: number
price
=
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
const shoppingCart: Item[]
shoppingCart
)
const
const priceAfterDiscount: number
priceAfterDiscount
=
function applyDiscount(cartPrice: number): (discountPercent: number) => number
applyDiscount
(
const price: number
price
)(10)
const
const priceAfterTax: number
priceAfterTax
=
function applyTax(discountedPrice: number): (taxRate: number) => number
applyTax
(
const priceAfterDiscount: number
priceAfterDiscount
)(7)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
({
priceAfterTax: number
priceAfterTax
})

จะเห็นว่าไม่ยากเลย
แต่ตอนเรียกใช้งานเราต้องใส่วงเล็บต่อๆกัน เราจะไม่คุ้ยเคยหน้าตาการเรียกใช้ function แบบนี้กันสักเท่าไรใช่ไหมครับ
เดี๋ยวด้านล่างผมจะพาแก้ปัญหานี้ แต่ขอไปเรื่อง pipe ก่อน


pipe function

อย่างที่บอกไปว่าประโยชน์อย่างหนึ่งของ Curried function คือทำให้เราเอา function มาใช้งานต่อๆ(compose)กันได้ง่ายขึ้น และส่วนตัวผมคิดว่าการใช้ pipe() เป็นการ compose function แล้วอ่านง่ายที่สุด

แต่ว่าใน Typescript ไม่ได้มี pipe operator มาให้เราใช้ ก็เลยจำเป็นต้องใช้ pipe function แทนไปก่อน
ส่วนตัวผมใช้ pipe() ที่อยู่ใน lib Effect นะครับ

How does pipe work?

มาดูกันว่า pipe มันทำงานยังไง

pipe() ใน Effect จะเริ่มต้นด้วยการรับ value เข้ามา

26 collapsed lines
import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from "effect"
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(cartPrice: number): (discountPercent: number) => number
applyDiscount
(
cartPrice: number
cartPrice
: number): (
discountPercent: number
discountPercent
: number) => number {
return (
discountPercent: number
discountPercent
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
function
function applyTax(discountedPrice: number): (taxRate: number) => number
applyTax
(
discountedPrice: number
discountedPrice
: number,): (
taxRate: number
taxRate
: number) => number {
return (
taxRate: number
taxRate
: number) =>
discountedPrice: number
discountedPrice
+ (
discountedPrice: number
discountedPrice
* (
taxRate: number
taxRate
/ 100))
}
const
const priceAfterTax: Item[]
priceAfterTax
=
pipe<Item[]>(a: Item[]): Item[] (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
const shoppingCart: Item[]
shoppingCart
,
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
({
priceAfterTax: Item[]
priceAfterTax
})

จากนั้นก็จะต้องตามด้วย function ซึ่ง parameter ใน function นี้จะมีได้แค่ตัวเดียว
นั่นคือทำไมเราต้องทำ function ของเราให้เป็น curried function

pipe จะเอา value ที่ได้ก่อนหน้ามาใส่ให้เรา เป็น argument ใน function ถัดไป
และ function นั้น จะต้อง return ด้วยนะ
แล้ว pipe ก็จะเอา return value ที่ได้จาก function ส่งต่อไปให้กับ function ถัดไป ถัดไป ถัดไป เรื่อยๆ
ถ้าไม่มี function ถัดไป ก็จะเอา return value ไปเก็บในตัวแปร const totalPrice = pipe() เป็นการจบการทำงานของ pipe()

pipe draw

มาดูโค้ดของเรา

25 collapsed lines
import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from "effect"
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(cartPrice: number): (discountPercent: number) => number
applyDiscount
(
cartPrice: number
cartPrice
: number): (
discountPercent: number
discountPercent
: number) => number {
return (
discountPercent: number
discountPercent
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
function
function applyTax(discountedPrice: number): (taxRate: number) => number
applyTax
(
discountedPrice: number
discountedPrice
: number,): (
taxRate: number
taxRate
: number) => number {
return (
taxRate: number
taxRate
: number) =>
discountedPrice: number
discountedPrice
+ (
discountedPrice: number
discountedPrice
* (
taxRate: number
taxRate
/ 100))
}
const
const priceAfterTax: number
priceAfterTax
=
pipe<Item[], number>(a: Item[], ab: (a: Item[]) => number): number (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
const shoppingCart: Item[]
shoppingCart
,
(
shopCart: Item[]
shopCart
) =>
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
shopCart: Item[]
shopCart
),
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
({
priceAfterTax: number
priceAfterTax
})

จากโค้ดด้านบนเราเริ่มต้นด้วยการใส่ value shoppingCart ไปก่อน
แล้วตามด้วย function ที่รับ parameter 1 ตัว ซึ่ง value ของ parameter นี้ก็จะเป็นค่า value ของ shoppingCart น่ะแหละ
ในโค้ดด้านบนเราสร้าง function ที่รับ parameter ตัวเดียวด้วย arrow function

ผมจะเอา function คำนวน discount กับคำนวณ tax มาใช้ด้วย แบบนี้

25 collapsed lines
import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from "effect"
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(cartPrice: number): (discountPercent: number) => number
applyDiscount
(
cartPrice: number
cartPrice
: number): (
discountPercent: number
discountPercent
: number) => number {
return (
discountPercent: number
discountPercent
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
function
function applyTax(discountedPrice: number): (taxRate: number) => number
applyTax
(
discountedPrice: number
discountedPrice
: number,): (
taxRate: number
taxRate
: number) => number {
return (
taxRate: number
taxRate
: number) =>
discountedPrice: number
discountedPrice
+ (
discountedPrice: number
discountedPrice
* (
taxRate: number
taxRate
/ 100))
}
const
const priceAfterTax: number
priceAfterTax
=
pipe<Item[], number, number, number>(a: Item[], ab: (a: Item[]) => number, bc: (b: number) => number, cd: (c: number) => number): number (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
const shoppingCart: Item[]
shoppingCart
,
(
shopCart: Item[]
shopCart
) =>
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
shopCart: Item[]
shopCart
),
(
price: number
price
) =>
function applyDiscount(cartPrice: number): (discountPercent: number) => number
applyDiscount
(
price: number
price
)(10),
(
discountedPrice: number
discountedPrice
) =>
function applyTax(discountedPrice: number): (taxRate: number) => number
applyTax
(
discountedPrice: number
discountedPrice
)(7)
)

ตรงนี้เพื่อนน่าจะเห็นแล้วว่า pipe มันใช้ยังไง
แต่ก็คงคิดใช่ไหมครับว่า มันน่าใช้ตรงไหน ต้องมาเขียน arrow function เพิ่มอีก ช่วยให้ compose functions ไม่เห็นง่ายเลย

คือถ้าใครคิดแบบนี้แสดงว่าตามผมทัน ฮ่าๆ
ผมตั้งใจแสดงให้เห็นว่าการที่เรามี functions ที่รับ parameters หลายๆตัว แล้วเอามาแปลงให้เป็น Curried function แบบที่ทำกันไปแล้วนั้น มันเอาไปใช้งานกับ pipe() ได้ยากครับ
แต่ผมไม่ได้บอกว่ามันผิดนะครับ ขึ้นอยู่กับว่าเราจะเอาไปใช้งานแบบไหน แต่กรณีของผม ผมจะเอาไปใช้กับ pipe() ไง

แล้วแบบไหนที่ง่ายต่อการเอาไปใช้กับ pipe() ละ
อันนี้ไม่ยากเลยครับ
เราแค่ ต้องเอา parameter ตัวแรกไปไว้เป็นตัวสุดท้าย เท่านั้นเองครับ
ในกรณีที่เรามี function ที่มี parameters แค่ 2 ตัว จะมองว่าเอามาสลับลำดับกันก็ได้

มาดูตัวอย่างกัน ใช้ตัวอย่างเดิมแหละ

function
function applyDiscount(cartPrice: number, discountPercent: number): number
applyDiscount
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number): number {
return
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}

เราจะเอาไปทำเป็น Curried function ก็ให้สลับตำแหน่องของ parameters แบบนี้

pipe draw2

มาดู code กัน

15 collapsed lines
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
// function applyDiscount(cartPrice: number, discountPercent: number): number {
// return cartPrice - (cartPrice * (discountPercent / 100))
// }
function
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
discountPercent: number
discountPercent
: number): (
cartPrice: number
cartPrice
: number) => number {
return (
cartPrice: number
cartPrice
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}

แล้วก็เอามาใช้กับ pipe() แบบนี้

17 collapsed lines
import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from "effect"
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
discountPercent: number
discountPercent
: number): (
cartPrice: number
cartPrice
: number) => number {
return (
cartPrice: number
cartPrice
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
const
const discountPercent: 10
discountPercent
= 10
const
const priceAfterTax: number
priceAfterTax
=
pipe<Item[], number, number>(a: Item[], ab: (a: Item[]) => number, bc: (b: number) => number): number (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
const shoppingCart: Item[]
shoppingCart
,
(
shopCart: Item[]
shopCart
) =>
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
shopCart: Item[]
shopCart
),
(
cartPrice: number
cartPrice
) =>
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
const discountPercent: 10
discountPercent
)(
cartPrice: number
cartPrice
),
)

ในโค้ดด้านบน เรายังใช้ arrow function อยู่
ใน arrow function pipe() จะส่ง shoppingCart เข้ามาเป็น argument ของ arrow function จากนั้น เราก็เรียกใช้ calculatePriceInCart(shoppingCart)
ถ้าเราเจอว่า arguemnt กับ last function มันใช้ค่าเดียวกัน เราจะเขียนให้มันกระชับขึ้นได้
และ arrow function ถัดไปก็เหมือนกัน ที่ arrow function ได้รับ argument เป็น cartPrice จากนั้นเรียกใช้ applyDiscount(discountPercent)(cartPrice)
ตัว argument กับ last fucntion argument มันใช้ค่าเดียวกัน เราจะเขียนให้มันกระชับขึ้นได้เช่นกัน

const priceAfterTax = pipe(
shoppingCart,
(shopCart) => calculatePriceInCart(shopCart),
▲ ▲
└────────────────────────────────────┘
(cartPrice) => applyDiscount(discountPercent)(cartPrice),
▲ ▲
└─────────────────────────────────────────────┘
)

เขียนใหม่เป็นแบบนี้

23 collapsed lines
import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from "effect"
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
discountPercent: number
discountPercent
: number): (
cartPrice: number
cartPrice
: number) => number {
return (
cartPrice: number
cartPrice
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
const
const discountPercent: 10
discountPercent
= 10
const
const priceAfterTax: number
priceAfterTax
=
pipe<Item[], number, number>(a: Item[], ab: (a: Item[]) => number, bc: (b: number) => number): number (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
const shoppingCart: Item[]
shoppingCart
,
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
,
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
const discountPercent: 10
discountPercent
),
)

จากโค้ดด้านบน จะเห็นว่า ใน pipe() ตัว function calculatePriceInCart เราไม่ได้ใส่ () เลย
ตรงนี้เพราะว่าตัว function calculatePriceInCart เป็น function ที่รับ parameter ตัวเดียวอยู่แล้ว และ pipe() ก็ต้องการ function ที่รับ parameter ตัวเดียว เราก็ส่ง function ไปเฉยๆได้เลย เราเรียกการส่ง function แบบนี้ว่า point-less หรือบางคนก็เรียก point-free ครับ

ส่วน applyDiscount(discountPercent) ก็ทำ partial applied ด้วยการส่ง discountPercent เข้าไปที่ function applyDiscount() จากนั้นเดียว pipe() ก็จะเอา argument มาเติมให้เอง

ทั้งหมดเราก็จะอ่านจากบนลงล่างได้ง่ายๆเลยแบบนี้

มีส่วนลดอยู่ 10% (discountPercent = 10) มีตะกร้าสินค้า (shoppingCart) แล้วก็เอาไปคำนวนราคาในตะกร้า (calculatePriceInCart) แล้วก็ใส่ส่วนลด 10% (applyDiscount(discountPercent))

จะเห็นว่าอ่านง่ายเลยใช่ไหมครับ

ถ้าเราจะเอาตัวแปรมารับค่าจาก function แล้วค่อยใชัตัวแปรนั้นในอีก function นึงก็ไม่ได้ผิดนะครับ ส่วนตัวผมมองว่าใช้ pipe() มันอ่านง่ายกว่า เราไม่ต้องคอยจำตัวแปรในหัวเวลาอ่านโค้ด
ยิ่งถ้าเราตั้งชื่อ function ให้มันสื่อถึงงานที่มันทำ เราไม่ต้องไปสนใจเลยว่า function มันจะคำนวณยังไง เราก็ยังสามารถเข้าใจภาพรวมของโปรแกรมได้ง่ายๆเลย

ทีนี้เราก็ใส่ applyTax เข้าไปด้วย

17 collapsed lines
import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from "effect"
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
discountPercent: number
discountPercent
: number): (
cartPrice: number
cartPrice
: number) => number {
return (
cartPrice: number
cartPrice
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
function
function applyTax(taxRate: number): (discountedPrice: number) => number
applyTax
(
taxRate: number
taxRate
: number): (
discountedPrice: number
discountedPrice
: number) => number {
return (
discountedPrice: number
discountedPrice
: number) =>
discountedPrice: number
discountedPrice
+ (
discountedPrice: number
discountedPrice
* (
taxRate: number
taxRate
/ 100))
}
function
function toThaiBaht(thbPerUsd: number): (price: number) => number
toThaiBaht
(
thbPerUsd: number
thbPerUsd
: number): (
price: number
price
: number) => number {
return (
price: number
price
: number) =>
price: number
price
*
thbPerUsd: number
thbPerUsd
}
const
const discountPercent: 10
discountPercent
= 10
const
const taxPercent: 7
taxPercent
= 7
const
const thbPerUsd: 35.2
thbPerUsd
= 35.2
const
const priceAfterTax: number
priceAfterTax
=
pipe<Item[], number, number, number, number>(a: Item[], ab: (a: Item[]) => number, bc: (b: number) => number, cd: (c: number) => number, de: (d: number) => number): number (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
const shoppingCart: Item[]
shoppingCart
,
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
,
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
const discountPercent: 10
discountPercent
),
function applyTax(taxRate: number): (discountedPrice: number) => number
applyTax
(
const taxPercent: 7
taxPercent
),
function toThaiBaht(thbPerUsd: number): (price: number) => number
toThaiBaht
(
const thbPerUsd: 35.2
thbPerUsd
)
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
({
priceAfterTax: number
priceAfterTax
})

จากใน pipe() เราก็จะอ่านว่า

มีตะกร้าสินค้า
|> คำนวณราคาทั้งตะกร้า
|> ใส่ส่วนลด 10%
|> ใส่ tax 7%
|> แปลงเป็นหน่วยบาท ด้วย rate=35.2
|> จะได้ราคาที่ต้องจ่าย


Function Overloading

แต่ในบางครั้ง เราก็อยากจะใช้ function ตัวนั้นตัวเดียว ไม่ได้เอามา compose กันเหมือนในตัวอย่างก่อนหน้า
เช่นถ้าเราแค่อยากลองคำนวณ priceAfterDiscounted เราก็จะใช้แค่ function applyDiscount() อย่างเดียว

เราก็จะเรียกใช้ function แบบนี้ applyDiscount(10)(100)

function
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(
discountPercent: number
discountPercent
: number): (
cartPrice: number
cartPrice
: number) => number {
return (
cartPrice: number
cartPrice
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
const
const price: 100
price
= 100
const
const priceAfterDiscounted: number
priceAfterDiscounted
=
function applyDiscount(discountPercent: number): (cartPrice: number) => number
applyDiscount
(10)(100)

เราต้องใส่วงเล็บต่อๆกัน โดยเราต้องใส่ discountPercent ก่อน แล้วจึงใส่ price ตามหลัง

แต่ในทีแรก function เดิมในตอนที่มี parameters 2 ตัว เราต้องใส่ price ก่อนแล้วตามด้วย discountPercent

function
function applyDiscount(cartPrice: number, discountPercent: number): number
applyDiscount
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number): number {
return
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}

จากปัญหาด้านบน ถ้าเป็น binary function ก็ให้เอา price ไว้ด้านหน้า
ถ้าเป็น curried function แล้วให้เอา price ไว้ด้านหลัง
เราสามารถใช้ Typescript feature Function Overloading เพื่อแก้ปัญหานี้ได้

มาดูตัวอย่างกัน

function applyDiscount(cartPriceOrDiscountPercent: number, discountPercent: number): number;
function applyDiscount(cartPriceOrDiscountPercent: number): (cartPrice: number) => number;
function applyDiscount(cartPriceOrDiscountPercent: number, discountPercent?: number): number | ((cartPrice: number) => number) {
if (discountPercent) {
const cartPrice = cartPriceOrDiscountPercent
return cartPrice - (cartPrice * (discountPercent / 100))
}
return (cartPrice: number) => cartPrice - (cartPrice * (cartPriceOrDiscountPercent / 100))
}

ด้านบนเป็นตัวอย่างการเขียน Function Overloading
ทำให้เรามี function ที่มีชื่อเดียวกัน แต่ parameters และ return type ไม่เหมือนกันได้
แต่พอจะเขียนการทำงานจริงๆ มันจะซับซ้อนตรงนี้แหละ
การใช้ Function Overloading ผมจะไม่ลงลึกมากนะครับ
แค่ยกตัวอย่างมาเพื่อให้เพื่อนๆเห็นว่าสามารถแก้ปัญหาแบบนี้ได้ด้วย

ใครสนใจเรื่อง Function overloading ไปอ่านได้ที่ Blog ของคุณ Dmitri Pavlutin


Dual Function from Effect

มาดู solution ที่ผมใช้ประจำในทุกวันนี้นะครับ
เดี๋ยวตอนท้ายจะใส่ตัวอย่างที่ผมใช้เมื่อนานมาแล้วให้ด้วยนะครับ

ใน Effect มี dual() ทำให้เราแก้ปัญหาด้านบนได้ง่ายกว่า

การใช้ dual() จะทำให้เรา define function แบบ binary function ไปเลยครั้งเดียว แล้ว dual() จะเอาไปทำเป็น curried ให้เราเองเลย แล้วมันก็จะสลับตำแหน่งให้เลย
แต่เราต้อง define type เอง ทั้งสองแบบ dual() ไม่สามารถ infer type ให้เราได้

How to use dual()

มาดูวิธีใช้กัน

import {
import Function
Function
} from "effect"
function
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number): number {
return
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
const
const applyDiscount: ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number)
applyDiscount
=
import Function
Function
.
const dual: <(discountedPrice: number) => (cartPrice: number) => number, (cartPrice: number, discountPercent: number) => number>(arity: 2, body: (cartPrice: number, discountPercent: number) => number) => ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number) (+1 overload)

Creates a function that can be used in a data-last (aka pipeable) or data-first style.

The first parameter to dual is either the arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.

Using the arity is the most common use case, but there are some cases where you may want to use a predicate. For example, if you have a function that takes an optional argument, you can use a predicate to determine if the function is being used in a data-first or data-last style.

You can pass either the arity of the uncurried function or a predicate which determines if the function is being used in a data-first or data-last style.

Example (Using arity to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(2, (self, that) => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using call signatures to define the overloads)

import { dual, pipe } from "effect/Function"
const sum: {
(that: number): (self: number) => number
(self: number, that: number): number
} = dual(2, (self: number, that: number): number => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using a predicate to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(
(args) => args.length === 2,
(self, that) => self + that
)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

@since2.0.0

dual
<
(
discountedPrice: number
discountedPrice
: number) => (
cartPrice: number
cartPrice
: number) => number,
typeof
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
>(2,
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
)

เรามี function อยู่ก่อนแล้ว ผมใช้ชื่อว่า _applyDiscount() นะครับ ตามโค้ดด้านบน
เราจะเอามาใส่ใน dual() นี่แหละ
เราจะต้องบอก dual() ว่า function ของเรามี parameters กี่ตัว ในที่นี้คือ 2 ครับ แล้วตามด้วย function ของเรา
ส่วน type ของ function นี้ก็คือส่วนนี้
ในส่วนของ type เราจะต้องเอา type ของ function ที่มี parameters มากที่สุดไว้เป็นตัวสุดท้ายนะครับ ในที่นี้คือ typeof _applyDiscount

Function.dual<
(discountedPrice: number) => (cartPrice: number) => number,
typeof _applyDiscount
>

หรือถ้าใครไม่ได้มี function แยกออกมา อยากใส่การทำงานของ function ไว้ใน dual() เลย
ก็จะเขียนแบบนี้

import {
import Function
Function
} from "effect"
const
const applyDiscount: ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number)
applyDiscount
=
import Function
Function
.
const dual: <(discountedPrice: number) => (cartPrice: number) => number, (cartPrice: number, discountPercent: number) => number>(arity: 2, body: (cartPrice: number, discountPercent: number) => number) => ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number) (+1 overload)

Creates a function that can be used in a data-last (aka pipeable) or data-first style.

The first parameter to dual is either the arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.

Using the arity is the most common use case, but there are some cases where you may want to use a predicate. For example, if you have a function that takes an optional argument, you can use a predicate to determine if the function is being used in a data-first or data-last style.

You can pass either the arity of the uncurried function or a predicate which determines if the function is being used in a data-first or data-last style.

Example (Using arity to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(2, (self, that) => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using call signatures to define the overloads)

import { dual, pipe } from "effect/Function"
const sum: {
(that: number): (self: number) => number
(self: number, that: number): number
} = dual(2, (self: number, that: number): number => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using a predicate to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(
(args) => args.length === 2,
(self, that) => self + that
)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

@since2.0.0

dual
<
(
discountedPrice: number
discountedPrice
: number) => (
cartPrice: number
cartPrice
: number) => number,
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number) => number
>(
2,
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number) =>
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
)
applyDiscount
const applyDiscount: ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number)

ส่วนการใช้งานก็จะเอามาใช้แบบนี้

14 collapsed lines
import {
import Function
Function
} from "effect"
function
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number): number {
return
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
const
const applyDiscount: ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number)
applyDiscount
=
import Function
Function
.
const dual: <(discountedPrice: number) => (cartPrice: number) => number, (cartPrice: number, discountPercent: number) => number>(arity: 2, body: (cartPrice: number, discountPercent: number) => number) => ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number) (+1 overload)

Creates a function that can be used in a data-last (aka pipeable) or data-first style.

The first parameter to dual is either the arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.

Using the arity is the most common use case, but there are some cases where you may want to use a predicate. For example, if you have a function that takes an optional argument, you can use a predicate to determine if the function is being used in a data-first or data-last style.

You can pass either the arity of the uncurried function or a predicate which determines if the function is being used in a data-first or data-last style.

Example (Using arity to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(2, (self, that) => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using call signatures to define the overloads)

import { dual, pipe } from "effect/Function"
const sum: {
(that: number): (self: number) => number
(self: number, that: number): number
} = dual(2, (self: number, that: number): number => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using a predicate to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(
(args) => args.length === 2,
(self, that) => self + that
)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

@since2.0.0

dual
<
(
discountedPrice: number
discountedPrice
: number) => (
cartPrice: number
cartPrice
: number) => number,
typeof
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
>(
2,
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
)
const
const discountPercent: 10
discountPercent
= 10
const
const price: 100
price
= 100
const
const discount1: number
discount1
=
const applyDiscount: (cartPrice: number, discountPercent: number) => number (+1 overload)
applyDiscount
(
const price: 100
price
,
const discountPercent: 10
discountPercent
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
("discount1:",
const discount1: number
discount1
) // discount1: 90
const
const discount2: number
discount2
=
const applyDiscount: (discountedPrice: number) => (cartPrice: number) => number (+1 overload)
applyDiscount
(
const discountPercent: 10
discountPercent
)(
const price: 100
price
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
("discount2:",
const discount2: number
discount2
) // discount2: 90
const
const discount10Percent: (cartPrice: number) => number
discount10Percent
=
const applyDiscount: (discountedPrice: number) => (cartPrice: number) => number (+1 overload)
applyDiscount
(
const discountPercent: 10
discountPercent
)
const
const discount3: number
discount3
=
const discount10Percent: (cartPrice: number) => number
discount10Percent
(
const price: 100
price
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
("discount3:",
const discount3: number
discount3
) // discount3: 90

Let’s replace all our functions with dual()

import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
,
import Function
Function
} from "effect"
16 collapsed lines
type
type Item = {
price: number;
quantity: number;
}
Item
= {
price: number
price
: number
quantity: number
quantity
: number
}
const
const shoppingCart: Item[]
shoppingCart
:
type Item = {
price: number;
quantity: number;
}
Item
[] = [
{
price: number
price
: 10,
quantity: number
quantity
: 2 }, // Two t-shirts
{
price: number
price
: 20,
quantity: number
quantity
: 1 }, // One pair of jeans
{
price: number
price
: 3,
quantity: number
quantity
: 3 } // Three pairs of socks
]
function
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
(
cart: Item[]
cart
:
type Item = {
price: number;
quantity: number;
}
Item
[]): number {
return
cart: Item[]
cart
.
Array<Item>.reduce<number>(callbackfn: (previousValue: number, currentValue: Item, currentIndex: number, array: Item[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
total: number
total
,
item: Item
item
) =>
total: number
total
+ (
item: Item
item
.
price: number
price
*
item: Item
item
.
quantity: number
quantity
), 0)
}
function
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
(
cartPrice: number
cartPrice
: number,
discountPercent: number
discountPercent
: number): number {
return
cartPrice: number
cartPrice
- (
cartPrice: number
cartPrice
* (
discountPercent: number
discountPercent
/ 100))
}
const
const applyDiscount: ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number)
applyDiscount
=
import Function
Function
.
const dual: <(discountedPrice: number) => (cartPrice: number) => number, (cartPrice: number, discountPercent: number) => number>(arity: 2, body: (cartPrice: number, discountPercent: number) => number) => ((discountedPrice: number) => (cartPrice: number) => number) & ((cartPrice: number, discountPercent: number) => number) (+1 overload)

Creates a function that can be used in a data-last (aka pipeable) or data-first style.

The first parameter to dual is either the arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.

Using the arity is the most common use case, but there are some cases where you may want to use a predicate. For example, if you have a function that takes an optional argument, you can use a predicate to determine if the function is being used in a data-first or data-last style.

You can pass either the arity of the uncurried function or a predicate which determines if the function is being used in a data-first or data-last style.

Example (Using arity to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(2, (self, that) => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using call signatures to define the overloads)

import { dual, pipe } from "effect/Function"
const sum: {
(that: number): (self: number) => number
(self: number, that: number): number
} = dual(2, (self: number, that: number): number => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using a predicate to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(
(args) => args.length === 2,
(self, that) => self + that
)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

@since2.0.0

dual
<
(
discountedPrice: number
discountedPrice
: number) => (
cartPrice: number
cartPrice
: number) => number,
typeof
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
>(2,
function _applyDiscount(cartPrice: number, discountPercent: number): number
_applyDiscount
)
function
function _applyTax(discountedPrice: number, taxRate: number): number
_applyTax
(
discountedPrice: number
discountedPrice
: number,
taxRate: number
taxRate
: number): number {
return
discountedPrice: number
discountedPrice
+ (
discountedPrice: number
discountedPrice
* (
taxRate: number
taxRate
/ 100))
}
const
const applyTax: ((taxRate: number) => (discountedPrice: number) => number) & ((discountedPrice: number, taxRate: number) => number)
applyTax
=
import Function
Function
.
const dual: <(taxRate: number) => (discountedPrice: number) => number, (discountedPrice: number, taxRate: number) => number>(arity: 2, body: (discountedPrice: number, taxRate: number) => number) => ((taxRate: number) => (discountedPrice: number) => number) & ((discountedPrice: number, taxRate: number) => number) (+1 overload)

Creates a function that can be used in a data-last (aka pipeable) or data-first style.

The first parameter to dual is either the arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.

Using the arity is the most common use case, but there are some cases where you may want to use a predicate. For example, if you have a function that takes an optional argument, you can use a predicate to determine if the function is being used in a data-first or data-last style.

You can pass either the arity of the uncurried function or a predicate which determines if the function is being used in a data-first or data-last style.

Example (Using arity to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(2, (self, that) => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using call signatures to define the overloads)

import { dual, pipe } from "effect/Function"
const sum: {
(that: number): (self: number) => number
(self: number, that: number): number
} = dual(2, (self: number, that: number): number => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using a predicate to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(
(args) => args.length === 2,
(self, that) => self + that
)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

@since2.0.0

dual
<
(
taxRate: number
taxRate
: number) => (
discountedPrice: number
discountedPrice
: number) => number,
typeof
function _applyTax(discountedPrice: number, taxRate: number): number
_applyTax
>(2,
function _applyTax(discountedPrice: number, taxRate: number): number
_applyTax
)
function
function _toThaiBaht(price: number, thbPerUsd: number): number
_toThaiBaht
(
price: number
price
: number,
thbPerUsd: number
thbPerUsd
: number) {
return
price: number
price
*
thbPerUsd: number
thbPerUsd
}
const
const toThaiBaht: ((thbPerUsd: number) => (price: number) => number) & ((price: number, thbPerUsd: number) => number)
toThaiBaht
=
import Function
Function
.
const dual: <(thbPerUsd: number) => (price: number) => number, (price: number, thbPerUsd: number) => number>(arity: 2, body: (price: number, thbPerUsd: number) => number) => ((thbPerUsd: number) => (price: number) => number) & ((price: number, thbPerUsd: number) => number) (+1 overload)

Creates a function that can be used in a data-last (aka pipeable) or data-first style.

The first parameter to dual is either the arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.

Using the arity is the most common use case, but there are some cases where you may want to use a predicate. For example, if you have a function that takes an optional argument, you can use a predicate to determine if the function is being used in a data-first or data-last style.

You can pass either the arity of the uncurried function or a predicate which determines if the function is being used in a data-first or data-last style.

Example (Using arity to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(2, (self, that) => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using call signatures to define the overloads)

import { dual, pipe } from "effect/Function"
const sum: {
(that: number): (self: number) => number
(self: number, that: number): number
} = dual(2, (self: number, that: number): number => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

Example (Using a predicate to determine data-first or data-last style)

import { dual, pipe } from "effect/Function"
const sum = dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(
(args) => args.length === 2,
(self, that) => self + that
)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5

@since2.0.0

dual
<
(
thbPerUsd: number
thbPerUsd
: number) => (
price: number
price
: number) => number,
typeof
function _toThaiBaht(price: number, thbPerUsd: number): number
_toThaiBaht
>(2,
function _toThaiBaht(price: number, thbPerUsd: number): number
_toThaiBaht
)
const
const discountPercent: 10
discountPercent
= 10
const
const taxPercent: 7
taxPercent
= 7
const
const thbPerUsd: 35.2
thbPerUsd
= 35.2
const
const priceAfterTax: number
priceAfterTax
=
pipe<Item[], number, number, number, number>(a: Item[], ab: (a: Item[]) => number, bc: (b: number) => number, cd: (c: number) => number, de: (d: number) => number): number (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
const shoppingCart: Item[]
shoppingCart
,
function calculatePriceInCart(cart: Item[]): number
calculatePriceInCart
,
const applyDiscount: (discountedPrice: number) => (cartPrice: number) => number (+1 overload)
applyDiscount
(
const discountPercent: 10
discountPercent
),
const applyTax: (taxRate: number) => (discountedPrice: number) => number (+1 overload)
applyTax
(
const taxPercent: 7
taxPercent
),
const toThaiBaht: (thbPerUsd: number) => (price: number) => number (+1 overload)
toThaiBaht
(
const thbPerUsd: 35.2
thbPerUsd
)
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
({
priceAfterTax: number
priceAfterTax
})

What if my functions have 3 or more parameters?

ส่วนนี้ dual() ไม่ได้ support ที่จะแปลง functions นั้นให้เป็น curried ทั้งหมดนะครับ
จะได้แค่เอา parameter ตัวแรกแยกไปเป็น Curried function อย่างเดียว

ทำไมละ
ต้องย้อนกลับไปก่อนว่า เราต้องการใช้ function กับ pipe() นะครับ pipe ต้องการ function ที่รับ parameter แค่ตัวเดียว
เราก็เลยจะดึง parameter ตัวแรก แยกไปทำ Curried Function แค่ครั้งเดียว
ซึ่ง dual() support แค่เรื่องนี้
ส่วนตัวผมก็มองว่ามันควรจะเป็นแบบนี้แหละ


Conclusion

blog นี้เพื่อนๆน่าจะได้รู้จักกับ

  • Curry
  • Unary function
  • Binary function
  • pipe function
  • Effect’s dual function

ขอบคุณเพื่อนๆที่อ่านมาจนถึงตรงนี้นะครับ
blog นี้ทีแรกคิดว่าเขียนวันเดียวก็น่าจะเสร็จ ทีแรกก็เขียนเรื่องของ dual() เลย แต่คิดไปคิดมา อยากให้รู้ที่มาที่ไปด้วย ก็เลยเพิ่มตัวอย่าง เปลี่ยนไปเปลี่ยนมา สุดท้ายใช้เวลา 2 วันเลย

หวังว่าเพื่อนๆน่าจะนำไปใช้งานได้นะครับ โดยเฉพาะคนที่ชอบใช้ pipe() นะครับ
อนาคตถ้า TC39 ปล่อย pipe operator ออกมาให้ใช้ dual() ก็น่าจะฮิตขึ้นมาอีกเยอะเลย

ใน blog นี้ผมพยายามทำให้มัน practical ครับ ใส่ตัวอย่างให้เห็นภาพ
แต่ถ้าจะเอาให้ถูกต้องผมควรใช้วิธีการอธิบายแบบ Self กับ That ซึ่งผมคิดตัวอย่างไม่ออก ฮ่าๆ

ส่วน blog นี้ยังไม่จบ ผมแถมให้ว่าเมื่อก่อนผมแปลง function ให้ไปเป็น Curried function ยังไง


Bonus Another solution

ผมใช้ code นี้ครับ
ผมก๊อปมาจาก lib date-fns อีกทีนึงนะครับ

142 collapsed lines
/**
* The type of a function that can be converted to FP.
*/
export type
type FPFnInput = (...args: any[]) => any

The type of a function that can be converted to FP.

FPFnInput
= (...
args: any[]
args
: any[]) => any;
/**
* The supported arity type.
*/
export type
type FPArity = 1 | 2 | 3 | 4

The supported arity type.

FPArity
= 1 | 2 | 3 | 4;
/**
* FP function interface. It infers the arity of the function and returns the
* corresponding FP function interface.
*/
export type
type FPFn<Fn extends FPFnInput, Arity extends FPArity> = Arity extends 4 ? FPFn4<ReturnType<Fn>, Parameters<Fn>[3], Parameters<Fn>[2], Parameters<Fn>[1], Parameters<...>[0]> : Arity extends 3 ? FPFn3<...> : Arity extends 2 ? FPFn2<...> : Arity extends 1 ? FPFn1<...> : never

FP function interface. It infers the arity of the function and returns the corresponding FP function interface.

FPFn
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
extends
type FPFnInput = (...args: any[]) => any

The type of a function that can be converted to FP.

FPFnInput
,
function (type parameter) Arity in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Arity
extends
type FPArity = 1 | 2 | 3 | 4

The supported arity type.

FPArity
> =
function (type parameter) Arity in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Arity
extends 4
?
interface FPFn4<Result, Arg4, Arg3, Arg2, Arg1>

FP function interface with 4 arguments.

FPFn4
<
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>,
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[3],
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[2],
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[1],
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[0]
>
:
function (type parameter) Arity in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Arity
extends 3
?
interface FPFn3<Result, Arg3, Arg2, Arg1>

FP function interface with 3 arguments.

FPFn3
<
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>,
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[2],
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[1],
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[0]
>
:
function (type parameter) Arity in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Arity
extends 2
?
interface FPFn2<Result, Arg2, Arg1>

FP function interface with 2 arguments.

FPFn2
<
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>,
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[1],
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[0]>
:
function (type parameter) Arity in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Arity
extends 1
?
interface FPFn1<Result, Arg>

FP function interface with 1 arguments.

FPFn1
<
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>,
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Obtain the parameters of a function type in a tuple

Parameters
<
function (type parameter) Fn in type FPFn<Fn extends FPFnInput, Arity extends FPArity>
Fn
>[0]>
: never;
/**
* FP function interface with 1 arguments.
*/
export interface
interface FPFn1<Result, Arg>

FP function interface with 1 arguments.

FPFn1
<
function (type parameter) Result in FPFn1<Result, Arg>
Result
,
function (type parameter) Arg in FPFn1<Result, Arg>
Arg
> {
/**
* Curried version of the function. Returns itself.
*/
():
interface FPFn1<Result, Arg>

FP function interface with 1 arguments.

FPFn1
<
function (type parameter) Result in FPFn1<Result, Arg>
Result
,
function (type parameter) Arg in FPFn1<Result, Arg>
Arg
>;
/**
* Returns the result of the function call.
*/
(
arg: Arg
arg
:
function (type parameter) Arg in FPFn1<Result, Arg>
Arg
):
function (type parameter) Result in FPFn1<Result, Arg>
Result
;
}
/**
* FP function interface with 2 arguments.
*/
export interface
interface FPFn2<Result, Arg2, Arg1>

FP function interface with 2 arguments.

FPFn2
<
function (type parameter) Result in FPFn2<Result, Arg2, Arg1>
Result
,
function (type parameter) Arg2 in FPFn2<Result, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn2<Result, Arg2, Arg1>
Arg1
> {
/**
* Curried version of the function. Returns itself.
*/
():
interface FPFn2<Result, Arg2, Arg1>

FP function interface with 2 arguments.

FPFn2
<
function (type parameter) Result in FPFn2<Result, Arg2, Arg1>
Result
,
function (type parameter) Arg2 in FPFn2<Result, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn2<Result, Arg2, Arg1>
Arg1
>;
/**
* Curried version of the function. Returns a function that accepts the rest
* arguments.
*/
(
arg2: Arg2
arg2
:
function (type parameter) Arg2 in FPFn2<Result, Arg2, Arg1>
Arg2
):
interface FPFn1<Result, Arg>

FP function interface with 1 arguments.

FPFn1
<
function (type parameter) Result in FPFn2<Result, Arg2, Arg1>
Result
,
function (type parameter) Arg1 in FPFn2<Result, Arg2, Arg1>
Arg1
>;
/**
* Returns the result of the function call.
*/
(
arg2: Arg2
arg2
:
function (type parameter) Arg2 in FPFn2<Result, Arg2, Arg1>
Arg2
,
arg1: Arg1
arg1
:
function (type parameter) Arg1 in FPFn2<Result, Arg2, Arg1>
Arg1
):
function (type parameter) Result in FPFn2<Result, Arg2, Arg1>
Result
;
}
/**
* FP function interface with 3 arguments.
*/
export interface
interface FPFn3<Result, Arg3, Arg2, Arg1>

FP function interface with 3 arguments.

FPFn3
<
function (type parameter) Result in FPFn3<Result, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg3 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg3
,
function (type parameter) Arg2 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg1
> {
/**
* Curried version of the function. Returns itself.
*/
():
interface FPFn3<Result, Arg3, Arg2, Arg1>

FP function interface with 3 arguments.

FPFn3
<
function (type parameter) Result in FPFn3<Result, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg3 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg3
,
function (type parameter) Arg2 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg1
>;
/**
* Curried version of the function. Returns a function that accepts the rest
* arguments.
*/
(
arg3: Arg3
arg3
:
function (type parameter) Arg3 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg3
):
interface FPFn2<Result, Arg2, Arg1>

FP function interface with 2 arguments.

FPFn2
<
function (type parameter) Result in FPFn3<Result, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg2 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg1
>;
/**
* Curried version of the function. Returns a function that accepts the rest
* arguments.
*/
(
arg3: Arg3
arg3
:
function (type parameter) Arg3 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg3
,
arg2: Arg2
arg2
:
function (type parameter) Arg2 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg2
):
interface FPFn1<Result, Arg>

FP function interface with 1 arguments.

FPFn1
<
function (type parameter) Result in FPFn3<Result, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg1 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg1
>;
/**
* Returns the result of the function call.
*/
(
arg3: Arg3
arg3
:
function (type parameter) Arg3 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg3
,
arg2: Arg2
arg2
:
function (type parameter) Arg2 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg2
,
arg1: Arg1
arg1
:
function (type parameter) Arg1 in FPFn3<Result, Arg3, Arg2, Arg1>
Arg1
):
function (type parameter) Result in FPFn3<Result, Arg3, Arg2, Arg1>
Result
;
}
/**
* FP function interface with 4 arguments.
*/
export interface
interface FPFn4<Result, Arg4, Arg3, Arg2, Arg1>

FP function interface with 4 arguments.

FPFn4
<
function (type parameter) Result in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg4 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg4
,
function (type parameter) Arg3 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg3
,
function (type parameter) Arg2 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg1
> {
/**
* Curried version of the function. Returns itself.
*/
():
interface FPFn4<Result, Arg4, Arg3, Arg2, Arg1>

FP function interface with 4 arguments.

FPFn4
<
function (type parameter) Result in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg4 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg4
,
function (type parameter) Arg3 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg3
,
function (type parameter) Arg2 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg1
>;
/**
* Curried version of the function. Returns a function that accepts the rest
* arguments.
*/
(
arg4: Arg4
arg4
:
function (type parameter) Arg4 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg4
):
interface FPFn3<Result, Arg3, Arg2, Arg1>

FP function interface with 3 arguments.

FPFn3
<
function (type parameter) Result in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg3 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg3
,
function (type parameter) Arg2 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg1
>;
/**
* Curried version of the function. Returns a function that accepts the rest
* arguments.
*/
(
arg4: Arg4
arg4
:
function (type parameter) Arg4 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg4
,
arg3: Arg3
arg3
:
function (type parameter) Arg3 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg3
):
interface FPFn2<Result, Arg2, Arg1>

FP function interface with 2 arguments.

FPFn2
<
function (type parameter) Result in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg2 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg2
,
function (type parameter) Arg1 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg1
>;
/**
* Curried version of the function. Returns a function that accepts the rest
* arguments.
*/
(
arg4: Arg4
arg4
:
function (type parameter) Arg4 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg4
,
arg3: Arg3
arg3
:
function (type parameter) Arg3 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg3
,
arg2: Arg2
arg2
:
function (type parameter) Arg2 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg2
):
interface FPFn1<Result, Arg>

FP function interface with 1 arguments.

FPFn1
<
function (type parameter) Result in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Result
,
function (type parameter) Arg1 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg1
>;
/**
* Returns the result of the function call.
*/
(
arg4: Arg4
arg4
:
function (type parameter) Arg4 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg4
,
arg3: Arg3
arg3
:
function (type parameter) Arg3 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg3
,
arg2: Arg2
arg2
:
function (type parameter) Arg2 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg2
,
arg1: Arg1
arg1
:
function (type parameter) Arg1 in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Arg1
):
function (type parameter) Result in FPFn4<Result, Arg4, Arg3, Arg2, Arg1>
Result
;
}
/**
* Converts a function to a curried function that accepts arguments in reverse
* order.
*
* @param fn - The function to convert to FP
* @param arity - The arity of the function
* @param curriedArgs - The curried arguments
*
* @returns FP version of the function
*
* @private
*/
export function
function convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>

Converts a function to a curried function that accepts arguments in reverse order.

@paramfn - The function to convert to FP

@paramarity - The arity of the function

@paramcurriedArgs - The curried arguments

@returnsFP version of the function

@private

convertToFP
<
function (type parameter) Fn in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Fn
extends
type FPFnInput = (...args: any[]) => any

The type of a function that can be converted to FP.

FPFnInput
,
function (type parameter) Arity in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Arity
extends
type FPArity = 1 | 2 | 3 | 4

The supported arity type.

FPArity
>(
fn: Fn extends FPFnInput

  • The function to convert to FP

@paramfn - The function to convert to FP

fn
:
function (type parameter) Fn in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Fn
,
arity: Arity extends FPArity

  • The arity of the function

@paramarity - The arity of the function

arity
:
function (type parameter) Arity in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Arity
,
curriedArgs: unknown[]

  • The curried arguments

@paramcurriedArgs - The curried arguments

curriedArgs
: unknown[] = [],
):
type FPFn<Fn extends FPFnInput, Arity extends FPArity> = Arity extends 4 ? FPFn4<ReturnType<Fn>, Parameters<Fn>[3], Parameters<Fn>[2], Parameters<Fn>[1], Parameters<...>[0]> : Arity extends 3 ? FPFn3<...> : Arity extends 2 ? FPFn2<...> : Arity extends 1 ? FPFn1<...> : never

FP function interface. It infers the arity of the function and returns the corresponding FP function interface.

FPFn
<
function (type parameter) Fn in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Fn
,
function (type parameter) Arity in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Arity
> {
return (
curriedArgs: unknown[]

  • The curried arguments

@paramcurriedArgs - The curried arguments

curriedArgs
.
Array<unknown>.length: number

Gets or sets the length of the array. This is a number one higher than the highest index in the array.

length
>=
arity: Arity extends FPArity

  • The arity of the function

@paramarity - The arity of the function

arity
?
fn: Fn
(...args: any[]) => any

  • The function to convert to FP

@paramfn - The function to convert to FP

fn
(...
curriedArgs: unknown[]

  • The curried arguments

@paramcurriedArgs - The curried arguments

curriedArgs
.
Array<unknown>.slice(start?: number, end?: number): unknown[]

Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array.

@paramstart The beginning index of the specified portion of the array. If start is undefined, then the slice begins at index 0.

@paramend The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. If end is undefined, then the slice extends to the end of the array.

slice
(0,
arity: FPArity

  • The arity of the function

@paramarity - The arity of the function

arity
).
Array<unknown>.reverse(): unknown[]

Reverses the elements in an array in place. This method mutates the array and returns a reference to the same array.

reverse
())
: (...
args: unknown[]
args
: unknown[]) =>
function convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>

Converts a function to a curried function that accepts arguments in reverse order.

@paramfn - The function to convert to FP

@paramarity - The arity of the function

@paramcurriedArgs - The curried arguments

@returnsFP version of the function

@private

convertToFP
(
fn: Fn extends FPFnInput

  • The function to convert to FP

@paramfn - The function to convert to FP

fn
,
arity: Arity extends FPArity

  • The arity of the function

@paramarity - The arity of the function

arity
,
curriedArgs: unknown[]

  • The curried arguments

@paramcurriedArgs - The curried arguments

curriedArgs
.
Array<unknown>.concat(...items: ConcatArray<unknown>[]): unknown[] (+1 overload)

Combines two or more arrays. This method returns a new array without modifying any existing arrays.

@paramitems Additional arrays and/or items to add to the end of the array.

concat
(
args: unknown[]
args
))
) as
type FPFn<Fn extends FPFnInput, Arity extends FPArity> = Arity extends 4 ? FPFn4<ReturnType<Fn>, Parameters<Fn>[3], Parameters<Fn>[2], Parameters<Fn>[1], Parameters<...>[0]> : Arity extends 3 ? FPFn3<...> : Arity extends 2 ? FPFn2<...> : Arity extends 1 ? FPFn1<...> : never

FP function interface. It infers the arity of the function and returns the corresponding FP function interface.

FPFn
<
function (type parameter) Fn in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Fn
,
function (type parameter) Arity in convertToFP<Fn extends FPFnInput, Arity extends FPArity>(fn: Fn, arity: Arity, curriedArgs?: unknown[]): FPFn<Fn, Arity>
Arity
>;
}
// usage example
const
const _addAndMultiply: (a: number, b: number, multiplier: number) => number
_addAndMultiply
= (
a: number
a
: number,
b: number
b
: number,
multiplier: number
multiplier
: number) => (
a: number
a
+
b: number
b
) *
multiplier: number
multiplier
const
const addAndMultiply: FPFn3<number, number, number, number>
addAndMultiply
=
function convertToFP<(a: number, b: number, multiplier: number) => number, 3>(fn: (a: number, b: number, multiplier: number) => number, arity: 3, curriedArgs?: unknown[]): FPFn3<number, number, number, number>

Converts a function to a curried function that accepts arguments in reverse order.

@paramfn - The function to convert to FP

@paramarity - The arity of the function

@paramcurriedArgs - The curried arguments

@returnsFP version of the function

@private

convertToFP
(
const _addAndMultiply: (a: number, b: number, multiplier: number) => number
_addAndMultiply
, 3)
const
const a: 10
a
= 10
const
const b: 20
b
= 20
const
const c: 3
c
= 3
const
const add1: number
add1
: number =
const addAndMultiply: FPFn3
(arg3: number, arg2: number, arg1: number) => number (+3 overloads)

Returns the result of the function call.

addAndMultiply
(
const c: 3
c
,
const b: 20
b
,
const a: 10
a
)
const
const add2: number
add2
=
const addAndMultiply: FPFn3
(arg3: number, arg2: number) => FPFn1<number, number> (+3 overloads)

Curried version of the function. Returns a function that accepts the rest arguments.

addAndMultiply
(
const c: 3
c
,
const b: 20
b
)(
const a: 10
a
)
const
const add3: number
add3
=
const addAndMultiply: FPFn3
(arg3: number) => FPFn2<number, number, number> (+3 overloads)

Curried version of the function. Returns a function that accepts the rest arguments.

addAndMultiply
(
const c: 3
c
)(
const b: 20
b
)(
const a: 10
a
)
const
const add4: number
add4
=
const addAndMultiply: FPFn3
(arg3: number) => FPFn2<number, number, number> (+3 overloads)

Curried version of the function. Returns a function that accepts the rest arguments.

addAndMultiply
(
const c: 3
c
)(
const b: 20
b
,
const a: 10
a
)

code ด้านบน สามารถแปลง function เป็น Curried function ได้หลากหลายมากๆ มากกว่า dual() ด้วย แต่ผมไม่ได้มี use cases เยอะขนาดนั้น


Crafted with care 📝❤️ ✨ bycode sook logoCodeSook