![alt text](https://raw.githubusercontent.com/timmolderez/classy-structs/master/priv/classy_structs.png "Classy structs logo")
*Classy structs* provides object-oriented features, such as inheritance and polymorphism, on top of Elixir's structs.
You can use this tiny macro library to define your own classes with fields and methods:
```Elixir
use Class
defclass Animal do
var speed:10
var weight:10
def sound(_this) do
"..."
end
end
```
Once a class is instantiated, you'll find that class instances are actually just structs:
```Elixir
iex> a = Animal.new()
%Animal{speed: 10}
```
However, these structs have a few more tricks up their sleeve:
```Elixir
use Class
defclass Dog do
extends Animal # Inheritance
var breed: ""
var bark: "Woof!"
var weight: 30 # Overriding fields
def new(b) do # Constructor
%Dog{breed: b}
end
def sound(this) do # Overriding methods
this.bark
end
end
iex> d = Dog.new("Greyhound")
%Dog{breed: "Greyhound", bark: "Woof!", weight: 30, speed: 10}
iex> Animal.sound(d) # Static dispatch
"..."
iex> Animal~>sound(d) # Dynamic dispatch
"Woof!"
```
To learn more about everything *Classy structs* has to offer, go ahead and have a look at the [Usage](#usage) section.
## Installation
*Classy structs* can be installed by adding it to your project's dependencies in the `mix.exs` file:
```elixir
def deps do
[
{:classy_structs, "~> 0.9"}
]
end
```
## <a id="usage"></a>Usage
[Basic classes](#basic)<br />
[Methods](#methods)<br />
[Constructors](#constructors)<br />
[Inheritance](#inheritance)<br />
[Static and dynamic method calls](#calls)<br />
[Super calls](#super)<br />
[Abstract methods](#abstract)
### <a id="basic"></a>Basic classes
`defclass` is used to define a class. The following is an example of a simple class definition:
```Elixir
use Class
defclass Rectangle do
var width: 20
var height: 10
def surface(this) do
this.width * this.height
end
def scale(this, factor) do
%{this | width: this.width * factor, height: this.height * factor}
end
end
```
This example defines the `Rectangle` class. It has two fields `width` and `height`. Fields and their initial value are defined using `var`.
The class also has two methods `surface` and `scale`. These are defined like any other Elixir function, using `def`.
To construct a `Rectangle` instance, call the `new` method:
```Elixir
iex> r = Rectangle.new()
%Rectangle{width: 20, height:10}
```
Note that class instances are structs. This is important to keep in mind because:
* It implies that, like all Elixir data structures, class instances are immutable.
* It implies that all fields are public.
* The syntax for [accessing and updating fields](https://elixir-lang.org/getting-started/structs.html#accessing-and-updating-structs) is reused.
Just to show how close classes are to structs, the `Rectangle` example actually expands to:
```Elixir
defmodule Rectangle do
defstruct width: 20, height: 10
def surface(this) do
this.width * this.height
end
def scale(this, factor) do
%{this | width: this.width * factor, height: this.height * factor}
end
def new(), do: %Rectangle{}
end
```
### <a id="methods"></a>Methods
Methods in *Classy structs* are just plain Elixir functions. There only is the convention that the class instance should be passed explicitly as the first parameter. (Unlike e.g. Java or C++, there is no implicit `this`.)
The only construct in *Classy structs* that relies on this convention is [dynamic dispatch](#calls).
You can also leave out the class instance parameter to mimic a "static method".
### <a id="constructors"></a>Constructors
A constructor can be defined by adding a `new` function:
```Elixir
defclass Dog do
var species: ""
var name: ""
def new(s) do
%Dog{species: s}
end
end
```
If no constructors are defined, a default one (without parameters) is automatically generated.
Given that a class instance is a struct, you're of course free to directly create your struct/instance instead of using constructors.
### <a id="inheritance"></a>Inheritance
*Classy structs* supports multiple inheritance. To make use of it, include an `extends` construct in your class definition that lists one or more superclasses.
Class without a superclass:
```Elixir
defclass Animal do
var legs: 4
var weight: 0
def sound(_this), do: "..."
end
```
Class with one superclass:
```Elixir
defclass Dog do
extends Animal
var weight: 30
var bark: "Woof!"
def sound(this), do: this.bark
end
defclass Cat do
extends Animal
var weight: 4
end
iex> d = Dog.new()
%Dog{legs: 4, weight: 30, bark: "Woof!"}
iex> c = Cat.new()
%Cat{legs: 4, weight: 4}
iex> Dog.sound(d)
"Woof!"
iex> Cat.sound(c)
"..."
```
As you can see, all inherited fields are included in class instances.
Class with multiple superclasses:
```Elixir
defclass PuppyCat do
extends Cat, Dog
var breed: "Maine Coon"
end
iex> p = PuppyCat.new()
%PuppyCat{legs: 4, weight: 4, bark: "Woof!", breed: "Maine Coon"}
iex> PuppyCat.sound(p)
"..."
```
When listing multiple superclasses, the order of these superclasses matters: if there are multiple superclasses that define the same field or method, the first one wins.
Finally, as class instances are just structs, you can also use a struct (that wasn't defined using `defclass`) as a superclass. This may come in useful whenever you'd like to extend structs from existing Elixir code.
### <a id="calls"></a>Static and dynamic method calls
The semantics of the `.` operator remain unchanged. A method call using `.` corresponds to calling a named function:
```Elixir
# (reusing the Dog and Cat examples of the previous section)
d = Dog.new()
c = Cat.new()
iex> Dog.sound(d)
"Woof!"
iex> Animal.sound(d)
"..."
iex> Cat.sound(c) # Inherited function
"..."
```
In other words, the `.` operator performs static dispatch. To call methods using dynamic dispatch (which is how e.g. Java method calls work), the `~>` operator should be used. Dynamic dispatch figures out which method to call by looking at the type of the first argument:
```Elixir
a = Animal.new()
d = Dog.new()
iex>Animal~>sound(a)
"..."
iex>Animal~>sound(d)
"Woof!"
```
### <a id="super"></a>Super calls
Because it is possible to make static method calls, there is no need for an additional construct for "super calls". For example:
```Elixir
defclass Text do
var msg
def toString(this), do: this.msg
end
defclass BoldText do
extends Text
def toString(this), do: "<b>" <> Text.toString(this) <> "<b>"
end
```
The `Text.toString(this)` call is a super call.
### <a id="abstract"></a>Abstract methods
Using the `@abstract` attribute, it is possible to define abstract methods. These are methods without a body; they only specify the method's interface. Any class that extends a superclass with abstract methods must provide an implementation for all abstract methods:
```Elixir
defclass Animal do
@abstract sound(Animal) :: String.t
end
defclass Dog do
extends Animal
var bark: "Woof!"
def sound(this), do: this.bark
end
```
Note that abstract methods also provide a type specification. Elixir's [typespecs notation](https://hexdocs.pm/elixir/typespecs.html) is used for this specification.
If a subclass does not implement an abstract method, this produces a warning:
```Elixir
iex>defclass Cat do
...> extends Animal
...>end
warning: function sound/1 required by behaviour Animal is not implemented (in module Cat)
```
## Limitations
There are a few things to watch out for when using *Classy structs*:
* Fields cannot be initialized to anonymous functions using `var`. However, you can always update a field of an existing class instance to an anonymous function.
* A class and its superclass must be defined in seperate context. That is, they can't both be defined in the same file (unless the subclass is nested a another module). This is because, in Elixir, one module cannot depend on another module that is defined in the same context.
* If a class uses multiple inheritance, and two of its superclasses define the same abstract method, this produces a warning. This is because abstract methods are implemented using Elixir's @callback and @behaviour attributes. (When a module implements two behaviours, it produces this warning when both behaviours define the same callback.)