Type Checking Methods
Suji provides type checking methods that allow you to determine the type of a value at runtime. These methods are available on all values and return boolean results.
Overview
All types support a set of type checking methods that return true if the value is of the specified type, and false otherwise. These methods are useful for runtime type validation and conditional processing based on type.
Available Methods
All values support these type checking methods:
value::is_number()- returnstrueif value is a numbervalue::is_bool()- returnstrueif value is a booleanvalue::is_string()- returnstrueif value is a stringvalue::is_list()- returnstrueif value is a listvalue::is_map()- returnstrueif value is a mapvalue::is_stream()- returnstrueif value is a streamvalue::is_function()- returnstrueif value is a functionvalue::is_tuple()- returnstrueif value is a tuplevalue::is_regex()- returnstrueif value is a regex
Every type has all of these methods available, and each method returns true only when called on its corresponding type. The nil type also supports all these methods and returns false for all of them.
Numbers have one additional predicate:
number::is_int()- returnstrueif the number has no fractional part
is_int() is only available on numbers; calling it on a string or any other type raises a Method error and terminates the program.
What does not exist
This list is complete. In particular, there is:
- No
is_nil()- test for nil withvalue == nil - No
is_boolean()- the method is calledis_bool() - No
type(),type_of()ortypeof- there is no way to obtain a type as a value; you ask a yes/no question with a predicate instead
Testing for Nil
Because there is no is_nil(), comparisons are how you detect nil. Every predicate returns false for nil, so a chain of predicates falls through to the catch-all arm:
import std:println
describe = |value| match {
value == nil => "nothing",
value::is_number() => "number",
value::is_string() => "string",
_ => "something else",
}
println(describe(nil)) # nothing
println(describe(42)) # number
println(describe("hi")) # string
println(describe([1, 2])) # something else
nil is also a valid pattern in the subject form of match:
import std:println
value = nil
println(match value {
nil => "missing",
_ => "present",
}) # missing
Integer Checks
import std:println
println((42)::is_int()) # true
println((3.5)::is_int()) # false
println((42.0)::is_int()) # true
println((10 / 2)::is_int()) # true
# Guard the check so it only runs on numbers
is_whole = |v| v::is_number() && v::is_int()
println(is_whole(7)) # true
println(is_whole("7")) # false
Basic Usage
import std:println
# Number type checking
x = 42
println(x::is_number()) # true
println(x::is_string()) # false
println(x::is_list()) # false
# String type checking
s = "hello"
println(s::is_string()) # true
println(s::is_number()) # false
println(s::is_map()) # false
# Boolean type checking
b = true
println(b::is_bool()) # true
println(b::is_number()) # false
# List type checking
lst = [1, 2, 3]
println(lst::is_list()) # true
println(lst::is_tuple()) # false
println(lst::is_map()) # false
# Map type checking
m = { a: 1, b: 2 }
println(m::is_map()) # true
println(m::is_list()) # false
# Tuple type checking
t = (1, 2, 3)
println(t::is_tuple()) # true
println(t::is_list()) # false
# Function type checking
f = |x| x + 1
println(f::is_function()) # true
println(f::is_number()) # false
# Stream type checking
import std:io
stream = io:stdout
println(stream::is_stream()) # true
println(stream::is_string()) # false
# Regex type checking
pattern = /^[a-z]+$/
println(pattern::is_regex()) # true
println(pattern::is_string()) # false
println(pattern::is_number()) # false
# Nil type checking
n = nil
println(n::is_number()) # false
println(n::is_string()) # false
println(n::is_list()) # false
println(n::is_map()) # false
println(n::is_tuple()) # false
println(n::is_bool()) # false
println(n::is_function()) # false
println(n::is_stream()) # false
println(n::is_regex()) # false
Common Use Cases
Runtime Type Validation
import std:println
process = |value| {
match {
value::is_number() => value * 2,
value::is_string() => value + " processed",
value::is_list() => value::length(),
_ => nil,
}
}
println(process(10)) # 20
println(process("data")) # data processed
println(process([1, 2, 3])) # 3
Type-Safe Function Parameters
import std:println
safe_divide = |a, b| {
match {
!a::is_number() || !b::is_number() => return nil,
b == 0 => return nil,
_ => a / b,
}
}
println(safe_divide(10, 2)) # 5
println(safe_divide(10, 0)) # nil
println(safe_divide("10", 2)) # nil
Conditional Type Handling
import std:println
format_value = |v| {
match {
v::is_string() => '"' + v + '"',
v::is_number() => v::to_string(),
v::is_bool() => match v {
true => "true",
false => "false",
},
v::is_list() => "[list]",
v::is_map() => "{map}",
v::is_function() => "<function>",
v::is_tuple() => "(tuple)",
v::is_stream() => "<stream>",
v::is_regex() => "<regex>",
_ => "nil",
}
}
println(format_value("hello")) # "hello"
println(format_value(42)) # 42
println(format_value(true)) # true
println(format_value([1, 2])) # [list]
println(format_value(nil)) # nil
Type-Based Dispatch
import std:println
handle = |data| {
match {
data::is_string() => {
println("Processing string: ${data}")
data::upper()
},
data::is_list() => {
println("Processing list with ${data::length()} items")
data::sum()
},
data::is_map() => {
println("Processing map with ${data::length()} keys")
data::keys()::join(", ")
},
_ => {
println("Unknown type")
nil
},
}
}
println(handle("hello")) # Processing string: hello\nHELLO
println(handle([1, 2, 3])) # Processing list with 3 items\n6
println(handle({ a: 1, b: 2 })) # Processing map with 2 keys\na, b
Input Validation
import std:println
validate_input = |input| {
match {
input::is_string() && input::length() > 0 => "Valid string",
input::is_number() && input > 0 => "Valid positive number",
input::is_list() && input::length() > 0 => "Valid non-empty list",
_ => "Invalid input",
}
}
println(validate_input("hello")) # Valid string
println(validate_input("")) # Invalid input
println(validate_input(42)) # Valid positive number
println(validate_input(-5)) # Invalid input
println(validate_input([1, 2])) # Valid non-empty list
println(validate_input([])) # Invalid input
Best Practices
DO:
- Use type checking methods for runtime validation
- Combine with match expressions for type-based dispatch
- Check types before performing type-specific operations
- Use type checks for input validation
DON’T:
- Overuse type checking (Suji is dynamically typed)
- Check types unnecessarily when types are already known
- Look for
is_nil(),is_boolean()ortype()- they do not exist - Call
is_int()on a value you have not already confirmed is a number
Implementation Notes
- Type checking methods are available on all values, including
nil - Each method performs a runtime type check and returns a boolean result
is_int()is the only predicate restricted to a single type (numbers)- Because there is no way to recover from a runtime error, predicates are the main tool for keeping a program on a valid path - check first, then operate
See Also
- Data Types - Overview of all Suji types
- Match Expressions - Pattern matching with type checks
- Nil Type - Handling nil values
- Error Handling - Validating before you act