amber: writing bash scripts in amber instead. pt. 4: functions
Tags: linux, bash
Published: 2024-07-03 15:04:42

a while ago, i blogged about [uploading files to s3 using curl](https://gbh.fruitbat.io/2024/04/22/uploading-to-s3-with-bash/) and provided the solution as [two functions written in bash](https://gist.github.com/gbhorwood/d861c7a21f2ab151046025137f6a65b1), and basically the only feedback was "wait. you can write _functions_ in bash?"

you can. but in reality, you probably don't want to. the syntax for defining (and calling) bash functions is horrible. there's a reason people genrally don't use them.

writing and using functions in amber, by comparison, is a borderline delight of sanity. if you've ever written a function in php or python or javascript, amber's function syntax will feel familiar and, well, 'normal'.

![community disaster meme](https://gbh.fruitbat.io/wp-content/uploads/2024/07/meme_amber4.jpg "the community disaster meme")<figcaption>a look at the syntax of functions in bash
</figcaption>

## a simple function to start
let's start with a basic hello-world calibre function:

```dart
fun hello() {
    echo "hello"
}

hello()
```

functions are defined with the keyword `fun` to keep things nice and terse, and have a body inside of braces. very comortable, standard stuff. when we call this function, the string 'hello' gets printed to `STDOUT`.


## accepting arguments
arguments can be passed to a function as a comma-separated list. again, no surprises here.

```dart
fun personalized_hello(name) {
    echo "hello {name}"
}

personalized_hello("gbhorwood")
```

note here that we're using [string interpolation](https://en.wikipedia.org/wiki/String_interpolation) in our `echo` statement to output our variable.

## return statements
we can return a value from a function using the `return` statement, just as we would expect.

```dart
fun get_personalized_hello(name) {
    return "hello {name}"
}

echo personalized_hello("gbhorwood")
```

## a little bit of type safety
everybody loves some type safety in their programming language, and amber obliges us by accepting optional types for both arguments and return values. 

```dart
fun sum(a: Num, b: Num): Num {
    return a + b
}
```

types are defined using the colon syntax.

amber has five types:

* **`Text`**: strings, basically.
* **`Num`**: either integers or floats.
* **`Bool`**: the standard `true` or `false`
* **`Null`**: the nothing type. amber uses `Null` as a return types for functions that do not return values.
* **`[<some type>]`**: the array type. in amber, arrays cannot contain mixed types, so the type definition for an array includes the type of the array's elements. if we want to define an array of numbers, for instance, we would type it is `[Num]`.

**important note:** if we define one type in a function, we have to define _all_ the types. for instance, we cannot define just the argument type without also defining the return type. not setting a return type of `Null` here throws an error.

```dart
// this errors because there is no return type
fun say_my_name(name: Text) {
    echo name
}

say_my_name("gbhorwood") // WARN Function has typed arguments but a generic return type
```

we would fix this error by writing our function as:

```dart
// this works
fun say_my_name(name: Text): Null {
    echo name
}
```

likewise, if we define the type of one argument, we have to define them all:

```dart
// this error because last_name has no type
fun say_my_name(first_name: [Text], last_name): Null {
    echo "hello"
}

say_my_name("grant", "horwood") // ERROR Function 'say_my_name' has a mix of generic and typed arguments
```

and, of course, if we define a type, we have to obey it.


```dart
fun say_my_name(name: Text) {
    echo name
}

say_my_name(9) // 1st argument 'name' of function 'say_my_name' expects type 'Text', but 'Num' was given
```

## throwing errors with `fail`
functions in amber can 'throw' an error by using the `fail` statement with an exit code. in this example, we want our function to fail if the user is not root.

```dart
fun root_only_function() {
    unsafe if($whoami$ != "root") {
        fail 1
    }

    echo "only root can do this"
}
```

in the [first installment](https://gbh.fruitbat.io/2024/06/18/amber-writing-bash-scripts-in-amber-instead-pt-1-commands-and-error-handling/), we covered handling bash errors using the `failed` block. we can 'catch' the errors 'thrown' by `fail` the same way.

```dart
root_only_function() failed {
    echo "failed condition"
}
```

likewise, we can also ignore the errors we `fail` from our functions by using `unsafe`.<p>
a while ago, i blogged about <a href="https://gbh.fruitbat.io/2024/04/22/uploading-to-s3-with-bash/">uploading files to s3 in bash</a> and provided the solution as <a href="https://gist.github.com/gbhorwood/d861c7a21f2ab151046025137f6a65b1">two functions written in bash</a>, and basically the only feedback was "wait. you can write <i>functions</i> in bash?"
</p>

<p>
you can. but in reality, you probably don't want to. the syntax for defining (and calling) bash functions is horrible. there's a reason people genrally don't use them.
</p>

writing and using functions in amber, by comparison, is a borderline delight of sanity. if you've ever written a function in php or python or javascript, amber's function syntax will feel familiar and, well, 'normal'.

<h2 class="wp-block-heading"> a simple function to start</h2>
<p>
let's start with a basic hello-world calibre function:
</p>

```dart
unsafe root_only_function()
```

### trapping `failed` cases in our functions
we can also, of course, handle errors from commands by using the `failed` block _inside_ our functions. this function, for example, attempts a shell command and, on failure, throws it's own `fail`.

```dart
fun failing_touch() {
    silent $touch /etc/passwd$ failed {
        fail 1
    }
}

failing_touch() failed {
    echo "function failing_touch failed"
}
```

note that we applied `silent` to our shell command to suppress bash's output. we only want users to see _our_ error messages, not the shell's.

### pushing `failed` cases up to our function call
trapping an error and throwing an explicit `fail` is a bit clumsy. amber also allows us to automatically `fail` up to our to where our function is called by replacing the `failed` block in our function with `?`.

```dart
fun failing_touch() {
    silent $touch /etc/passwd$?
}

failing_touch() failed {
    echo "function failing_touch failed"
}
```

in this example, our function, when called, fails exactly the same way as it would if we'd called `$touch /etc/passwd$` directly. very handy.

## conclusion
this series has covered calling shell commands; handling errors; composing `if` statements and writing loops; using the convenience commands in the standard library; and writing functions. is that all amber can do? no. but it is certainly enough for us to start using this language to do useful, meaningful things.

## a note about vim
writing code in vim is a joyful thing (or, at least, that's my opinion), but not having syntax highlighting in this modern day and age is intolerable, so i composed an [amber syntax file for vim](https://github.com/gbhorwood/amber.vim). i've never written a syntax file before and the effort there is clearly sophomoric, but it _does_ work.

> 🔎  this post was originally written in the [grant horwood technical blog](https://gbh.fruitbat.io/2024/07/03/amber-writing-bash-scripts-in-amber-instead-pt-4-functions/)