Variables and Types
Utah provides a strong type system inspired by TypeScript, allowing you to write more reliable shell scripts with compile-time type checking.
Variable Declarations
let - Mutable Variables
Use let to declare mutable variables:
let userName: string = "Alice";
let count: number = 42;
let isActive: boolean = true;
// Variables can be reassigned
userName = "Bob";
count = count + 1;
isActive = false;
Generated bash:
userName="Alice"
count="42"
isActive="true"
userName="Bob"
count=$((count + 1))
isActive="false"
const - Immutable Constants
Use const for values that won't change:
const APP_NAME: string = "MyApplication";
const MAX_RETRIES: number = 3;
const DEBUG_MODE: boolean = false;
// Error: Cannot reassign const variables
// APP_NAME = "OtherApp";  // This would cause an error
Generated bash:
readonly APP_NAME="MyApplication"
readonly MAX_RETRIES="3"
readonly DEBUG_MODE="false"
Type Annotations
Utah supports several built-in types:
Basic Types
string
Text and character data:
let message: string = "Hello, Utah!";
let templateLiteral: string = "Dynamic: ${message}";
number
Numeric values (integers and decimals):
let integer: number = 42;
let decimal: number = 3.14;
let negative: number = -10;
let calculated: number = 5 + 3;
boolean
True/false values:
let isEnabled: boolean = true;
let isDisabled: boolean = false;
let computed: boolean = count > 0;
Array Types
string[]
Arrays of strings:
let names: string[] = ["Alice", "Bob", "Charlie"];
let fruits: string[] = [];  // Empty array
let colors: string[] = ["red", "green", "blue"];
// Access elements
let firstName: string = names[0];    // "Alice"
let lastColor: string = colors[2];   // "blue"
number[]
Arrays of numbers:
let scores: number[] = [85, 92, 78, 90];
let fibonacci: number[] = [1, 1, 2, 3, 5, 8];
let measurements: number[] = [1.5, 2.7, 3.8];
// Array operations
let firstScore: number = scores[0];
let arrayLength: number = scores.length;
Object Type
object
For JSON/YAML objects:
// Parse JSON into object
let configJson: string = "{\"host\": \"localhost\", \"port\": 8080}";
let config: object = json.parse(configJson);
// Parse YAML into object
let yamlConfig: string = "host: localhost\nport: 8080";
let yamlObj: object = yaml.parse(yamlConfig);
// Access object properties
let host: string = json.get(config, ".host");
let port: number = json.get(config, ".port");
Type Inference
While type annotations are recommended, Utah can infer types in some cases:
// Explicit typing (preferred)
let name: string = "Utah";
let version: number = 1.0;
// Type inference (less clear)
let name = "Utah";     // Inferred as string
let version = 1.0;     // Inferred as number
String Interpolation
Use template literals with ${} for string interpolation:
let userName: string = "Alice";
let score: number = 95;
let isWinner: boolean = score > 90;
let message: string = "Hello ${userName}!";
let result: string = "Score: ${score}, Winner: ${isWinner}";
let complex: string = "User ${userName} scored ${score} points and ${isWinner ? \"won\" : \"lost\"}";
Generated bash:
userName="Alice"
score="95"
isWinner="true"
message="Hello ${userName}!"
result="Score: ${score}, Winner: ${isWinner}"
if [ "${isWinner}" = "true" ]; then
  complex="User ${userName} scored ${score} points and won"
else
  complex="User ${userName} scored ${score} points and lost"
fi
Variable Scope
Global Variables
Variables declared at the script level are global:
let globalVar: string = "Available everywhere";
const GLOBAL_CONSTANT: number = 42;
function useGlobals(): void {
  console.log(globalVar);        // Accessible
  console.log(GLOBAL_CONSTANT);  // Accessible
}
Function Parameters
Function parameters are local to the function:
function greet(name: string, age: number): void {
  // 'name' and 'age' are only available within this function
  let greeting: string = "Hello ${name}, you are ${age} years old";
  console.log(greeting);
}
greet("Alice", 30);
// console.log(name);  // Error: 'name' not available here
Generated bash:
greet() {
  local name="$1"
  local age="$2"
  local greeting="Hello ${name}, you are ${age} years old"
  echo "${greeting}"
}
Arrays in Detail
Array Declaration and Initialization
// Different ways to declare arrays
let emptyStrings: string[] = [];
let emptyNumbers: number[] = [];
let languages: string[] = ["JavaScript", "TypeScript", "Utah"];
let versions: number[] = [1, 2, 3, 4, 5];
// Mixed initialization
let mixed: string[] = ["first"];
mixed[1] = "second";
mixed[2] = "third";
Array Access and Properties
let items: string[] = ["apple", "banana", "cherry"];
// Access by index
let first: string = items[0];     // "apple"
let second: string = items[1];    // "banana"
let last: string = items[2];      // "cherry"
// Array properties
let length: number = array.length(items);        // 3
let isEmpty: boolean = array.isEmpty(items);   // false
Array Methods
let numbers: number[] = [3, 1, 4, 1, 5];
// Check if array contains item
let hasThree: boolean = array.contains(numbers, 3);  // true
let hasTen: boolean = array.contains(numbers, 10);   // false
// Reverse array
let reversed: number[] = array.reverse(numbers);   // [5, 1, 4, 1, 3]
// Join array elements
let joined: string = array.join(["a", "b", "c"], "-");  // "a-b-c"
Array Iteration
let fruits: string[] = ["apple", "banana", "orange"];
// For-in loop (recommended)
for (let fruit: string in fruits) {
  console.log("Fruit: ${fruit}");
}
// Traditional for loop
for (let i: number = 0; i < fruits.length; i++) {
  let fruit: string = fruits[i];
  console.log("Index ${i}: ${fruit}");
}
Working with Objects
Objects in Utah are primarily used for JSON and YAML data:
JSON Objects
// Parse JSON
let userJson: string = "{\"name\": \"Alice\", \"age\": 30, \"admin\": true}";
let user: object = json.parse(userJson);
// Access properties
let name: string = json.get(user, ".name");
let age: number = json.get(user, ".age");
let isAdmin: boolean = json.get(user, ".admin");
// Modify objects
user = json.set(user, ".name", "Bob");
user = json.set(user, ".lastLogin", "2023-12-01");
// Convert back to JSON
let updatedJson: string = json.stringify(user);
YAML Objects
// Parse YAML
let configYaml: string = `
database:
  host: localhost
  port: 5432
features:
  - logging
  - monitoring
`;
let config: object = yaml.parse(configYaml);
// Access nested properties
let dbHost: string = yaml.get(config, ".database.host");
let dbPort: number = yaml.get(config, ".database.port");
let firstFeature: string = yaml.get(config, ".features[0]");
// Check property existence
let hasDatabase: boolean = yaml.has(config, ".database");
let hasAuth: boolean = yaml.has(config, ".auth");
Type Conversion
Utah handles type conversions automatically in many cases:
String to Number
let input: string = "42";
let num: number = parseInt(input);     // Manual parsing if needed
let calculated: number = 10 + 5;      // Direct numeric operation
Boolean Conversion
let flag: string = "true";
let isTrue: boolean = flag == "true";  // String comparison to boolean
let count: number = 5;
let hasItems: boolean = count > 0;     // Numeric comparison to boolean
Template Literal Conversion
let count: number = 42;
let active: boolean = true;
// Automatic conversion in template literals
let message: string = "Count: ${count}, Active: ${active}";
// Result: "Count: 42, Active: true"
Environment Variables
Access environment variables with proper typing:
// Get environment variable with default
let dbHost: string = env.get("DB_HOST", "localhost");
let dbPort: number = parseInt(env.get("DB_PORT", "5432"));
let debugMode: boolean = env.get("DEBUG", "false") == "true";
// Use environment variables
console.log("Connecting to ${dbHost}:${dbPort}");
if (debugMode) {
  console.log("Debug mode enabled");
}
Best Practices
1. Always Use Type Annotations
// Good - explicit and clear
let userName: string = "Alice";
let userCount: number = 42;
let isActive: boolean = true;
// Avoid - unclear types
let userName = "Alice";
let userCount = 42;
let isActive = true;
2. Use Descriptive Variable Names
// Good - self-documenting
let databaseConnectionString: string = "localhost:5432";
let maxRetryAttempts: number = 3;
let isUserAuthenticated: boolean = false;
// Avoid - unclear meaning
let db: string = "localhost:5432";
let max: number = 3;
let auth: boolean = false;
3. Use Constants for Fixed Values
// Good - prevents accidental changes
const API_ENDPOINT: string = "https://api.example.com";
const TIMEOUT_SECONDS: number = 30;
const ENABLE_LOGGING: boolean = true;
// Less safe - might be changed accidentally
let apiEndpoint: string = "https://api.example.com";
4. Initialize Arrays Explicitly
// Good - clear intent
let usernames: string[] = [];
let scores: number[] = [0, 0, 0];
// Clear when adding items
usernames[0] = "alice";
usernames[1] = "bob";
5. Validate Data Before Use
// Good - safe data handling
let configData: string = fs.readFile("config.json");
if (json.isValid(configData)) {
  let config: object = json.parse(configData);
  let host: string = json.get(config, ".database.host");
} else {
  console.log("Invalid configuration file");
  exit(1);
}
Common Patterns
Configuration Loading
const CONFIG_FILE: string = "app.json";
const DEFAULT_PORT: number = 8080;
let port: number = DEFAULT_PORT;
let host: string = "localhost";
if (fs.exists(CONFIG_FILE)) {
  let configContent: string = fs.readFile(CONFIG_FILE);
  if (json.isValid(configContent)) {
    let config: object = json.parse(configContent);
    if (json.has(config, ".port")) {
      port = json.get(config, ".port");
    }
    if (json.has(config, ".host")) {
      host = json.get(config, ".host");
    }
  }
}
console.log("Server will start on ${host}:${port}");
Data Processing Pipeline
let inputFiles: string[] = ["data1.json", "data2.json", "data3.json"];
let processedCount: number = 0;
let errors: string[] = [];
for (let file: string in inputFiles) {
  try {
    if (fs.exists(file)) {
      let content: string = fs.readFile(file);
      if (json.isValid(content)) {
        let data: object = json.parse(content);
        // Process data here
        processedCount++;
      } else {
        errors[errors.length] = "Invalid JSON in ${file}";
      }
    } else {
      errors[errors.length] = "File not found: ${file}";
    }
  }
  catch {
    errors[errors.length] = "Error processing ${file}";
  }
}
console.log("Processed ${processedCount} files successfully");
if (errors.length > 0) {
  console.log("Errors encountered:");
  for (let error: string in errors) {
    console.log("- ${error}");
  }
}
User Input Validation
args.define("--count", "-c", "Number of items", "number", false, 10);
args.define("--name", "-n", "User name", "string", true);
let count: number = args.get("--count");
let userName: string = args.get("--name");
// Validate input
if (count <= 0) {
  console.log("Count must be positive");
  exit(1);
}
if (userName.length == 0) {
  console.log("Name cannot be empty");
  exit(1);
}
console.log("Processing ${count} items for user: ${userName}");
Next Steps
Now that you understand variables and types in Utah:
- Learn about functions - Function declarations and parameters
 - Explore control flow - Conditionals, loops, and logic
 - Study arrays - Array manipulation and operations
 - Study strings - String processing and manipulation
 - Discover built-in functions - Leverage Utah's function library