Sapphire
A Ruby-inspired, gradually typed, object-oriented scripting language — everything is an object, types are optional, and the syntax stays out of your way.
Website · Try it online · Tutorial
Features
- Gradual typing — annotate as much or as little as you like; types are checked at runtime when present
- Everything is an object —
Int,Bool,String, and other primitives have methods - Classes with inheritance — single inheritance,
attrfields, private methods viadefp, class methods - Closures and blocks — first-class functions,
yield, and block-accepting methods - Rich standard library —
List,Map,Set,String,Regex,Math,Date,File, and more - Imports — split code across files with
import - Mark-and-sweep GC — handles cycles; no manual memory management
Quick look
{
attr color =
{ 0 }
{
}
}
{
attr radius: Float
{
Math::PI * self.radius * self.radius
}
}
c = Circle.new(color: , radius: 3.0)
print c.describe()
print c.is_a?(Shape) # true
Syntax
Variables and types
x = 10
name: =
flag = true
Arithmetic and comparisons
1 + 2 * 3
x == 10
x > 0
!flag
Control flow
if x > 0 {
print x
} elsif x == 0 {
print
} else {
print
}
while x < 10 {
x = x + 1
}
(1..5).each { print i }
Functions
-> Int {
a + b
}
-> Int {
return min if value < min
return max if value > max
value
}
Blocks and yield:
{
i = 0
while i < n {
yield(i)
i = i + 1
}
}
repeat(3) { print }
Classes
{
attr balance: = 0
{
self.balance = self.balance + validate(amount)
}
{
self.balance = self.balance - validate(amount)
}
defp validate(amount: Int) -> Int {
raise if amount <= 0
amount
}
}
account = BankAccount.new()
account.deposit(100)
account.withdraw(30)
print account.balance # 70
Class methods use self { }:
{
attr r: Int
attr g: Int
attr b: Int
self {
{ Color.new(r: 255, g: 0, b: 0) }
{ Color.new(r: 0, g: 255, b: 0) }
{ Color.new(r: 0, g: 0, b: 255) }
}
}
c = Color.red()
Collections
numbers = [3, 1, 4, 1, 5, 9]
doubled = numbers.map { n * 2 }
evens = numbers.select { n % 2 == 0 }
total = numbers.reduce(0) { acc + n }
print numbers.any? { n > 8 } # true
print numbers.all? { n > 0 } # true
scores = { alice: 95, bob: 82, carol: 91 }
scores.each { print }
passing = scores.select { score >= 90 }
Imports
import
import
p = Point.new(x: 1.0, y: 2.0)
Error handling
begin
result = risky_op()
rescue e
print
else
print
end
Inline rescue inside a function:
-> Int {
a / b
rescue e
0
}
Running
sapphire run file.spr # run a file
sapphire test # run *_test.spr files
sapphire typecheck file.spr
sapphire console # start the REPL