DolphinDB Custom Functions in Practice: Syntax, Lambda, and Modular Tool Library Design

[AI Readability Summary] DolphinDB custom functions help you consolidate reusable business logic, reduce script duplication, simplify maintenance, and improve performance stability. This article focuses on three core themes: function definitions, Lambda, and modular tool libraries, with a practical Industrial IoT example.

Technical Specification Snapshot

Parameter Description
Language DolphinDB Script
License CC 4.0 BY-SA
Stars Not provided in the original article
Core Dependencies DolphinDB runtime environment, Modules mechanism, built-in vectorized functions

DolphinDB custom functions are the foundation of a dedicated tool library

In DolphinDB, custom functions are more than a language feature. They are the infrastructure for codifying business rules. They work well for encapsulating repetitive logic such as metric calculation, state evaluation, data validation, and anomaly detection.

Compared with scattering scripts across query statements, a functional approach delivers immediate benefits: stronger reuse, easier testing, clearer module boundaries, and a more practical path toward a team-shared tool library.

Basic definitions determine the maintainability of a function library

The most common way to define a function is def. Once the function name, parameter list, and return-value contract are clearly defined, the cost of future extension and troubleshooting drops significantly.

// Basic function: define addition for two numbers
def add(a, b) {
    return a + b  // Return the computed result
}

// Call the function
add(1, 2)

This example shows the smallest complete lifecycle of a DolphinDB custom function: definition, execution, and result return.

Return value design should support the analysis pipeline

DolphinDB supports single-return, multi-return, and no-return functions. In analytical workloads, multi-return functions are especially useful because they can reduce repeated data scans.

// Compute multiple metrics for a vector
def stats(v) {
    return avg(v), max(v), min(v), std(v)  // Return multiple statistics at once
}

a, m, n, s = stats(1..100)

This example produces the mean, maximum, minimum, and standard deviation in a single call, which makes it well suited for batch analytics.

Parameter passing directly affects function usability

Positional parameters work well for simple functions, default parameters fit common configurations, and variable arguments are useful for aggregation scenarios. The more thoughtfully you design parameters, the more likely the function will be reused across a team.

Type checking marks the transition from ad hoc scripting to engineering discipline. Constraining inputs prevents runtime issues from propagating further down the pipeline.

// Safe division with validation
def safeDivide(a, b) {
    if (typestr(a) != "DOUBLE" and typestr(a) != "INT") {
        throw "Parameter a must be numeric"  // Validate input type
    }
    if (typestr(b) != "DOUBLE" and typestr(b) != "INT") {
        throw "Parameter b must be numeric"  // Validate input type
    }
    if (b == 0) {
        throw "Divisor cannot be 0"  // Prevent invalid calculation
    }
    return a / b
}

This function shifts common errors earlier through input validation, which makes it a strong candidate for a foundational utility function.

Advanced function features make business logic composable

Closures, higher-order functions, and recursion allow DolphinDB to do more than simple scripting. They let you build composable computational units. These capabilities are especially important in strategy engines and rule-based systems.

The key value of a closure is that it remembers context. For example, when you create multipliers, threshold evaluators, or scoring functions with preset weights, closures can simplify invocation.

// Generate a multiplier through a closure
def makeMultiplier(factor) {
    return def (x) {
        return x * factor  // Capture the outer factor
    }
}

double = makeMultiplier(2)
triple = makeMultiplier(3)

This example packages fixed rules into repeatedly generated function instances, which is useful for reusing parameterized logic.

Lambda and partial application are ideal for assembling lightweight analysis flows

Anonymous functions are ideal for short logic blocks, partial application is useful for pre-filling parameters, and function composition works well for building chained calculations. Combined together, they make it easy to assemble lightweight processing flows.

// Function composition: double first, then add one
def compose(f, g) {
    return def (x) {
        return f(g(x))  // Execute g first, then f
    }
}

double = def (x) { x * 2 }
increment = def (x) { x + 1 }
process = compose(increment, double)

This example shows how to combine several small functions into a single computation pipeline, improving code expressiveness.

Industrial IoT scenarios are ideal candidates for function libraries

One of the most valuable ideas in the original content is to encapsulate business metrics such as temperature, humidity, pressure, vibration, and power into dedicated functions. This approach decouples rules from data processing.

Typical scenarios include device state evaluation, health scoring, anomaly detection, and data quality checks. These patterns are highly reusable, highly consistent, and highly beneficial from a maintenance perspective.

// Comprehensive device status evaluation
def getDeviceStatus(temperature, humidity, pressure, vibration) {
    tempStatus = iif(temperature > 35, "High temperature warning", "Normal")  // Temperature rule
    humidStatus = iif(humidity > 80, "High humidity warning", "Normal")   // Humidity rule
    pressStatus = iif(pressure > 1020, "High pressure warning", "Normal") // Pressure rule
    vibStatus = iif(vibration > 4.0, "Abnormal vibration", "Normal")   // Vibration rule
    warnings = [tempStatus, humidStatus, pressStatus, vibStatus]
    warningCount = sum(iif(warnings != "Normal", 1, 0))       // Count alerts
    return iif(warningCount == 0, "Normal", "Abnormal")
}

This function normalizes multiple metric inputs into a single state output, which makes it suitable for real-time monitoring and alerting systems.

Modular packaging determines whether a function library can evolve over time

As the number of functions grows, you need module-based management. By placing common capabilities under the modules/ directory, you can maintain state evaluation, statistical processing, and utility functions in separate layers.

module declares a namespace, and use imports it for use. This structure prevents scripts from becoming scattered and reduces the risk of naming conflicts.

// File: modules/iot_functions.dos
module iot_functions

def celsiusToFahrenheit(celsius) {
    return celsius * 9 / 5 + 32  // Convert Celsius to Fahrenheit
}

// Use the module
use iot_functions
celsiusToFahrenheit(25.0)

This example demonstrates the shortest path from writing a function to reusing it through a module, which is the foundational pattern for building a tool library.

Performance optimization should prioritize vectorization and built-in functions

Much of DolphinDB’s performance advantage comes from vectorization. If a problem can be expressed with vector operations, avoid falling back to row-by-row loops. If a built-in function can solve the problem, avoid writing a slower custom implementation.

You should also avoid repeated computation. For example, if avg(data) will be used multiple times, cache it first. Small optimizations become significantly amplified when processing large-scale time-series data.

// Recommended: vectorized processing instead of row-by-row loops
def processVector(data) {
    return data * 2 + 1  // Compute directly on the full column
}

This example reflects a DolphinDB best practice: use vectorization to gain throughput and reduce script complexity.

The image mainly shows platform branding and decorative site elements

DolphinDB column cover

AI Visual Insight: This image is a column-cover style graphic used primarily to indicate content ownership and topical entry points. It does not show system architecture, code execution results, or data flow details, so it does not add technical mechanism explanations.

Best practices can be summarized into four actionable principles

First, follow single responsibility: one function should do one thing. Second, use explicit naming: function names should directly express intent. Third, keep parameters manageable and within a comprehensible range. Fourth, always include comments and exception handling.

If your goal is to build a team-level tool library, advance simultaneously across four dimensions: general-purpose functions, business-specific functions, module directory structure, and performance baselines. Do not stop at syntax examples alone.

FAQ

FAQ 1: What logic is best suited for DolphinDB custom functions?

Custom functions are best for logic that appears repeatedly, follows stable rules, and needs reuse across multiple locations, such as metric calculations, data cleansing, device state evaluation, anomaly detection, and unit conversion.

FAQ 2: When should a function be promoted into a module?

You should move a function into the modules/ directory and manage it with module and use when it is reused across scripts, grows in number, requires namespace isolation, or needs to be shared by a team.

FAQ 3: How can I balance flexibility and performance in functions?

Prioritize vectorization and built-in functions, and avoid loops in hot paths. Apply parameter constraints and result caching for high-frequency calculations. Break complex rules into small functions and reuse them through composition.

Core Summary

This article systematically reconstructs the DolphinDB custom function development path, covering function definitions, parameter passing, closures, higher-order functions, Lambda, modular packaging, and performance optimization. It also uses an Industrial IoT example to show how to build a highly reusable, high-performance dedicated function library.