Functions
One way to organize code and to make it more readable and reusable is to factor-out useful pieces into reusable functions. In mathematics, function is a mapping from defined source set to a corresponding defined destination set. The implementation remains an abstraction to keep the code clean. Here we'll cover ways of creating functions.
In languages such as Julia, a function is an object that maps a tuple of argument values to a return value. Julia functions are not pure mathematical functions, because they can alter and be affected by the global state of the program.
Let's look into Fibonacci program implemented as a function:
go
js
dart
python
julia
package main

import "fmt"

func fibonacci(n int) []int {
    var a, b = 0, 1
    var fibonacci_numbers = []int{}

    for len(fibonacci_numbers) < n {
        a, b = b, a+b
        fibonacci_numbers = append(fibonacci_numbers, a)
    }

    return fibonacci_numbers
}

func main() {
    fib_numbers := fibonacci(8)
    fmt.Println(fib_numbers)
}
function fibonacci(n: number) :number[] {
    let a = 0, b = 1
    let fibonacci_numbers: number[] = []
    let temp: number
    while (fibonacci_numbers.length < n) {
        temp = a 
        a = b
        b = temp + b
        fibonacci_numbers.push(a)
    }
    return fibonacci_numbers
}

let fib_numbers = fibonacci(8)
console.log(fib_numbers)
void main () {
var fibNumbers = fibonacci(8);
print(fibNumbers);
}

List<int> fibonacci(int n) {
var a = 0, b = 1;
var fibonacciNumbers = <int>[];
late int temp;
while (fibonacciNumbers.length < n){
    temp = a;
    a = b;
    b = temp + b;
    fibonacciNumbers.add(a);
}
return fibonacciNumbers;
}
# typing for python 3.9+
# for earlier versions use
# from typing import List
def fibonacci(n: int) -> list[int]:
    a, b = 0, 1
    fibonacci_numbers: list[int] = []

    while len(fibonacci_numbers) < n:
        a, b = b, a + b
        fibonacci_numbers.append(a)

    return fibonacci_numbers

fib_numbers: list[int] = fibonacci(8)
print(fib_numbers)
function fibonacci(n::Int)::Vector{Int}
    a, b = 0, 1
    fibonacci_numbers = Vector{Int}([])
    
    while length(fibonacci_numbers) < n
        a, b = b, a + b
        push!(fibonacci_numbers, a)
    end
    return fibonacci_numbers
end

fib_numbers = Vector{Int}(fibonacci(8))

# append!(fib_numbers, fibonacci(8))
println(fib_numbers)
Note: In the above code, iterative approach has been used to write the Fibonacci program. Try to use the recursive approach in your stack of ML-in-production languages
If you're familiar with strongly-typed languages (Dart, Go, Julia), you'll immediately notice that there is no type information associated with the function inputs or outputs for languages like JS and Python. Python functions can return any Python object, simple or compound, which means constructs that may be difficult in other languages are straightforward in Python, (and JS). We therefore use TypeScript, (for JS) and MyPy (for Python) to enforce type hinting.
Default Argument Values
Often when defining a function, there are certain values that we want the function to use most of the time, but we'd also like to give the user some flexibility. In this case, we can use default values for arguments.
Note: Go does not support default arguments
js
dart
python
julia
function printValues(x = 1, y = 2) {
    console.log(x, y)
}

// This kind of default parameter overidding
// only overides from the first
printValues(4, 9)

// This is more flexible
function printValuesToo({x = 1, y = 2} = {}) {
    console.log(x, y)
}

// You can specify a particular value to overide
printValuesToo({ y: 6 })
// In Dart, named parameters are optional (null) unless they're explicitly marked as required
// They are enclosed in {...}

void main() {
    printValues(-3, c: 9, e: 10);  
}

// a is positional and required
// b, c and d, are optional 
// the named parameter e is required
void printValues(int a, {int b = 1, c = 2, int? d, required int e}) {
    print("$a, $b, $c, $d, $e");
}
def printValues(a, b = 4, c = 5):
    print(f"{a}, {b}, {c}")
    
printValues(12, c = 8)
function printValues(a::Int64, b = 2a, c = 6, z = nothing)
    println("$a, $b, $c, $z")
end

printValues(24, 1, 7)   

# using keyword arguments, note the semicolon            
function printValues(a::Int64; b = 2a, c = 6, z = nothing)
    println("$a, $b, $c, $z")
end

printValues(24, c =7, z = "Hello world")     
# varargs can be used in positional or keyword arguments
Flexible Arguments
Sometimes you might wish to write a function in which you don't initially know how many arguments the user will pass. In this case, you can use the special form to catch all arguments that are passed.
Dart has no real support for this. Generally, a function accepting more arguments than they are formally declared to accept, are called variadic functions
Here is an example:
go
js
python
julia
package main
import "fmt"

func sum(nums ...int) {
    fmt.Print(nums, " ")
    total := 0
    for _, num := range nums {
        total += num
    }
    fmt.Println(total)
}
func main() {

    sum(1, 2)
    sum(1, 2, 3)

    nums := []int{1, 2, 3, 4}
    sum(nums...)
}
function argFun(a = 3) {
    console.log(a);
    // since arguments has all args, explicitly remove those defined in function input
    const args = Array.prototype.slice.call(arguments, 1)
    console.log(args);
}

// @ts-ignore
argFun(2, [1, 3, 4, 6], 6, 9)
# In Python, this gets interesting
# as there's *args and **kwargs

def catch_all(*args, **kwargs):
    # args are the 'positional arguments'
    print("args =", args)
    a = args[0]
    print(f"The first arg: {a}")
    # kwargs are the 'keyword arguments'
    b = kwargs.get('b')
    # while trying to get c, if it's not found, set default to 58
    c = kwargs.get('c', 58)
    print(f"kwargs = {kwargs}")
    print(f'b:{b} c: {c}')
    print(*args, sep="; ")
    
catch_all(1, 2, 3, a=4, b=5)
bar(a,b,x...) = (a,b,x)

println(bar(1, 2, 3))
# (1, 2, (3,))

println(bar(1, 2, 3, 4))
# (1, 2, (3, 4))

x = (3, 4)

# ...  "splat" the values contained in an iterable collection
# into a function call as individual arguments
println(bar(1,2,x...))
# (1, 2, (3, 4))

x = [1,2,3,4]

println(bar(x...))
# (1, 2, (3, 4))
Note: Print variations
go
python
package main

import "fmt"

func main() {
    var a, b = 10, 4.5
    // fmt.Print and fmt.Println print the raw string
    // Println inserts spaces between arguments
    // Print only inserts spaces between arguments
    // when either argument is not a string.
    fmt.Print("The numbers are", a, "and", b, "\n") // Does not have new line by default
    // Res ==> The numbers are10and4.5
    fmt.Println("The numbers are", a, "and", b) // Has new line by default
    // Res ==> The numbers are 10 and 4.5
    var res string = fmt.Sprintf("The numbers are %d and %.3f\n", a, b)
    fmt.Print(res)
    // Res ==> The numbers are 10 and 4.500
    fmt.Printf("The numbers are %d and %f", a, b)
    // Res ==> The numbers are 10 and 4.500000
    // Note: using %v prints 4.5 instead of 6-floating precision 
    // %v is generic for all data types during printing
    // %T prints data type instead of the data itself
}
# In Python, there's 4 print variants
a, b = 5, 4.5 
# C-style formatting
# ... ==> https://stackabuse.com/python-string-interpolation-with-the-percent-operator/
print("The numbers are %d and %d" %(a, b))

# f-strings formatting
print(f"The numbers are {a} and {b:.2f}")

# raw interpolation
print("The numbers are", a, "and", b)

# using .format
print("The numbers are {} and {:.2f}".format(a, b))
Anonymous Functions
In computer programming, an anonymous function (function literal, lambda abstraction, lambda function, lambda expression or block) is a function definition that is not bound to an identifier. Anonymous functions are often arguments being passed to higher-order functions or used for constructing the result of a higher-order function that needs to return a function.1 If the function is only used once, or a limited number of times, an anonymous function may be syntactically lighter than using a named function
An anonymous function defined into another function F can use elements that are not defined in its scope but in the scope of F. Anonymous functions can keep references to its surrounding state. They are known as function closures.
go
js
dart
python
julia
package main

import "fmt"

func main() {
    func() {
        fmt.Println("Anonymous function called")
    }()

    // closure call
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

func adder() func(int) int {
    sum := 0
    // Function closure
    return func(x int) int {
        sum += x
        return sum
    }
}
// Simple IIFE
(function() {
    console.log('IIFE');
})()

let person = {
    firstName: 'John',
    lastName: 'Doe'
};

(function () {
    console.log(person.firstName} + ' ' + person.lastName);
})(person)

// ES6 introduced arrow function expressions that 
// provide a shorthand for declaring anonymous functions
let add = (a: number, b: number) => a + b

function makeAdder(x) {
    //Function closure
    return function (y) {
        return x + y;
    };
}

const add5 = makeAdder(5);
console.log(add5(2)); // 7
void main () {
    /*
    The following example defines an anonymous function with an untyped parameter, 
    item, and passes it to the map function. 
    The function, invoked for each item in the list, converts each string to uppercase. 
    Then in the anonymous function passed to forEach, 
    each converted string is printed out alongside its length
    */
    const list = ['apples', 'bananas', 'oranges'];
    list.map((item) {
        return item.toUpperCase();
    }).forEach((item) {
        print('$item: ${item.length}');
    });

    /* If the function contains only a single 
    expression or return statement, 
    you can shorten it using arrow notation. */
    list
    .map((item) => item.toUpperCase())
    .forEach((item) => print('$item: ${item.length}'));

    // Create a function that adds 2.
    var add2 = makeAdder(2);

    assert(add2(3) == 5);
}

//function closure
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
# the code below is an anonymous function bound to a variable
add = lambda x, y: x + y

# Let us look at a more practical example
data: list[dict[str, str]] = [{'first':'Guido', 'last':'Van Rossum', 'YOB':1956},
{'first':'Grace', 'last':'Hopper',     'YOB':1906},
{'first':'Alan',  'last':'Turing',     'YOB':1912}]

# Now suppose we want to sort this data. 
# Python has a sorted function that does this

sorted([2, 4, 3, 5, 1, 6])
# [1, 2, 3, 4, 5, 6]

# But dictionaries are not orderable: we need a way
# to tell the function how to sort our data. We can 
# do this by specifying the key function, a function 
# which given an item returns the sorting key for that item

# sort alphabetically by first name
sorted(data, key=lambda item: item['first'])

# sort by year of birth
sorted(data, key=lambda item: item['YOB'])
map(round, [1.2, 3.5, 1.7])
#= 
3-element Vector{Float64}:
1.0
4.0
2.0
=#

map(x -> x^2 + 2x - 1, [1, 3])
#= 
2-element Vector{Int64}:
2
14
=#

# An anonymous function accepting multiple arguments can be written using the syntax 
# (x,y,z) -> 2x + y - z
# ==> https://m3g.github.io/JuliaNotes.jl/stable/anonymous/
Defer Functions in Go
A defer statement defers the execution of a function until the surrounding function returns. The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.
package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}
            [Golang]$ go run test.go
hello
world
[Golang]$
          
Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.
package main

import "fmt"

func another() {
    for i := 0; i < 10; i++ {
        defer fmt.Println("Defer:", i)
    }
}
func main() {
    defer another()
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}
            [Golang]$ go run test.go
counting
done
9
8
7
6
5
4
3
2
1
0
Defer: 9
Defer: 8
Defer: 7
Defer: 6
Defer: 5
Defer: 4
Defer: 3
Defer: 2
Defer: 1
Defer: 0
[Golang]$
          
Defer only works within a certain thread as they are bound within a goroutine. Goroutines are defined here
Read more onDefer, Panic and Recover
Weird returns
Let's see how the two languages, Julia and Go have weird returns, if you may call it that way. In Go, you can initialize the variable name in the return part of a function and it will be returned. In Julia, the last executed operation/variable is the one returned to the caller, ensuring the type matches.
go
julia
package main

import "fmt"

func add(x, y int) int {
    z := x + y
    return z
}

func weirdAdd(x, y int) (z int) {
    z = x + y
    return
}

func main() {
    var a, b int = 5, 8
    fmt.Println(add(a, b))
    fmt.Println(weirdAdd(a, b))
}
function add(x::Int64, y::Int64)::Int64
    return x + y
end

function weirdAdd(x::Int64, y::Int64)::Int64
    x + y
    # uncommenting the code below returns it instead
    # s = 98
end

x, y = 5, 8
println(add(x, y))
println(weirdAdd(x, y))
Of course, these ways of returns are not recommended.