Scope Functions
Stop drowning in temporary variables and nested callbacks. Transform verbose spaghetti code into elegant, readable pipelines.
The Problem: TypeScript's Variable Hell
Every TypeScript developer knows this pain. You've been there.
The Familiar Nightmare
// We've all written this mess...
const data = await fetchData();
const validated = validateData(data);
console.log("Validation complete:", validated);
const transformed = transformData(validated);
const enriched = enrichWithMetadata(transformed);
console.log("Processing:", enriched.id);
const compressed = compressData(enriched);
const cached = await cacheData(compressed);
const result = formatForDisplay(cached);
return result;
// Variables everywhere. No clear flow. Cognitive overload.
// Each line depends on remembering what came before.
// Refactoring? Good luck moving these lines around.The Elegant Solution
❌ Before: Variable Soup
const user = await fetchUser(id);
const profile = user.profile;
const settings = profile.settings;
const theme = settings.theme;
const isDark = theme === 'dark';
if (isDark) {
applyDarkMode();
}
const formatted = formatUser(user);
return formatted;✅ After: Crystal Clear Intent
import { asScope } from 'kotlinify-ts/scope';
return await asScope(fetchUser(id))
.apply(u => {
if (u.profile.settings.theme === 'dark') {
applyDarkMode();
}
})
.let(user => formatUser(user))
.value();
// One expression. Clear data flow. Easy to refactor.Why This Matters
- →Reduced Cognitive Load: No more tracking 10 temporary variables in your head
- →Refactoring Safety: Move entire transformation chains without breaking dependencies
- →Clear Intent: Code reads like a story, not a puzzle
- →Fewer Bugs: Can't accidentally use the wrong variable when there aren't any
Installation & Setup
One import unlocks the full power of chainable scope functions
🚀 Chainable Scope Functions
The true power of kotlinify-ts comes from its chainable API. Wrap any value with asScope() to create elegant transformation pipelines with zero prototype pollution.
import { asScope } from 'kotlinify-ts/scope';
// Use asScope() for safe method chaining
const result = asScope(value)
.let(v => v.transform())
.also(v => console.log('Debug:', v))
.let(v => v.toString())
.value();
// Chain through complex transformations
const processed = asScope(getUserData())
.let(data => normalize(data))
.apply(data => {
data.timestamp = Date.now();
data.version = '2.0';
})
.also(data => cache.store(data))
.let(data => formatForDisplay(data))
.value();
// Alternative: Enable global prototype extensions (use with caution)
import { enableKotlinifyExtensions } from 'kotlinify-ts/scope';
enableKotlinifyExtensions();
// Now values have direct access to scope functions
const direct = value.let(v => v * 2).also(console.log);Why chaining matters: No intermediate variables, clear data flow, easy refactoring, and code that reads like a story.
let & letValue
Transform a value and return the result of the transformation
When to use:
- Converting nullable values to non-nullable results
- Executing a block of code only if a value is not null
- Introducing an expression as a variable in local scope
import { asScope, letValue } from 'kotlinify-ts/scope';
// Method 1: Use asScope() for safe chaining
const formatted = asScope(getUserData())
.let(data => data.normalize())
.let(data => data.validate())
.let(data => JSON.stringify(data))
.value();
// Method 2: Use standalone function
const upperName = letValue(
{ name: "Alice", age: 30 },
user => user.name.toUpperCase()
);
// Method 3: With prototype extensions enabled
import { enableKotlinifyExtensions } from 'kotlinify-ts/scope';
enableKotlinifyExtensions();
// Now use directly on values
const result = (5)
.let(x => x * 2) // 10
.let(x => x + 3) // 13
.let(x => x ** 2) // 169
.let(x => "Result: " + x);
// Real-world: API response processing
const displayName = await asScope(fetchUserProfile(id))
.let(profile => profile.firstName + " " + profile.lastName)
.let(fullName => fullName.trim() || "Anonymous")
.value();
// Chain through async operations
const processed = await asScope(fetchData())
.let(response => response.json())
.let(data => data.items)
.let(items => items.filter(i => i.active))
.value();Try it yourself:
// Transform user data with let
const user = { name: "Alice", age: 30 };
const greeting = asScope(user)
.let(u => `${u.name}, age ${u.age}`)
.let(text => text.toUpperCase())
.value();
console.log(greeting);
// Chain multiple transformations
const result = asScope(10)
.let(x => x * 2) // 20
.let(x => x + 5) // 25
.let(x => x ** 2) // 625
.value();
console.log('Result:', result);apply
Configure an object and return the same object - perfect for initialization
When to use:
- Object configuration and initialization
- Builder pattern implementation
- Setting multiple properties at once
// Use asScope() for chaining
const user = asScope({})
.apply(u => {
u.name = 'John Doe';
u.email = 'john@example.com';
u.role = 'user';
})
.apply(u => {
u.preferences = { theme: 'dark', language: 'en' };
})
.value();
console.log('User:', user);
// Combine apply with let
const jsonConfig = asScope({ database: {} })
.apply(cfg => {
cfg.database.host = 'localhost';
cfg.database.port = 5432;
cfg.database.name = 'myapp';
})
.let(cfg => JSON.stringify(cfg, null, 2))
.value();
console.log('Config:', jsonConfig);Try it yourself:
// Configure an object with apply
const config = asScope({})
.apply(cfg => {
cfg.name = 'MyApp';
cfg.version = '1.0.0';
cfg.debug = true;
})
.value();
console.log('Config:', JSON.stringify(config, null, 2));
// Chain apply with let
const settings = asScope({ theme: 'dark' })
.apply(s => {
s.fontSize = 14;
s.lineHeight = 1.5;
})
.let(s => `Theme: ${s.theme}, Font: ${s.fontSize}px`)
.value();
console.log(settings);also
Perform side effects like logging or validation and return the original value
When to use:
- Logging intermediate values in a chain
- Validation that doesn't transform the value
- Side effects like caching or analytics
// Use also for side effects (logging, debugging)
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = asScope(data)
.also(arr => console.log('Original:', arr.length, 'items'))
.let(arr => arr.filter(x => x % 2 === 0))
.also(arr => console.log('After filter:', arr.length, 'items'))
.let(arr => arr.map(x => x * 2))
.also(arr => console.log('After map:', arr))
.value();
console.log('Final result:', result);run
Execute a block with 'this' context and return the result - great for computed properties
When to use:
- Computing values based on object properties
- When you need 'this' context instead of parameter
- Complex calculations with multiple object properties
// Use run for calculations with 'this' context
const metrics = { views: 1000, clicks: 50, conversions: 10 };
const report = asScope(metrics)
.run(function() {
return {
total: this.views + this.clicks,
ctr: (this.clicks / this.views * 100).toFixed(2) + '%',
quality: this.conversions / this.clicks
};
})
.value();
console.log('Report:', report);
// Calculate area with run
const dimensions = { width: 10, height: 20 };
const area = asScope(dimensions)
.run(function() {
return this.width * this.height;
})
.value();
console.log('Area:', area);// Real-world: Complex calculations with context
const summary = await asScope(fetchUserStats(userId))
.run(function() {
const total = this.posts + this.comments + this.likes;
const average = total / 3;
return {
engagement: average > 100 ? 'high' : 'normal',
score: this.posts * 10 + this.comments * 5 + this.likes,
level: Math.floor(Math.log2(total))
};
})
.also(s => console.log('User summary:', s))
.value();
// Chain run with other scope functions
const processedStats = asScope(getPlayerStats())
.run(function() {
return {
kda: (this.kills + this.assists) / Math.max(this.deaths, 1),
winRate: (this.wins / this.games * 100).toFixed(1) + '%',
avgScore: this.totalScore / this.games
};
})
.apply(stats => {
stats.rank = calculateRank(stats.kda);
})
.also(stats => saveToLeaderboard(stats))
.value();Note: Use regular function syntax, not arrow functions, to access 'this' context
withValue
Like run, but with clearer syntax when the receiver is a parameter
import { withValue } from 'kotlinify-ts/scope';
// Execute multiple operations on an object
const htmlContent = withValue(
{ title: 'Hello', body: 'World', footer: '2024' },
function() {
return `
<article>
<h1>${this.title}</h1>
<main>${this.body}</main>
<footer>© ${this.footer}</footer>
</article>
`;
}
);
// Complex DOM manipulation
document.body.appendChild(
withValue(document.createElement('button'), function() {
this.textContent = 'Click me';
this.className = 'btn btn-primary';
this.onclick = () => alert('Clicked!');
return this;
})
);
// Building SQL queries dynamically
const query = withValue(
{ table: 'users', conditions: [], joins: [] },
function() {
this.conditions.push('active = true');
this.conditions.push('created_at > NOW() - INTERVAL 30 DAY');
this.joins.push('LEFT JOIN profiles ON users.id = profiles.user_id');
return `
SELECT * FROM ${this.table}
${this.joins.join(' ')}
WHERE ${this.conditions.join(' AND ')}
`;
}
);Null-Safe Variants
Handle nullable values gracefully without explicit null checks
Available for all scope functions: letOrNull, applyOrNull, alsoOrNull, runOrNull
These variants automatically handle null/undefined, returning null instead of throwing errors
import { letOrNull, applyOrNull, alsoOrNull, runOrNull } from 'kotlinify-ts/scope';
// Method 1: Use standalone null-safe functions
const userAge = letOrNull(getUserOrNull(id), user => user.age);
const configuredWidget = applyOrNull(findWidget(id), widget => {
widget.color = 'blue';
widget.size = 'large';
});
// Method 2: With prototype extensions (requires enableKotlinifyExtensions())
import { enableKotlinifyExtensions } from 'kotlinify-ts/scope';
enableKotlinifyExtensions();
// Safe chaining with nullable values - elegant and safe!
const result: string | null = maybeGetUser()
?.letOrNull(u => u.profile)
?.letOrNull(p => p.settings)
?.letOrNull(s => s.theme)
?.alsoOrNull(t => console.log('Theme:', t));
// Real-world: Safe API data processing
const displayData = fetchApiResponse()
?.letOrNull(res => res.data)
?.applyOrNull(data => {
data.timestamp = Date.now();
data.processed = true;
})
?.letOrNull(data => formatForDisplay(data))
?? 'No data available'; // Fallback value
// alsoOrNull - Side effects only if not null
const cachedValue = computeExpensiveValue()
?.alsoOrNull(value => cache.set(key, value));
// runOrNull - Compute only if not null
const fullName = findUserProfile(id)
?.runOrNull(function() {
return this.firstName + ' ' + this.lastName;
});
// Combine with other utility functions
import { takeIf } from 'kotlinify-ts/nullsafety';
const processed = getUserInput()
?.letOrNull(input => input.trim())
?.let(s => takeIf(s, s => s.length > 0))
?.let(s => s.toLowerCase())
?.also(s => log('Processing:', s))
?? 'default';Advanced Patterns
Combine scope functions for powerful, expressive code
Pipeline Pattern
import { asScope } from 'kotlinify-ts/scope';
// Clean, readable data processing pipeline
const apiResult = await asScope(fetchRawData())
.let(raw => parseJSON(raw))
.also(data => console.log('Parsed:', data))
.let(data => validateSchema(data))
.also(valid => metrics.recordValidation(valid))
.apply(data => {
data.processed = true;
data.timestamp = Date.now();
})
.let(data => compress(data))
.also(compressed => cache.store(compressed))
.value();
// Complex transformation pipeline
const processedData = asScope(rawData)
.let(data => normalizeData(data))
.also(d => validateData(d))
.let(normalized => enrichData(normalized))
.also(d => logProcessing(d))
.let(enriched => transformData(enriched))
.apply(result => {
result.processedAt = Date.now();
result.version = '2.0';
})
.value();
// Async pipeline with error handling
const result = await asScope(fetchUser(id))
.let(user => enrichUserData(user))
.also(user => console.log('Enriched:', user.id))
.let(user => validateUserPermissions(user))
.also(validated => auditLog.record(validated))
.let(user => prepareUserResponse(user))
.value();Builder Pattern
import { asScope } from 'kotlinify-ts/scope';
// Building complex objects fluently
const request = asScope({})
.apply(r => {
r.method = 'POST';
r.url = '/api/users';
})
.apply(r => {
r.headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
};
})
.apply(r => {
r.body = JSON.stringify({ name, email });
})
.also(r => console.log('Sending request:', r))
.let(r => fetch(r.url, r))
.value();Conditional Execution
import { asScope, also } from 'kotlinify-ts/scope';
// Execute different branches based on conditions
const result = asScope(getValue())
.let(v => v > 10 ? v * 2 : v)
.also(v => {
if (v > 100) console.warn('Large value:', v);
})
.let(v => shouldCache ? also(v, val => cache.set(key, val)) : v)
.apply(v => {
if (needsFormatting) {
v.formatted = true;
v.display = formatValue(v);
}
})
.value();Quick Reference
Returns Transformation Result
letValue(value, fn)→ fn(value)run(value, fn)→ fn.call(value)withValue(value, fn)→ fn.call(value)Returns Original Value
apply(value, fn)→ valuealso(value, fn)→ valueDecision Tree
• Need the transformed result? → Use letValue or asScope().let()
• Configuring an object? → Use apply
• Logging or side effects? → Use also
• Need 'this' context? → Use run or withValue
• Handling nullable values? → Use *OrNull variants
• Want method chaining? → Use asScope() for safe chaining