whatiskadudoing/fp-ts-skills/fp-fundamentals

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Core FP concepts including pure functions, currying, composition, and pointfree style - the foundation for mastering functional TypeScript

Install

npx vskill@latest install whatiskadudoing/fp-ts-skills/fp-fundamentals

SKILL.md

Functional Programming Fundamentals

This skill covers the foundational concepts of functional programming. Master these before diving into fp-ts types like Option, Either, and Task. These concepts apply universally across functional programming languages and libraries.

Why Functional Programming?

Functional programming provides:

  • Predictability: Pure functions always produce the same output for the same input
  • Testability: No mocking of global state or dependencies
  • Composability: Small functions combine into complex behavior
  • Maintainability: Isolated pieces are easier to understand and change
  • Parallelization: Pure functions can safely run concurrently

1. Pure Functions and Referential Transparency

What is a Pure Function?

A pure function has two properties:

  1. Same input, same output: Given the same arguments, it always returns the same result
  2. No side effects: It doesn't modify external state, perform I/O, or cause observable changes
Why It Matters

Pure functions are:

  • Cacheable: Results can be memoized since they're deterministic
  • Portable: No hidden dependencies on external state
  • Testable: No setup/teardown of global state needed
  • Parallelizable: Safe to run concurrently without race conditions
  • Reasoned about locally: You can understand them without knowing the entire system
Examples
typescript
// PURE: Same input always gives same output
const add = (x: number, y: number): number => x + y
const greet = (name: string): string => `Hello, ${name}!`
const head = <T>(arr: readonly T[]): T | undefined => arr[0]
// IMPURE: Depends on external state
let counter = 0
const incrementCounter = (): number => {
counter += 1 // Mutates external state
return counter
}
// IMPURE: Different output for same input
const randomNumber = (): number => Math.random()
// IMPURE: Performs I/O
const logMessage = (msg: string): void => {
console.log(msg) // Side effect: writes to console
}
// IMPURE: Depends on mutable external state
const config = { apiUrl: 'https://api.example.com' }
const getApiUrl = (): string => config.apiUrl // Could return different values
Referential Transparency

An expression is referentially transparent if it can be replaced with its value without changing program behavior.

typescript
// Referentially transparent
const double = (x: number): number => x * 2
const result = double(5) + double(5)
// Can be replaced with: 10 + 10 = 20
// NOT referentially transparent
let count = 0
const incrementAndGet = (): number => {
count += 1
return count
}
const result2 = incrementAndGet() + incrementAndGet()
// Cannot replace with values: first call returns 1, second returns 2
// Result is 3, not 1 + 1 = 2
Making Impure Functions Pure
typescript
// IMPURE: Modifies input
const addItem = (arr: string[], item: string): void => {
arr.push(item) // Mutates the original array
}
// PURE: Returns new array
const addItem = (arr: readonly string[], item: string): readonly string[] =>
[...arr, item]
// IMPURE: Depends on Date
const getAge = (birthYear: number): number =>
new Date().getFullYear() - birthYear
// PURE: Accept date as parameter (dependency injection)
const getAge = (birthYear: number, currentYear: number): number =>
currentYear - birthYear
// IMPURE: Reads from database
const getUser = async (id: string): Promise<User> =>
await database.find(id)
// Functional approach: Return a description of the effect
// (This is the concept behind Task and IO in fp-ts)
const getUser = (id: string): Task<User> =>
() => database.find(id)
Common Mistakes
typescript
// MISTAKE 1: Mutating arguments
const sortArray = (arr: number[]): number[] => {
return arr.sort((a, b) => a - b) // sort() mutates the original!
}
// FIX: Create a copy first
const sortArray = (arr: readonly number[]): number[] =>
[...arr].sort((a, b) => a - b)
// MISTAKE 2: Using Date, Math.random, or other non-deterministic APIs
const generateId = (): string =>
`id-${Date.now()}-${Math.random()}`
// FIX: Accept dependencies as parameters
const generateId = (timestamp: number, random: number): string =>
`id-${timestamp}-${random}`
// MISTAKE 3: Hidden dependencies via closure
const multiplier = 2
const double = (x: number): number => x * multiplier // Captures external variable
// FIX: Make dependencies explicit
const multiply = (factor: number) => (x: number): number => x * factor
const double = multiply(2)

2. Currying and Partial Application

What is Currying?

Currying transforms a function that takes multiple arguments into a sequence of functions that each take a single argument.

typescript
// Uncurried: takes all arguments at once
const add = (x: number, y: number, z: number): number => x + y + z
add(1, 2, 3) // 6
// Curried: takes one argument at a time
const addCurried = (x: number) => (y: number) => (z: number): number => x + y + z
addCurried(1)(2)(3) // 6
Why It Matters

Currying enables:

  • Partial application: Create specialized functions by supplying some arguments
  • Composition: Single-argument functions compose cleanly
  • Reusability: Create families of related functions from one definition
  • Deferred computation: Supply arguments as they become available
Currying in Practice
typescript
// Curried function definition
const multiply = (x: number) => (y: number): number => x * y
// Create specialized functions through partial application
const double = multiply(2)
const triple = multiply(3)
double(5) // 10
triple(5) // 15
// Curried comparison helpers
const greaterThan = (threshold: number) => (value: number): boolean =>
value > threshold
const isAdult = greaterThan(17)
const isExpensive = greaterThan(100)
isAdult(25) // true
isExpensive(50) // false
Currying for Array Methods
typescript
// Curried filter predicate
const filterBy = <T>(predicate: (item: T) => boolean) =>
(items: readonly T[]): T[] => items.filter(predicate)
// Create reusable filters
const keepPositive = filterBy((n: number) => n > 0)
const keepEven = filterBy((n: number) => n % 2 === 0)
keepPositive([-1, 2, -3, 4]) // [2, 4]
keepEven([1, 2, 3, 4, 5, 6]) // [2, 4, 6]
// Curried map transformer
const mapWith = <A, B>(fn: (a: A) => B) =>
(items: readonly A[]): B[] => items.map(fn)
const doubleAll = mapWith((n: number) => n * 2)
const stringify = mapWith(String)
doubleAll([1, 2, 3]) // [2, 4, 6]
stringify([1, 2, 3]) // ['1', '2', '3']
Partial Application vs Currying
typescript
// Partial application: fixing some arguments of a function
const greet = (greeting: string, name: string): string =>
`${greeting}, ${name}!`
// Using bind for partial application (uncurried)
const sayHello = greet.bind(null, 'Hello')
sayHello('Alice') // "Hello, Alice!"
// Curried version is naturally partially applicable
const greetCurried = (greeting: string) => (name: string): string =>
`${greeting}, ${name}!`
const sayHello = greetCurried('Hello')
const sayGoodbye = greetCurried('Goodbye')
sayHello('Alice') // "Hello, Alice!"
sayGoodbye('Bob') // "Goodbye, Bob!"
Argument Order Matters
typescript
// BAD: Data first prevents easy partial application
const map = <A, B>(arr: A[], fn: (a: A) => B): B[] => arr.map(fn)
// Must use anonymous function to specialize
const doubleAll = (arr: number[]) => map(arr, n => n * 2)
// GOOD: Data last enables clean partial application
const map = <A, B>(fn: (a: A) => B) => (arr: readonly A[]): B[] => arr.map(fn)
// Specialize by supplying the function
const doubleAll = map((n: number) => n * 2)
const stringify = map(String)
// This is why fp-ts uses data-last curried functions
import * as A from 'fp-ts/Array'
import { pipe } from 'fp-ts/function'
pipe(
[1, 2, 3],
A.map(n => n * 2), // A.map takes fn first, returns function expecting array
A.filter(n => n > 2) // Same pattern
)
Common Mistakes
typescript
// MISTAKE 1: Inconsistent argument order
const process = (config: Config) => (data: Data) => (options: Options) => ...
const handle = (data: Data) => (config: Config) => ... // Inconsistent!
// FIX: Establish convention - typically: config -> options -> data
const process = (config: Config) => (options: Options) => (data: Data) => ...
const handle = (config: Config) => (data: Data) => ...
// MISTAKE 2: Over-currying simple functions
const add = (a: number) => (b: number) => (c: number) => (d: number) =>
a + b + c + d
add(1)(2)(3)(4) // Tedious to call
// FIX: Curry only to the level needed for your use case
const add = (a: number, b: number, c: number, d: number) => a + b + c + d
// Or group related parameters
const addPairs = (a: number, b: number) => (c: number, d: number) =>
a + b + c + d
// MISTAKE 3: Forgetting to curry when using with pipe
const formatPrice = (currency: string, amount: number): string =>
`${currency}${amount.toFixed(2)}`
pipe(
100,
formatPrice('$', ???) // Can't use in pipe - needs both args!
)
// FIX: Curry with data last
const formatPrice = (currency: string) => (amount: number): string =>
`${currency}${amount.toFixed(2)}`
pipe(
100,
formatPrice('$') // Works!
) // "$100.00"

3. Function Composition

What is Function Composition?

Function composition combines two or more functions to create a new function. The output of one function becomes the input of the next.

typescript
// Mathematical composition: (f . g)(x) = f(g(x))
const compose = <A, B, C>(f: (b: B) => C, g: (a: A) => B) =>
(x: A): C => f(g(x))
const addOne = (x: number): number => x + 1
const double = (x: number): number => x * 2
const addOneThenDouble = compose(double, addOne)
addOneThenDouble(5) // double(addOne(5)) = double(6) = 12
Why It Matters

Composition enables:

  • Building complex behavior from simple pieces: Small, focused functions combine into powerful transformations
  • Reusability: Composed functions can themselves be composed further
  • Readability: Each step has a clear, single purpose
  • Testing: Test small pieces in isolation, composition is mathematically guaranteed
Pipe vs Flow (Left-to-Right Composition)

Traditional mathematical composition reads right-to-left, which can be confusing. pipe and flow provide left-to-right composition.

typescript
import { pipe, flow } from 'fp-ts/function'
const addOne = (x: number): number => x + 1
const double = (x: number): number => x * 2
const toString = (x: number): string => `Value: ${x}`
// pipe: Execute immediately with a value
const result = pipe(
5,
addOne, // 6
double, // 12
toString // "Value: 12"
)
// flow: Create a reusable function
const transform = flow(
addOne,
double,
toString
)
transform(5) // "Value: 12"
transform(10) // "Value: 22"
Composition with Multiple Types
typescript
// Functions don't need matching types, just compatible connections
const parseNumber = (s: string): number => parseInt(s, 10)
const isEven = (n: number): boolean => n % 2 === 0
const toYesNo = (b: boolean): string => b ? 'Yes' : 'No'
const isInputEven = flow(
parseNumber, // string -> number
isEven, // number -> boolean
toYesNo // boolean -> string
)
isInputEven('4') // "Yes"
isInputEven('7') // "No"
Building Pipelines
typescript
import { pipe, flow } from 'fp-ts/function'
interface User {
name: string
email: string
age: number
}
// Small, focused functions
const getName = (user: User): string => user.name
const toUpperCase = (s: string): string => s.toUpperCase()
const addGreeting = (name: string): string => `Hello, ${name}!`
const addExcitement = (s: string): string => `${s}!!!`
// Compose into a pipeline
const greetUser = flow(
getName,
toUpperCase,
addGreeting,
addExcitement
)
greetUser({ name: 'alice', email: 'alice@example.com', age: 30 })
// "Hello, ALICE!!!!"

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
11
Repository forks
3
7-day trend score
12

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/whatiskadudoing/fp-ts-skills/fp-fundamentals/badge)](https://verified-skill.com/skills/whatiskadudoing/fp-ts-skills/fp-fundamentals)

Something wrong with this skill or its page? Tell us.