Skip to content

Functions

Carina provides built-in functions for common operations and lets you define your own functions. This guide covers both, along with the pipe and compose operators.

Built-in functions

String functions

FunctionSignatureDescription
upperupper(string) -> stringConverts to uppercase
lowerlower(string) -> stringConverts to lowercase
trimtrim(string) -> stringRemoves leading/trailing whitespace
replacereplace(search, replacement, string) -> stringReplaces all occurrences
splitsplit(separator, string) -> listSplits string into a list
joinjoin(separator, list) -> stringJoins list elements into a string

Examples:

awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = upper('my-vpc') # 'MY-VPC'
Env = replace('_', '-', 'my_env') # 'my-env'
Id = join('-', ['vpc', 'prod']) # 'vpc-prod'
}
}

Collection functions

FunctionSignatureDescription
lengthlength(list | map | string) -> intReturns element/character count
concatconcat(items, base_list) -> listAppends items to a list
flattenflatten(list) -> listFlattens nested lists by one level
keyskeys(map) -> listReturns map keys as a sorted list
valuesvalues(map) -> listReturns map values sorted by key
lookuplookup(map, key, default) -> anyLooks up a key with a fallback
mapmap(accessor, collection) -> list | mapExtracts a field from each element

Examples:

let parts1 = ['web', 'test']
let parts2 = ['vpc']
awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = join('-', concat(parts2, parts1)) # 'web-test-vpc'
}
}

Numeric functions

FunctionSignatureDescription
minmin(a, b) -> numberReturns the smaller value
maxmax(a, b) -> numberReturns the larger value

Network functions

FunctionSignatureDescription
cidr_subnetcidr_subnet(prefix, newbits, netnum) -> stringCalculates a subnet CIDR block

Example:

let vpcs = for (i, env) in ['dev', 'stg'] {
awscc.ec2.vpc {
cidr_block = cidr_subnet('10.0.0.0/8', 8, i)
# i=0 -> '10.0.0.0/16', i=1 -> '10.1.0.0/16'
tags = {
Name = "vpc-${env}"
}
}
}

Environment and security functions

FunctionSignatureDescription
envenv(name) -> stringReads an environment variable
secretsecret(value) -> secretMarks a value as secret (stored as hash in state)
decryptdecrypt(ciphertext, key?) -> stringDecrypts using the provider’s encryption service (e.g., AWS KMS)

User-defined functions

Define reusable logic with the fn keyword:

fn tag_name(env: string, service: string): string {
join('-', [env, service, 'vpc'])
}
awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = tag_name('production', 'web') # 'production-web-vpc'
}
}

Function syntax

fn name(param1: type1, param2: type2): return_type {
expression
}
  • Parameters can have type annotations (: string, : int, etc.)
  • Return type annotation is optional (: string)
  • The function body is a single expression (the return value)

Local variables in functions

Functions can have local let bindings before the final expression:

fn subnet_name(env: string, tier: string, index: int): string {
let prefix = join("-", [env, tier])
"${prefix}-${index}"
}

Default parameter values

Parameters can have default values:

fn make_tags(name: string, env: string = 'dev'): map(string) {
{
Name = name
Environment = env
}
}

Pipe operator

The pipe operator |> passes the result of the left side as the last argument to the function on the right (data-last convention):

# Without pipe
let result = join('-', split('_', upper('hello_world')))
# With pipe -- reads left to right
let result = 'hello_world' |> upper() |> split('_') |> join('-')
# Result: 'HELLO-WORLD'

The pipe operator is especially useful with collection functions:

let names = ['web', 'api', 'worker']
# Extract and transform
let result = names |> join(', ')

Compose operator

The compose operator >> creates a new function by chaining two partially applied functions (closures). Both sides must be closures:

# split('_') is a closure (1 of 2 args provided)
# join('-') is a closure (1 of 2 args provided)
let transform = split('_') >> join('-')

The resulting function applies the left function first, then passes the result to the right function.

Partial application

When you call a built-in function with fewer arguments than it expects, you get a closure — a partially applied function that waits for the remaining arguments:

# replace expects 3 args; giving 2 returns a closure
let dashify = replace('_', '-')
# The closure is called when the last argument arrives (via pipe)
let result = 'hello_world' |> dashify() # 'hello-world'

This works naturally with the pipe operator, since the piped value fills in the last argument.