Skip to content

Data Types

What is a Data Type?

Every value in Frankie has a type. The type determines what the value can do — what methods it responds to, how it behaves in arithmetic, and how it prints. You never declare types explicitly; Frankie figures them out from the values you assign.

x = 42          # Integer
y = 3.14        # Float
name = "Alice"  # String
flag = true     # Boolean
items = [1,2,3] # Vector
empty = nil     # Nil

Primitive vs Composite Types

Frankie types fall into two groups.

Primitive types hold a single value. They are the atoms of the language — everything else is built from them.

Type Example Description
Integer 42, -7, 0 Whole numbers
Float 3.14, -0.5, 1.0 Decimal numbers
String "hello" Text — UTF-8, double-quoted
Boolean true, false Logical values
Nil nil Absence of a value

Composite types hold multiple values or represent a complex object. They are built from primitives and other composites.

Type Example Description
Vector [1, "two", 3.0] Ordered list of any values
Hash {name: "Alice", age: 30} Key-value map
Lambda ->( x) { x * 2 } A storable, callable function
Record Point(x: 3, y: 4) A named data object built on a Hash

There are also stdlib object typesDB, DateTime, WebApp, HTTPResponse, File — returned by specific stdlib functions. They behave like composite types but are not part of the core language.


How Variables Store Values

Frankie is dynamically typed — a variable has no fixed type. It holds whatever value was last assigned to it, and that value's type can change between assignments.

x = 42        # x is an Integer
x = "hello"   # x is now a String — perfectly valid
x = [1, 2, 3] # x is now a Vector

Variables are created on first assignment. There is no declaration step — no var, let, or int x. If you reference a variable before assigning it, you get a runtime error.

puts mystery   # Runtime error: Undefined variable: mystery

Type Conversion

Frankie does not coerce types silently. Adding an Integer to a String is an error, not a guess. Convert explicitly when you need to cross type boundaries.

# Explicit conversion
puts 42.to_s        # "42"    — Integer to String
puts "42".to_i      # 42     — String to Integer
puts "3.14".to_f    # 3.14   — String to Float
puts 3.14.to_i      # 3      — Float to Integer (truncates)
puts 42.to_f        # 42.0   — Integer to Float

# Type checking
puts is_integer(42)       # true
puts is_float(3.14)       # true
puts is_string("hello")   # true
puts is_vector([1, 2])    # true
puts is_nil(nil)          # true
puts is_bool(false)       # true

String interpolation converts any value automatically inside #{}:

n = 42
puts "The answer is #{n}"   # "42" — no .to_s needed here

Truthiness and Nil

Frankie has strict boolean semantics. Only false and nil are falsy. Everything else — including 0, "", and [] — is truthy. This matches Ruby's model and avoids the silent bugs common in languages that treat 0 as false.

if 0
  puts "0 is truthy in Frankie"   # this prints
end

if ""
  puts "empty string is truthy"   # this prints too
end

if nil
  puts "nil is falsy"   # this does NOT print
end

if false
  puts "false is falsy"   # this does NOT print
end

Check for nil explicitly with .nil? or == nil:

x = nil

if x == nil
  puts "x has no value"
end

if x.nil?
  puts "same thing, method form"
end

Mutability

In Frankie, composite types are mutable — you can change their contents after creation. Primitive types are effectively immutable — string methods like .upcase return new strings rather than modifying the original.

# Strings — immutable style (methods return new values)
s = "hello"
puts s.upcase   # "HELLO"
puts s          # "hello" — unchanged

# Vectors — mutable
v = [1, 2, 3]
v.push(4)
puts v          # [1, 2, 3, 4] — modified in place

v[0] = 99
puts v          # [99, 2, 3, 4] — index assignment modifies in place

# Hashes — mutable
h = {a: 1, b: 2}
h["c"] = 3
puts h          # {a: 1, b: 2, c: 3}

Nil Safety

Calling a method on nil raises a runtime error. Use the safe navigation operator &. to short-circuit instead:

name = nil

puts name.upcase    # Runtime error — nil has no .upcase
puts name&.upcase   # nil — safe, no crash

Chains short-circuit at the first nil:

user = nil
puts user&.address&.city   # nil — stops at user

Type Summary

Type Primitive / Composite Mutable Literal syntax
Integer Primitive No 42, -7
Float Primitive No 3.14, 1.0
String Primitive No "hello"
Boolean Primitive No true, false
Nil Primitive nil
Vector Composite Yes [1, 2, 3]
Hash Composite Yes {a: 1}
Lambda Composite No ->(x) { x * 2 }
Record Composite Yes record Point(x, y)