Functions in Soplang

Functions are reusable blocks of code that perform specific tasks. In Soplang, functions are defined using the howl keyword and can take parameters, return values, and be composed in various ways.

basic_function.sop
// Basic function definition
howl greet() {
    qor("Hello, World!")
}

// Calling the function
greet()  // Outputs: "Hello, World!"

Function Basics

In Soplang, functions are defined using the howl keyword, followed by the function name, parentheses for parameters, and a block of code enclosed in curly braces:

basic_function.sop
// Basic function definition
howl greet() {
    qor("Hello, World!")
}

// Calling the function
greet()  // Outputs: "Hello, World!"

Functions should be named according to what they do, typically using verbs or verb phrases. In Soplang, function names conventionally use camelCase:

function_naming.sop
// Good function names
howl calculateSum(a, b) {
    soo_celi a + b
}

howl getFirstName(names) {
    soo_celi names[0]
}

howl addData(data, section) {
    section.kudar(data)
}

Parameters and Arguments

Functions can accept parameters, which are variables listed in the function definition. When you call a function with specific values, those values are called arguments.

Basic Parameters

basic_parameters.sop
// Function with parameters
howl greet(name) {
    qor("Hello, " + name + "!")
}

// Calling with arguments
greet("Farah")  // Outputs: "Hello, Farah!"
greet("Halima")  // Outputs: "Hello, Halima!"

Multiple Parameters

multiple_parameters.sop
// Function with multiple parameters
howl add(num1, num2, num3) {
    soo_celi num1 + num2 + num3
}

door result = add(10, 20, 30)
qor(result)  // Outputs: 60

Default Parameter Values

You can provide default values for parameters, which are used if no argument is provided:

default_parameters.sop
// Function with default parameter values
howl calculateTax(amount, taxPercent = 15) {
    door tax = (taxPercent / 100) * amount
    soo_celi tax
}

qor(calculateTax(1000))  // Uses default 15%, Outputs: 150
qor(calculateTax(1000, 10))  // Uses provided 10%, Outputs: 100

Named Arguments

For better readability, especially with multiple parameters, you can use named arguments:

named_arguments.sop
// Function definition
howl createArticle(title, author, date, description = "") {
    // Function body
    soo_celi {
        "title": title,
        "author": author,
        "date": date,
        "description": description
    }
}

// Using named arguments
door article = createArticle(
    title = "Learning Soplang",
    author = "Farah",
    date = "2023-10-24",
    description = "A great new language"
)

// You can change the order with named arguments
door anotherArticle = createArticle(
    author = "Halima",
    description = "Making programming easier to learn", 
    date = "2023-10-20",
    title = "Soplang and Its Benefits"
)

Variable Number of Arguments

You can create functions that accept a variable number of arguments using the rest parameter syntax (denoted by ...):

rest_parameters.sop
// Function with variable number of arguments
howl addAll(...numbers) {
    door total = 0
    ku_celi num ku dhex jira numbers {
        total += num
    }
    soo_celi total
}

qor(addAll(1, 2, 3))  // Outputs: 6
qor(addAll(10, 20, 30, 40, 50))  // Outputs: 150
qor(addAll())  // Outputs: 0

Return Values

Functions can return values using the soo_celi keyword. If no return statement is specified, the function returns waxba (null) by default.

Basic Return Values

return_values.sop
// Function returning a value
howl square(num) {
    soo_celi num * num
}

door result = square(5)
qor(result)  // Outputs: 25

// Function with no explicit return
howl printMessage(message) {
    qor("LOG: " + message)
    // No return statement, implicitly returns waxba (null)
}

door output = printMessage("Hello")
qor(output)  // Outputs: null

Early Returns

early_returns.sop
// Early return example
howl getDivision(a, b) {
    haddii (b == 0) {
        qor("Error: Cannot divide by zero")
        soo_celi waxba
    }
    
    soo_celi a / b
}

qor(getDivision(10, 2))  // Outputs: 5
qor(getDivision(10, 0))  // Outputs: "Error: Cannot divide by zero" and then null

Returning Multiple Values

You can return multiple values using tuples or objects:

multiple_returns.sop
// Returning multiple values using a tuple
howl getNameParts(fullName) {
    door parts = fullName.split(" ")
    soo_celi [parts[0], parts.length > 1 ? parts[1] : ""]
}

door [firstName, lastName] = getNameParts("John Smith")
qor(firstName)  // Outputs: "John"
qor(lastName)   // Outputs: "Smith"

// Returning multiple values using an object
howl getPersonStats(person) {
    soo_celi {
        "age": person.age,
        "height": person.height,
        "weight": person.weight
    }
}

door person = { "name": "Sarah", "age": 28, "height": 165, "weight": 60 }
door stats = getPersonStats(person)
qor(stats.age)     // Outputs: 28
qor(stats.height)  // Outputs: 165

Function Scope

Functions create their own scope, which means variables defined inside a function are not accessible outside of it:

function_scope.sop
// Function scope example
howl calculateArea(width, height) {
    door area = width * height  // Local variable
    soo_celi area
}

door rectArea = calculateArea(5, 10)
qor(rectArea)  // Outputs: 50

// This would cause an error:
// qor(area)  // Error: 'area' is not defined

// Variables with the same name don't conflict
door area = "This is a global area variable"
door functionArea = calculateArea(3, 4)  // This uses its own local 'area'
qor(area)  // Outputs: "This is a global area variable"
qor(functionArea)  // Outputs: 12

Closures

Functions in Soplang can form closures, which means they can remember the environment in which they were created:

closures.sop
// Closure example
howl createCounter(startValue = 0) {
    door count = startValue
    
    // Return a function that has access to the 'count' variable
    howl increment() {
        count += 1
        soo_celi count
    }
    
    soo_celi increment
}

door counter1 = createCounter(10)
door counter2 = createCounter(5)

qor(counter1())  // Outputs: 11
qor(counter1())  // Outputs: 12
qor(counter2())  // Outputs: 6
qor(counter1())  // Outputs: 13 - counter1 maintained its own state
qor(counter2())  // Outputs: 7 - counter2 maintained its own state

Anonymous Functions

Anonymous functions are functions without a name. They can be assigned to variables or passed as arguments to other functions:

anonymous_functions.sop
// Anonymous function assigned to a variable
door greet = howl(name) {
    soo_celi "Hello, " + name + "!"
}

qor(greet("John"))  // Outputs: "Hello, John!"

// Anonymous function as an argument
door numbers = [1, 2, 3, 4, 5]
door squaredNumbers = numbers.map(howl(num) {
    soo_celi num * num
})

qor(squaredNumbers)  // Outputs: [1, 4, 9, 16, 25]

// Shorthand syntax for simple functions (arrow functions)
door cubes = numbers.map(num => num * num * num)
qor(cubes)  // Outputs: [1, 8, 27, 64, 125]

Higher-Order Functions

Higher-order functions are functions that take other functions as arguments or return functions. They are powerful for functional programming patterns:

higher_order_functions.sop
// Higher-order function that takes a function as an argument
howl applyToEach(array, func) {
    door results = []
    ku_celi item ku dhex jira array {
        results.push(func(item))
    }
    soo_celi results
}

door numbers = [1, 2, 3, 4, 5]
door doubled = applyToEach(numbers, num => num * 2)
qor(doubled)  // Outputs: [2, 4, 6, 8, 10]

// Higher-order function that returns a function
howl multiplier(factor) {
    soo_celi howl(number) {
        soo_celi number * factor
    }
}

door double = multiplier(2)
door triple = multiplier(3)

qor(double(5))  // Outputs: 10
qor(triple(5))  // Outputs: 15

// Chaining higher-order functions
howl filterMap(array, predicate, transform) {
    door results = []
    ku_celi item ku dhex jira array {
        haddii (predicate(item)) {
            results.push(transform(item))
        }
    }
    soo_celi results
}

// Get squares of even numbers
door numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
door evenSquares = filterMap(
    numbers,
    num => num % 2 == 0,  // Filter for even numbers
    num => num * num      // Transform by squaring
)
qor(evenSquares)  // Outputs: [4, 16, 36, 64, 100]

Recursion

Recursion is a technique where a function calls itself. This is useful for solving problems that can be broken down into smaller, similar subproblems:

recursion.sop
// Factorial function using recursion
howl factorial(n) {
    haddii (n <= 1) {
        soo_celi 1
    }
    soo_celi n * factorial(n - 1)
}

qor(factorial(5))  // Outputs: 120 (5 * 4 * 3 * 2 * 1)

// Fibonacci sequence using recursion
howl fibonacci(n) {
    haddii (n <= 0) {
        soo_celi 0
    } laq_heli (n == 1) {
        soo_celi 1
    }
    soo_celi fibonacci(n - 1) + fibonacci(n - 2)
}

qor(fibonacci(7))  // Outputs: 13

// Tree traversal using recursion
howl createNode(value, left = null, right = null) {
    soo_celi { value, left, right }
}

// Creating a simple binary tree
door root = createNode(1,
    createNode(2, 
        createNode(4), 
        createNode(5)
    ),
    createNode(3, 
        createNode(6), 
        createNode(7)
    )
)

// In-order traversal (Left -> Root -> Right)
howl inorder_traverse(node) {
    haddii (node == null) {
        soo_celi []
    }
    
    door results = []
    results = results.concat(inorder_traverse(node.left))
    results.push(node.value)
    results = results.concat(inorder_traverse(node.right))
    
    soo_celi results
}

qor(inorder_traverse(root))  // Outputs: [4, 2, 5, 1, 6, 3, 7]

Note: Be cautious with recursion, as deep recursion can lead to stack overflow errors. For deep recursion, consider using tail-call optimization or iterative approaches.

Best Practices for Functions

  • Single Responsibility: Each function should do one thing and do it well.

  • Descriptive Names: Name functions with verbs that describe what they do.

  • Limit Parameters: Functions with many parameters are harder to use. Consider using objects for multiple parameters.

  • Consistent Return Values: Functions should return consistent types to avoid confusion.

  • Pure Functions: When possible, use pure functions (no side effects, same output for same input).

  • Comments: Add comments to explain complex logic, but aim for self-documenting code.