Files
2026-07-28 01:17:34 +01:00

173 lines
6.0 KiB
Markdown

# Classes and objects
Chloride has "a proper class and inheritance system" (per the project README) — classes
are real objects, supporting inheritance and introspection.
## Defining a class
```
class name do
this.__init__(self) = do
term.log("hello world")
term.log(name) # the class itself
term.log(name()) # calling it constructs an instance; prints "hello world"
```
(from [`class_test.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/class_test.ar))
Inside a class body, `this.method_name(self, ...) = do ... ` defines an instance method.
The first parameter is conventionally named `self` and refers to the instance, similar to
Python.
## Inheritance
Pass the parent class in parentheses after the class name:
```
class test(name) do
this.wow(self) = do
term.log(self)
test().wow()
```
(from [`class_test.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/class_test.ar)) — `test` inherits from
`name`.
## Public vs. private members
Members defined with `this.member = ...` are **public** — accessible from outside the
class. Members defined with a plain `let` inside the class body are **private** — only
accessible from within the class's own methods:
```
class some_public_private_statics do
this.some_public = "hello im public"
let some_private = "hello im private"
this.get_the_private(self) = do
return some_private
term.log(some_public_private_statics.some_public) # OK — public
term.log(some_public_private_statics.some_private) # errors — private, not accessible
```
(from [`class_test.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/class_test.ar), which explicitly comments
that accessing the private member from outside `# will error`)
## Static vs. instance members
`this.some_public` in the example above is a **static/class-level** member (accessed on
the class itself, `some_public_private_statics.some_public`, without needing an instance).
Instance data is normally set inside `__init__` via `self.attr = value`:
```
class custom_range do
this.__init__(self, stop) = do
self.stop = stop
this.__iter__(self) = custom_range_iterator(self.stop)
```
(from [`custom_range.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/custom_range.ar))
## The constructor: `__init__`
`__init__` runs when the class is called like a function:
```
class something do
this.__init__(self, x) = do
self.x = x
this.f(self) = do
term.log(self, self.x, self.x = self.x + 1)
let instance = something(0)
instance.f() # logs the instance, its x, and increments x
```
(from [`import_test.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/import_test.ar), showing that an
assignment like `self.x = self.x + 1` is itself an expression that returns the assigned
value — this is why it can appear as a `term.log(...)` argument.)
## Operator overloading (dunder methods)
Classes can customise how they behave with built-in operators and syntax by implementing
"dunder" (double-underscore) methods. These are exactly how the built-in types (`number`,
`string`, `array`, etc.) implement their own behaviour, so you can make your own types work
just as naturally.
| Method | Triggered by |
|---|---|
| `__init__(self, ...)` | calling the class, e.g. `MyClass(args)` |
| `__string__(self)` | converting to a string / `term.log` |
| `__getitem__(self, key)` | `obj[key]` |
| `__setitem__(self, key, value)` | `obj[key] = value` |
| `__contains__(self, item)` | `item in obj` |
| `__iter__(self)` | starting a `for` loop over `obj` |
| `__next__(self)` | each step of a `for` loop, on an iterator object |
| `__equal__` / `__not_equal__` | `==` / `!=` |
| `__less_than__` / `__less_than_equal__` / `__greater_than__` / `__greater_than_equal__` | `<`, `<=`, `>`, `>=` |
| `__add__` / `__subtract__` / `__multiply__` / `__division__` / `__floor_division__` / `__modulo__` / `__exponent__` | `+`, `-`, `*`, `/`, `//`, `%`, `**` |
| `__negation__` | unary `-` |
| `__hash__` | using the object as a dictionary key |
| `__name__` | a class-level display name (used by exception types, for example) |
### Example: custom subscript / slice handling
```
class x do
this.__getitem__(self, key) = key
this.__setitem__(self, key, value) = key
term.log(x()[1:2, 2])
term.log(x()[1:2, 2] = 1)
```
(from [`slice.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/slice.ar))
### Example: making a class iterable
```
class custom_range_iterator do
this.__init__(self, stop) = do
self.current = 0
self.stop = stop
this.__next__(self) = do
if (self.stop <= self.current) do
throw StopIteration()
let result = self.current
self.current = self.current + 1
return result
class custom_range do
this.__init__(self, stop) = do
self.stop = stop
this.__iter__(self) = custom_range_iterator(self.stop)
```
(from [`custom_range.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/custom_range.ar)) — see
[`for` loops and iterators](../control-flow/for/README.md) for how this plugs into `for`.
## Introspection
- `type(obj)` returns an object's class/type.
- `is_instance(obj, SomeType)` checks whether `obj` is an instance of `SomeType` (or a
subclass).
```
term.log(type(5)) # number
term.log(is_instance(5, number)) # true
```
You can also assign to class-level attributes dynamically through `type()`:
```
term.log(type(some_public_private_statics_instance).some_public = 10)
```
(from [`class_test.ar`](https://git.wbell.dev/Open-Argon/Chloride/src/commit/8e1ee06b88200ab112cf34e49efe969030ea435c/tests/class_test.ar))
## Next
- [Errors and exceptions](../error-handling/README.md)