Lambda Expressions¶
Lambda expressions (arrow functions) provide a concise syntax for creating anonymous functions. They're particularly useful with array methods and functional programming patterns.
Basic Syntax¶
Single Parameter¶
Example:
Multiple Parameters¶
Example:
No Parameters¶
Example:
Block Body¶
Example:
numbers = [1, 2, 3, 4, 5];
processed = numbers.Map((x) => {
doubled = x * 2;
squared = doubled * doubled;
return squared;
});
Lambdas vs Functions¶
Similarities¶
Both can:
- Accept parameters
- Return values
- Access outer scope (closures)
Differences¶
| Feature | Lambda | Function |
|---|---|---|
| Syntax | Concise arrow syntax | function keyword |
| Name | Anonymous | Named |
| Usage | Inline, passed as values | Declared and called by name |
| Best For | Short operations, callbacks | Complex logic, reuse |
Example comparison:
// Lambda
doubled = numbers.Map((x) => x * 2);
// Function
function double(x) {
return x * 2;
}
doubled = numbers.Map(double);
Common Use Cases¶
Array Filtering¶
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Get even numbers
evens = numbers.Filter((x) => x % 2 == 0); // [2, 4, 6, 8, 10]
// Get numbers greater than 5
large = numbers.Filter((x) => x > 5); // [6, 7, 8, 9, 10]
// Complex condition
eligible = policies.Filter((p) => p.premium > 1000 && p.isActive);
Array Mapping¶
numbers = [1, 2, 3, 4, 5];
// Double values
doubled = numbers.Map((x) => x * 2); // [2, 4, 6, 8, 10]
// Square values
squared = numbers.Map((x) => x * x); // [1, 4, 9, 16, 25]
// Extract property
names = customers.Map((c) => c.name);
// Transform objects
simplified = policies.Map((p) => {
return {
number: p.policyNumber,
premium: p.annualPremium,
};
});
Array Reduction¶
numbers = [1, 2, 3, 4, 5];
// Sum
sum = numbers.ReduceToNum((acc, x) => acc + x, 0); // 15
// Product
product = numbers.ReduceToNum((acc, x) => acc * x, 1); // 120
// Max value
max = numbers.ReduceToNum((acc, x) => (x > acc ? x : acc), numbers[0]);
// Count matching
count = numbers.ReduceToNum((acc, x) => (x > 3 ? acc + 1 : acc), 0); // 2
Array Sorting¶
numbers = [5, 2, 8, 1, 9, 3];
// Ascending
ascending = numbers.OrderBy((x) => x); // [1, 2, 3, 5, 8, 9]
// Descending
descending = numbers.OrderByDescending((x) => x); // [9, 8, 5, 3, 2, 1]
// Sort objects by property
sorted = customers.OrderBy((c) => c.age);
sortedByName = customers.OrderBy((c) => c.lastName);
Array Iteration¶
names = ["Alice", "Bob", "Charlie"];
// Print each
names.ForEach((name) => $Log(name));
// With index
names.ForEach((name, i) => $Log(i + ": " + name));
// Output:
// 0: Alice
// 1: Bob
// 2: Charlie
Closures with Lambdas¶
Lambdas capture variables from their enclosing scope:
Capturing Variables¶
multiplier = 10;
numbers = [1, 2, 3, 4, 5];
scaled = numbers.Map((x) => x * multiplier); // [10, 20, 30, 40, 50]
Capturing in Loops¶
functions = [];
i = 0;
while (i < 3) {
value = i;
functions.Push(() => value);
i++;
}
// Each lambda captures its own copy of value
result0 = functions[0](); // 0
result1 = functions[1](); // 1
result2 = functions[2](); // 2
Factory with Lambdas¶
function makeMultiplier(factor) {
return (x) => x * factor; // Lambda captures factor
}
double = makeMultiplier(2);
triple = makeMultiplier(3);
result1 = double(5); // 10
result2 = triple(5); // 15
Parameter Patterns¶
Single Parameter¶
// Parentheses optional for single parameter
numbers = [1, 2, 3];
result1 = numbers.Map((x) => x * 2); // Without parentheses
result2 = numbers.Map((x) => x * 2); // With parentheses (same)
Two Parameters¶
Used with .Map(), .Reduce(), .ForEach():
items = ["a", "b", "c"];
// Map with index
indexed = items.Map((item, index) => item + index);
// ["a0", "b1", "c2"]
// ForEach with index
items.ForEach((item, index) => {
$Log("Item " + index + ": " + item);
});
Three Parameters¶
Used with .Reduce() variants:
numbers = [1, 2, 3, 4, 5];
// Accumulator, current item, index
result = numbers.ReduceToNum((acc, x, i) => {
$Log("Index " + i + ": adding " + x + " to " + acc);
return acc + x;
}, 0);
Expression vs Block Body¶
Expression Body¶
Returns the expression result automatically:
// Simple expression
doubled = numbers.Map((x) => x * 2);
// Ternary expression
signs = numbers.Map((x) => (x >= 0 ? "+" : "-"));
// Function call
uppercase = names.Map((n) => n.ToUpper());
Block Body¶
Requires explicit return:
// Multiple statements
processed = numbers.Map((x) => {
doubled = x * 2;
squared = doubled * doubled;
return squared; // Explicit return
});
// Conditional logic
categorized = ages.Map((age) => {
if (age < 18) {
return "minor";
} else if (age < 65) {
return "adult";
} else {
return "senior";
}
});
// Complex calculation
adjusted = policies.Map((p) => {
basePremium = p.coverage * 0.001;
if (p.customer.age < 25) {
basePremium *= 1.5;
}
return $Round(basePremium, 2);
});
Chaining Array Methods¶
Combine multiple operations:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Filter then map
result = numbers
.Filter((x) => x % 2 == 0) // [2, 4, 6, 8, 10]
.Map((x) => x * x); // [4, 16, 36, 64, 100]
// Filter, map, then reduce
sum = numbers
.Filter((x) => x > 5) // [6, 7, 8, 9, 10]
.Map((x) => x * 2) // [12, 14, 16, 18, 20]
.ReduceToNum((acc, x) => acc + x, 0); // 80
// Complex pipeline
processed = customers
.Filter((c) => c.isActive)
.Filter((c) => c.age >= 18)
.Map((c) => {
return {
name: c.firstName + " " + c.lastName,
email: c.email,
discount: c.yearsWithCompany > 5 ? 0.15 : 0.1,
};
})
.OrderBy((c) => c.name);
Common Patterns¶
Pattern 1: Data Transformation¶
policies = getPolicies();
// Transform to DTOs
dtos = policies.Map((p) => {
return {
policyNumber: p.number,
customerName: p.customer.firstName + " " + p.customer.lastName,
monthlyPremium: $Round(p.premium / 12, 2),
status: p.isActive ? "Active" : "Inactive",
};
});
Pattern 2: Filtering and Counting¶
orders = getOrders();
// Count high-value orders
highValueCount = orders.Filter((o) => o.total > 1000).Length;
// Sum of premium orders
premiumTotal = orders
.Filter((o) => o.isPremium)
.ReduceToNum((acc, o) => acc + o.total, 0);
Pattern 3: Grouping¶
customers = getCustomers();
// Separate by age
minors = customers.Filter((c) => c.age < 18);
adults = customers.Filter((c) => c.age >= 18 && c.age < 65);
seniors = customers.Filter((c) => c.age >= 65);
// Get active vs inactive
active = customers.Filter((c) => c.isActive);
inactive = customers.Filter((c) => !c.isActive);
Pattern 4: Property Extraction¶
policies = getPolicies();
// Extract all policy numbers
numbers = policies.Map((p) => p.number);
// Get unique customer IDs
customerIds = policies.Map((p) => p.customerId).Distinct();
// Extract nested property
emails = policies.Map((p) => p.customer.email);
Pattern 5: Validation¶
orders = getOrders();
// Check if all valid
allValid = orders.All((o) => o.total > 0 && o.customerId != null);
// Check if any invalid
hasInvalid = orders.Any((o) => o.total <= 0);
// Find first invalid
firstInvalid = orders.Find((o) => o.total <= 0);
Pattern 6: Aggregation¶
sales = getSales();
// Total revenue
totalRevenue = sales.ReduceToNum((acc, s) => acc + s.amount, 0);
// Average order value
averageValue = totalRevenue / sales.Length;
// Max sale
maxSale = sales.ReduceToNum((acc, s) => (s.amount > acc ? s.amount : acc), 0);
// Concatenate names
allNames = customers.Reduce((acc, c) => acc + c.name + ", ", "");
Best Practices¶
1. Keep Lambdas Simple¶
// Less ideal: Complex lambda
result = items.Map((x) => {
value = calculateComplexValue(x);
adjusted = applyMultipleAdjustments(value);
formatted = formatOutput(adjusted);
return formatted;
});
// Better: Extract to function
function processItem(item) {
value = calculateComplexValue(item);
adjusted = applyMultipleAdjustments(value);
formatted = formatOutput(adjusted);
return formatted;
}
result = items.Map((item) => processItem(item));
// Or even simpler:
result = items.Map(processItem);
2. Use Expression Body When Possible¶
// Verbose
doubled = numbers.Map((x) => {
return x * 2;
});
// Concise
doubled = numbers.Map((x) => x * 2);
3. Name Lambda Parameters Meaningfully¶
// Less clear
customers.Filter((x) => x.a > 18);
// Clear
customers.Filter((customer) => customer.age > 18);
// For simple operations, short names are fine
numbers.Map((n) => n * 2);
items.Filter((x) => x > 0);
4. Chain Operations Clearly¶
// Hard to read (all on one line)
result = items
.Filter((x) => x.isActive)
.Map((x) => x.value)
.ReduceToNum((a, x) => a + x, 0);
// Easier to read
result = items
.Filter((x) => x.isActive)
.Map((x) => x.value)
.ReduceToNum((a, x) => a + x, 0);
5. Avoid Side Effects in Lambdas¶
// Problematic: Side effect
total = 0;
numbers.ForEach((x) => {
total += x; // Modifies external variable
});
// Better: Use reduce
total = numbers.ReduceToNum((acc, x) => acc + x, 0);
6. Use Appropriate Array Method¶
// Less ideal: Map when you need filter
positives = numbers.Map((x) => (x > 0 ? x : null)).Filter((x) => x != null);
// Better: Just filter
positives = numbers.Filter((x) => x > 0);
Common Mistakes¶
Mistake 1: Forgetting Return in Block Body¶
// ERROR: No return value
result = numbers.Map((x) => {
doubled = x * 2;
// Missing: return doubled;
});
// result is array of nulls
// CORRECT
result = numbers.Map((x) => {
doubled = x * 2;
return doubled;
});
Mistake 2: Using Braces for Expression Body¶
// Unnecessary braces
doubled = numbers.Map((x) => {
x * 2;
}); // Returns null!
// Correct: Expression body
doubled = numbers.Map((x) => x * 2);
// Or with explicit return
doubled = numbers.Map((x) => {
return x * 2;
});
Mistake 3: Wrong Parameter Count¶
numbers = [1, 2, 3, 4, 5];
// Filter takes 1 parameter
evens = numbers.Filter((x) => x % 2 == 0); // OK
// Map can take 2 parameters (item, index)
withIndex = numbers.Map((x, i) => x + i); // OK
// ERROR: Filter doesn't provide index
// numbers.Filter((x, i) => x > i); // ERROR
Mistake 4: Modifying Array During Iteration¶
// Problematic
items.ForEach((item) => {
items.Push(item * 2); // Modifying while iterating!
});
// Better: Create new array
doubled = items.Map((item) => item * 2);
items = items.AddRange(doubled);
Mistake 5: Expecting this Binding¶
// EverSharp lambdas don't have 'this'
// They only capture variables from outer scope
object = {
value: 10,
process: () => value * 2, // 'value' from outer scope
};
Advanced Examples¶
Example 1: Data Processing Pipeline¶
orders = getOrders();
// Calculate total revenue from premium orders in last 30 days
cutoffDate = $AddDays($Today(), -30);
revenue = orders
.Filter((o) => o.date >= cutoffDate)
.Filter((o) => o.isPremium)
.Map((o) => o.total)
.ReduceToNum((acc, total) => acc + total, 0);
$Log("Premium revenue (30 days): $" + revenue);
Example 2: Complex Transformation¶
policies = getPolicies();
// Create summary report
report = policies
.Filter((p) => p.isActive)
.Map((p) => {
monthlyPremium = $Round(p.premium / 12, 2);
customerAge = $DifferenceInYears($Today(), p.customer.birthDate);
ageCategory =
customerAge < 25 ? "young" : customerAge < 65 ? "standard" : "senior";
return {
policyNumber: p.number,
customerName: p.customer.firstName + " " + p.customer.lastName,
ageCategory: ageCategory,
monthlyPremium: monthlyPremium,
yearlyPremium: p.premium,
coverage: p.coverage,
};
})
.OrderByDescending((p) => p.yearlyPremium);
Example 3: Validation and Error Collection¶
function validatePolicies(policies) {
errors = [];
policies.ForEach((policy, index) => {
if (policy.premium <= 0) {
errors.Push("Policy " + index + ": Invalid premium");
}
if (policy.customer == null) {
errors.Push("Policy " + index + ": Missing customer");
} else {
if (policy.customer.age < 18) {
errors.Push("Policy " + index + ": Customer under 18");
}
}
if (policy.coverage < 50000) {
errors.Push("Policy " + index + ": Coverage below minimum");
}
});
return errors;
}
Example 4: Statistical Calculations¶
function calculateStats(numbers) {
count = numbers.Length;
sum = numbers.ReduceToNum((acc, x) => acc + x, 0);
average = sum / count;
min = numbers.ReduceToNum((acc, x) => (x < acc ? x : acc), numbers[0]);
max = numbers.ReduceToNum((acc, x) => (x > acc ? x : acc), numbers[0]);
// Count above average
aboveAvg = numbers.Filter((x) => x > average).Length;
return {
count: count,
sum: sum,
average: $Round(average, 2),
min: min,
max: max,
aboveAverage: aboveAvg,
};
}
numbers = [10, 20, 30, 40, 50];
stats = calculateStats(numbers);
Example 5: Nested Object Processing¶
departments = getDepartments();
// Extract all employees from all departments
allEmployees = departments
.Map((dept) => dept.employees)
.ReduceToArr((acc, emps) => acc.AddRange(emps), []);
// Get average salary across all departments
totalSalary = allEmployees
.Map((emp) => emp.salary)
.ReduceToNum((acc, sal) => acc + sal, 0);
avgSalary = $Round(totalSalary / allEmployees.Length, 2);
// Find high earners
highEarners = allEmployees
.Filter((emp) => emp.salary > avgSalary)
.OrderByDescending((emp) => emp.salary);
Next Steps¶
- Learn about objects: Objects
- Explore array methods: Arrays and Native Functions - Arrays
- See practical examples: Array Operations
- Understand closures: Functions