Skip to content

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

(parameter) => expression;

Example:

numbers = [1, 2, 3, 4, 5];
doubled = numbers.Map((x) => x * 2); // [2, 4, 6, 8, 10]

Multiple Parameters

(param1, param2) => expression;

Example:

numbers = [1, 2, 3, 4, 5];
withIndex = numbers.Map((x, i) => x + i); // [1, 3, 5, 7, 9]

No Parameters

() => expression;

Example:

getTimestamp = () => $Now();
timestamp = getTimestamp();

Block Body

(parameter) => {
  statement1;
  statement2;
  return value;
};

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