Variables & Types
Variables
Variables are created on first assignment. There is no declaration step — no var, let, or type annotation. Frankie figures out the type from the value.
name = "Alice"
age = 30
score = 97.5
active = true
nothing = nil
Variable names are lowercase with underscores. A variable referenced before assignment raises a runtime error.
puts mystery # Runtime error: Undefined variable: mystery
Reassignment
A variable can be reassigned to any type at any time:
x = 42
x = "hello" # now a String — valid
x = [1, 2, 3] # now a Vector — still valid
Multiple Assignment (Destructuring)
Assign several variables at once from a vector:
a, b, c = [10, 20, 30]
puts a # 10
puts b # 20
puts c # 30
# Extra variables are set to nil
x, y, z = [1, 2]
puts z # nil
# Common use: functions that return multiple values
def min_max(v)
[min(v), max(v)]
end
lo, hi = min_max([3, 1, 4, 1, 5, 9])
puts "#{lo}..#{hi}" # 1..9
Constants
Names written in UPPER_SNAKE_CASE are treated as constants. Reassignment prints a warning and the original value is preserved.
MAX_RETRIES = 3
PI = 3.14159
APP_NAME = "Frankie"
puts MAX_RETRIES # 3
MAX_RETRIES = 10 # Warning: MAX_RETRIES is a constant
puts MAX_RETRIES # 3 — unchanged
Types
Frankie is dynamically typed — every value has a type, but variables do not. The type is determined by the value at the time it is used.
| Type | Example | Description |
|---|---|---|
Integer |
42, -7 |
Whole numbers, no size limit |
Float |
3.14, -0.5 |
Decimal numbers |
String |
"hello" |
UTF-8 text |
Boolean |
true, false |
Logical values |
Nil |
nil |
Absence of a value |
Vector |
[1, 2, 3] |
Ordered list |
Hash |
{name: "Alice"} |
Key-value map |
Lambda |
->(x) { x * 2 } |
Storable function |
Record |
Point(x: 3, y: 4) |
Named data object |
Type Checking
puts is_integer(42) # true
puts is_float(3.14) # true
puts is_string("hello") # true
puts is_vector([1,2,3]) # true
puts is_nil(nil) # true
puts is_bool(true) # true
Type Conversion
Frankie does not coerce types silently. Convert explicitly:
puts 42.to_s # "42" — Integer to String
puts 42.to_f # 42.0 — Integer to Float
puts "42".to_i # 42 — String to Integer
puts "3.14".to_f # 3.14 — String to Float
puts 3.9.to_i # 3 — Float to Integer (truncates)
# Standalone function forms
puts to_int("42") # 42
puts to_float("3.14") # 3.14
puts to_str(42) # "42"
Inside #{} interpolation, any value is converted to a string automatically — no .to_s needed:
n = 42
puts "The answer is #{n}" # The answer is 42
puts "Double is #{n * 2}" # Double is 84
puts "Type check: #{is_integer(n)}" # Type check: true
Truthiness
Only false and nil are falsy. Everything else — including 0 and "" — is truthy:
if 0 then puts "truthy" end # prints
if "" then puts "truthy" end # prints
if [] then puts "truthy" end # prints
if false then puts "truthy" end # does not print
if nil then puts "truthy" end # does not print
Nil Safety
Calling a method on nil raises a runtime error. The &. operator returns nil instead of crashing:
name = nil
puts name.upcase # Runtime error
puts name&.upcase # nil — safe
# Chains short-circuit at the first nil
puts name&.upcase&.reverse # nil
Scope
Frankie has simple lexical scope. Variables defined in the outer scope are readable inside functions and blocks. Variables assigned inside a function are local to that function.
x = 10
def show
puts x # readable — outer scope is accessible
end
show # 10
def modify
x = 99 # local to modify — does not affect outer x
end
modify
puts x # 10 — unchanged