Skip to content

Commit

Permalink
Make various types inline types
Browse files Browse the repository at this point in the history
For types such as tuples, Option, Result, and various others, there's no
need to allocate them on the heap. In fact, for frequently used types
such as Option and Result this does nothing but harm performance. To
resolve this, various types are marked as `inline` such that they are
allocated on the stack.

Changelog: performance
  • Loading branch information
yorickpeterse committed Dec 10, 2024
1 parent 873adca commit fe867a6
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 62 deletions.
1 change: 1 addition & 0 deletions docs/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"getting-started/generics.md",
"getting-started/strings.md",
"getting-started/arrays.md",
"getting-started/tuples.md",
"getting-started/maps.md",
"getting-started/pattern-matching.md",
"getting-started/destructors.md",
Expand Down
60 changes: 60 additions & 0 deletions docs/source/getting-started/tuples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
{
"title": "Tuples"
}
---

A tuple is a finite collection of values, possibly of different types. Tuples
are created using the syntax `(A, B, ...)` where `A`, `B` and `...` represent
the values stored in a tuple. For example:

```inko
(10, 20, 30)
```

This expression returns a tuple with three values: `10`, `20` and `30`. Unlike
arrays, the values don't need to be of the same type:

```inko
(10, 'hello', true)
```

Tuples are _finite_ meaning that they have a fixed length, unlike arrays which
can grow in size. This means there's no equivalent of `Array.push`, `Array.pop`
and so on.

## Accessing tuple values

Tuple values are accessed using their position, using the syntax `the_tuple.N`
where `N` is the index starting at `0`:

```inko
let pair = (10, 'hello')
pair.0 # => 10
pair.1 # => 'hello'
```

## Tuples are inline types

Tuples are `inline` types and thus are stored on the stack. This means it's not
possible to assign a new value to a tuple:

```inko
let pair = (10, 'hello')
pair.0 = 20 # => not valid, resulting in a compile-time error
```

## Limitations

The tuple syntax is syntax sugar for creating instances of the various tuple
types provided by the `std.tuple` module. For example, the expression
`(10, 'hello')` is syntax sugar for creating an instance of `std.tuple.Tuple2`.
The `std.tuple` module only provides types for tuples with up to 8 values, and
thus tuples can only store at most 8 values.

::: tip
It's highly recommended to avoid creating tuples with more than three values.
Instead, use a custom type when you need to store more than three values.
:::
74 changes: 20 additions & 54 deletions std/src/std/json.inko
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fn char(byte: Int) -> String {
# End users don't really care about the difference between
# RecursionLimitExceeded and InvalidSurrogate(...), nor can they really act
# differently based on the kind of error.
class pub enum ErrorKind {
class pub inline enum ErrorKind {
# The maximum recursion limit is exceeded.
case RecursionLimitExceeded(Int)

Expand Down Expand Up @@ -204,7 +204,7 @@ impl ToString for ErrorKind {
}

# A type describing an error produced while parsing a JSON document.
class pub Error {
class pub inline Error {
# A value describing the kind of error.
let pub @kind: ErrorKind

Expand Down Expand Up @@ -287,7 +287,7 @@ impl ToString for Error {
# and `Query.index` to get the value of an array index. Methods such as
# `Query.as_string` and `Query.as_int` are used to extract the final value as a
# certain type, if such a value is present.
class pub Query {
class pub inline Query {
let @value: Option[ref Json]

# Returns a `Query` that matches the value assigned to the object key `name`,
Expand All @@ -305,12 +305,12 @@ class pub Query {
# Json.Int(42).query.key('name').as_string # => Option.None
# ```
fn pub move key(name: String) -> Query {
@value = match ref @value {
let val = match ref @value {
case Some(Object(v)) -> v.opt(name)
case _ -> Option.None
}

self
Query(val)
}

# Returns a `Query` that matches the value assigned to the array index
Expand All @@ -325,12 +325,12 @@ class pub Query {
# Json.Int(42).query.index(0).as_int # => Option.None
# ```
fn pub move index(index: Int) -> Query {
@value = match ref @value {
let val = match ref @value {
case Some(Array(v)) -> v.opt(index)
case _ -> Option.None
}

self
Query(val)
}

# Returns the value `self` matches against if it's a `Bool`.
Expand Down Expand Up @@ -440,7 +440,7 @@ class pub Query {
}

# A JSON value, such as `true` or an array.
class pub enum Json {
class pub inline enum Json {
case Int(Int)
case Float(Float)
case String(String)
Expand Down Expand Up @@ -539,55 +539,21 @@ impl FormatTrait for Json {

impl Equal[ref Json] for Json {
fn pub ==(other: ref Json) -> Bool {
match self {
case Int(lhs) -> {
match other {
case Int(rhs) -> lhs == rhs
case _ -> false
}
}
case Float(lhs) -> {
match other {
case Float(rhs) -> lhs == rhs
case _ -> false
}
}
case String(lhs) -> {
match other {
case String(rhs) -> lhs == rhs
case _ -> false
}
}
case Array(lhs) -> {
match other {
case Array(rhs) -> lhs == rhs
case _ -> false
}
}
case Object(lhs) -> {
match other {
case Object(rhs) -> lhs == rhs
case _ -> false
}
}
case Bool(lhs) -> {
match other {
case Bool(rhs) -> lhs == rhs
case _ -> false
}
}
case Null -> {
match other {
case Null -> true
case _ -> false
}
}
match (self, other) {
case (Int(a), Int(b)) -> a == b
case (Float(a), Float(b)) -> a == b
case (String(a), String(b)) -> a == b
case (Bool(a), Bool(b)) -> a == b
case (Array(a), Array(b)) -> a == b
case (Object(a), Object(b)) -> a == b
case (Null, Null) -> true
case _ -> false
}
}
}

# A numeric value that's either an `Int` or a `Float`.
class pub enum Number {
class pub inline enum Number {
case Int(Int)
case Float(Float)
}
Expand All @@ -613,7 +579,7 @@ impl FormatTrait for Number {

# A type describing what kind of value is located at the current position in an
# input stream.
class pub enum Type {
class pub inline enum Type {
case Array
case Bool
case Null
Expand Down Expand Up @@ -1361,7 +1327,7 @@ class pub Rule[T: mut + Read] {
# obj.int('b', fn (v) { nums.push(v) }).optional
# obj.parse(parser).or_panic('failed to parse the JSON')
# ```
class pub ObjectParser[T: mut + Read] {
class pub inline ObjectParser[T: mut + Read] {
# The keys to parse along with the closures that parse their values.
let @rules: Map[String, Rule[T]]

Expand Down
2 changes: 1 addition & 1 deletion std/src/std/option.inko
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import std.fmt (Format, Formatter)
#
# An `Option` is is either a `Some` containing a value, or a `None` that doesn't
# contain a value.
class pub enum Option[T] {
class pub inline enum Option[T] {
# A value of type `T`.
case Some(T)

Expand Down
2 changes: 1 addition & 1 deletion std/src/std/result.inko
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import std.fmt (Format, Formatter)
import std.iter (Iter)

# A type that represents either success (`Ok(T)`) or failure (`Error(E)`).
class pub enum Result[T, E] {
class pub inline enum Result[T, E] {
# The case and value for a successful result.
case Ok(T)

Expand Down
7 changes: 3 additions & 4 deletions std/src/std/tuple.inko
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
# triple.2 # => 5.4
# ```
#
# When accessing values using thes methods, the value is returned as a
# reference. If you want to destructure a tuple, you can do so using pattern
# matching:
# When accessing values using these methods, the value is returned as a borrow.
# If you want to destructure a tuple, you can do so using pattern matching:
#
# ```inko
# match (10, "foo", 5.4) {
Expand All @@ -36,7 +35,7 @@
# # Limitations
#
# Tuples are limited up to 8 values. If you need to store more than 8 values,
# it's recommended to use a custom class instead. If you _really_ want to use
# it's recommended to use a custom type instead. If you _really_ want to use
# tuples you can always nest them:
#
# ```inko
Expand Down
7 changes: 5 additions & 2 deletions types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1429,13 +1429,16 @@ impl Class {
}

fn tuple(name: String) -> Self {
Self::new(
let mut cls = Self::new(
name,
ClassKind::Tuple,
Visibility::Public,
ModuleId(DEFAULT_BUILTIN_MODULE_ID),
Location::default(),
)
);

cls.storage = Storage::Inline;
cls
}

fn is_generic(&self) -> bool {
Expand Down

0 comments on commit fe867a6

Please sign in to comment.