# Functions

A function packages up a piece of logic so you can name it, reuse it, and reason about it in isolation. That's true in every language. In Julia, functions carry more weight than usual for two reasons: they're the unit of **compilation** (as I hinted at the end of the last chapter), and they're the unit of **dispatch**, which is the organizing principle of the entire language.

So this chapter matters more than the title suggests. Let's take it slowly.

## Defining a function

The long form:

```{code-block} julia
function net_pay(gross, tax_rate)
    deduction = gross * tax_rate
    return gross - deduction
end
```

And the short form, for one-liners, which is used constantly in Julia:

```{code-block} julia
net_pay(gross, tax_rate) = gross - gross * tax_rate
```

Both define exactly the same thing. The short form is not a lesser "lambda" — it's a full function definition in fewer characters.

### `return` is optional

A Julia function returns the value of its last expression automatically:

```{code-block} julia
function net_pay(gross, tax_rate)
    gross - gross * tax_rate        # this value is returned
end
```

Both styles are common. I use an explicit `return` when a function has early exits or is long enough that I want the exit point to be obvious, and I leave it off for short mathematical functions. Do whichever makes the code clearer.

To return nothing at all, write `return nothing` (or just `return`). Julia has a real `nothing` value of type `Nothing`, which is distinct from `missing` (missing data) and from `NaN` (a floating-point non-number). Keeping those three straight will save you confusion later.

## Arguments

### Positional and optional

```{code-block} julia
function apply_discount(price, rate = 0.10)
    return price * (1 - rate)
end

apply_discount(100)         # 90.0  — uses the default
apply_discount(100, 0.25)   # 75.0
```

### Keyword arguments

Anything after a semicolon in the signature is a **keyword argument**, and must be passed by name at the call site:

```{code-block} julia
function post_entry(account, amount; currency = "USD", reversal = false)
    sign = reversal ? -1 : 1
    println("$account: $(sign * amount) $currency")
end

post_entry("GL-4001", 250)
post_entry("GL-4001", 250; currency = "EUR", reversal = true)
```

The semicolon is the important character. This distinction between positional and keyword arguments isn't just style — positional arguments participate in dispatch (which method gets called), and keyword arguments do not. That's a rule you'll want to remember when we get to the dispatch chapter.

There's also a nice shorthand when your variable name matches the keyword name:

```{code-block} julia
currency = "GBP"
post_entry("GL-4001", 250; currency)     # same as currency = currency
```

### Variable numbers of arguments

Three dots — the "splat" operator — collects extra arguments:

```{code-block} julia
function total(items...)
    s = 0
    for item in items
        s += item
    end
    return s
end

total(1, 2, 3)          # 6
total(1, 2, 3, 4, 5)    # 15
```

The same dots work in reverse at a call site, spreading a collection into individual arguments:

```{code-block} julia
values = [1, 2, 3, 4]
total(values...)        # 10
```

## Type annotations on arguments

You can restrict what a function accepts:

```{code-block} julia
function net_pay(gross::Float64, tax_rate::Float64)
    return gross - gross * tax_rate
end
```

Now here's the thing that surprises people coming from static languages: **this does not make the function faster.** Julia was already going to compile a specialized version for whatever concrete types you passed. Adding `::Float64` doesn't give the compiler information it didn't have; it just refuses other types.

So why annotate at all? Two real reasons:

1. **To dispatch** — to say "when the arguments are *these* types, run *this* implementation." That's the next chapter, and it's the main reason.
2. **To document and constrain** — to make a wrong call fail loudly at the boundary rather than producing a confusing error five frames deep.

What annotating does *not* do is help performance, and over-annotating actively hurts you by making your code needlessly rigid. The version above can't take an `Int`, can't take a `Float32`, can't take a `BigFloat`. The unannotated version handles all of them, each with its own specialized compiled code. In Julia, **leaving types off is how you write generic, reusable, fast code** — which is the opposite of the instinct you may have brought with you.

If you want a middle ground, annotate abstractly:

```{code-block} julia
function net_pay(gross::Real, tax_rate::Real)
    return gross - gross * tax_rate
end
```

That accepts any real number type — `Int`, `Float64`, `Rational`, `BigFloat` — while still rejecting a `String`. This is usually the right instinct, and we'll formalize it in the *Type System* chapter.

## Anonymous functions

Functions that don't need a name, usually because you're handing them to another function:

```{code-block} julia
xs = [1, 2, 3, 4, 5]

map(x -> x^2, xs)                 # [1, 4, 9, 16, 25]
filter(x -> x % 2 == 0, xs)       # [2, 4]
```

For multi-line anonymous functions there's a block form:

```{code-block} julia
map(xs) do x
    y = x^2
    y + 1
end
```

That `do` block syntax looks strange the first time. All it means is: *take this block, make it an anonymous function, and pass it as the first argument*. That's why `open("file.txt") do f ... end` worked in the last chapter — `open` takes a function as its first argument and takes care of closing the file afterwards.

## Functions are values

This is worth stating explicitly because it unlocks a lot of Julia code:

```{code-block} julia
function apply_twice(f, x)
    return f(f(x))
end

apply_twice(sqrt, 16.0)          # 2.0
apply_twice(x -> x + 1, 10)      # 12
```

Functions can be stored in variables, put in arrays, passed as arguments, and returned from other functions. And unlike in many dynamic languages, this costs you nothing — Julia specializes `apply_twice` on the specific function you passed, so `f(f(x))` compiles down to a direct call with no indirection.

## The `!` convention

We met this in chapter three, and now that we're writing functions it's worth stating as a rule you should follow in your own code: **if your function modifies its arguments, end its name with `!`**.

```{code-block} julia
sort(xs)      # returns a new sorted array; xs is untouched
sort!(xs)     # sorts xs in place, and returns it
```

The compiler doesn't enforce this. It's a promise between programmers, and the entire ecosystem keeps it, which means you can read unfamiliar Julia and know at a glance which calls are destructive. Keep the promise in your own code.

There's a related convention: when a function mutates one of its arguments, that argument goes **first**. `push!(collection, item)`, `sort!(array)`, `copyto!(destination, source)`.

## Composition and piping

Two small operators that make data transformations read nicely:

```{code-block} julia
# Piping: value |> function
[1, 4, 9] .|> sqrt |> sum          # 6.0

# Composition: build a new function out of two
normalize = strip ∘ lowercase       # \circ + Tab
normalize("  Amit Shukla  ")        # "amit shukla"
```

`|>` sends a value into a function. `∘` glues two functions into one, applied right to left, exactly like the mathematical notation. Neither is required, but pipelines of small transformations are common in data work and these read well.

## A small, real example

Here's the shape of a function I'd actually write. Take a vector of raw ledger amounts, clean it, and summarize it.

```{code-block} julia
"""
    summarize(amounts; drop_negatives = true)

Return a named tuple with the count, total, and mean of `amounts`.
Negative entries are treated as data-entry errors and dropped by default.
"""
function summarize(amounts; drop_negatives = true)
    clean = drop_negatives ? filter(>=(0), amounts) : amounts
    n = length(clean)
    total = sum(clean; init = zero(eltype(clean)))
    return (count = n, total = total, mean = n == 0 ? NaN : total / n)
end
```

```{code-block} julia
julia> summarize([1200, 800, -50, 1500])
(count = 3, total = 3500, mean = 1166.6666666666667)

julia> summarize([1200, 800, -50, 1500]; drop_negatives = false)
(count = 4, total = 3450, mean = 862.5)
```

Three things in there worth pointing out.

**The docstring.** That triple-quoted string above the function isn't a comment — Julia attaches it to the function, and it shows up when anyone types `?summarize`. Writing these is a habit worth forming early; the convention is to show the signature indented on the first line, then describe what it does.

**No type annotations.** `summarize` works on a vector of `Int`, a vector of `Float64`, even a vector of `Rational`, each compiled separately and each fast. If I'd written `amounts::Vector{Float64}` I'd have thrown all of that away for nothing.

**`zero(eltype(clean))`.** This is a small but very Julian move. Rather than hardcoding `0` (an `Int`) or `0.0` (a `Float64`) as the starting value for the sum, I ask for "the zero of whatever type this collection holds." The function stays generic. You'll see `zero`, `one`, `eltype`, and `similar` used this way throughout the ecosystem — they're how generic code stays type-correct.

## Try it yourself

1. Write a function `celsius_to_fahrenheit(c)` and use it on a whole array with `map`.
2. Write a function `describe(name; greeting = "Hello")` that prints `"Hello, name!"` and lets the caller change the greeting.
3. Write `apply_n(f, x, n)` that applies function `f` to `x`, `n` times. Test it with `apply_n(x -> x * 2, 1, 10)`.
4. Write a mutating function `clip!(xs, lo, hi)` that clamps every element of `xs` into the range `[lo, hi]`, in place. Name it correctly.

### Solutions

```{code-block} julia
# 1.
celsius_to_fahrenheit(c) = c * 9/5 + 32
map(celsius_to_fahrenheit, [0, 20, 37, 100])    # [32.0, 68.0, 98.6, 212.0]

# 2.
function describe(name; greeting = "Hello")
    println("$greeting, $name!")
end
describe("Amit")
describe("Amit"; greeting = "Namaste")

# 3.
function apply_n(f, x, n)
    for _ in 1:n
        x = f(x)
    end
    return x
end
apply_n(x -> x * 2, 1, 10)      # 1024

# 4.
function clip!(xs, lo, hi)
    for i in eachindex(xs)
        xs[i] = clamp(xs[i], lo, hi)
    end
    return xs
end
```

(For that last one — `clamp` already exists in Base, and `clamp!` does exactly this. Checking whether the standard library already has your function is a habit worth building.)

You can now write and compose functions. Next we'll look at the collections you'll be feeding them: arrays, tuples, dictionaries, and sets.
