⬅ 🏠 Home
Immutability, which allows us to change a value of a variable, brings with it great flexibility. In certain situations however, this flexibility may not be desirable and can be abused. Therefore, we want to be able to define something as immutable, meaning that it cannot be changed.
When a variable is immutable however, it should truly be immutable. Often I see that even when a variable is declared immutable, it is still possible to modify it’s internal fields.
We specify whether
self is mutable in the signature of a method.
This means that methods which modify the internals of an object, can only be called if said object is mutable.
Also notice that for
self, we do not need to specify the type.
stateful MyStateful def mut field <- 10 # can only be called if self is mutable def function_1(mut self) => self.field <- 20 # we can assign because self is mutable def function_2(self) => print "This doesn't modify anything, but we can still read internal fields: [field]."
function_1 specifies that self must be mutable for it to be called.
function_2 may not modify the fields of
self is not mutable.
We also make a distinction between State and Statelessness, using
Statlessness is often seen in functional programming languages.
It also allows us to enfore injectivity:
forall f, x, y: x = y -> f(x) = f(y), as is the case with (pure) functional languages.
Take the following:
stateful MyStateful def mut called_before <- false def f(x: Int): Int => if called_before then x * 2 else x * 3
Now we call the same method twice, and we see that it returns two different values:
def my_stateful <- MyStateful() my_stateful.f(10) # retuns 20 my_stateful.f(10) # return 30
Ensuring that something has no state allows us to enforce the above rule, as the same function or method will always return the same value for the same input.
stateless, we cannot have mutable top level definitions, which allows use to enforce this rules as its internal state can never change:
stateless MyStateless def f(x: Int): Int => x * 2
Now we always get the same value:
def my_stateless <- MyStateless() my_stateless.f(10) # returns 20 my_stateless.f(10) # still returns 20
A class describes the properties of an instance of that class. A class encapsulates data and contains definitions. A definition is either a value, or an immutable method. A definition may be private.
An instance of a class may be created. A class may not inherit from another class, but may have another class as a property, and forward its methods. A class may implement a type. It must then either implement the methods described in the type, or contain an instance of a class that forwards these methods.
In certain situations, we want to make sure that certain methods can only be called when an instance of a class is in a certain state. This can be achieved using type aliases and type refinement.
Say I have a type
type Server def connected: Boolean def ip_address: IPAddress def mut last_message: String def connect: (IPAddress) -> Boolean throws [ConnectionErr] def last_sent_message: () -> String def send_message: (String) -> Boolean throws [ConnectionErr] def disconnect: () -> Boolean type ServerErr(msg: String) isa Err(msg)
We can do the following:
type ConnectedHTTPServer isa HTTPServer when self connected else ServerErr("Not connected.") type DiconnectedHTTPServer isa HTTPServer when self not connected else ServerErr("Already connected.") stateless HTTPServer def stateless_message => "This message is always the same." stateful HTTPServer(mut self: DisconnectedHTTPServer, def ip_address: IPAddress) isa Server def connected <- false def mut last_message <- undefined def last_sent_message(self): String => self last_message # self must be disconnected def connect (mut self: DisconnectedHTTPServer, ip_address: IPAddress): Boolean => # perform some operations here self connected <- true true # self must be mutable to send message def send_message(mut self: ConnectedHTTPServer, message: String): Boolean => # perform some operations here self last_message <- message true # self must be connected to disconnect def disconnect(mut self: ConnectedHTTPServer): Boolean => # perform some operations here self connected <- false true