A solution to inheritance
I’ve had some more thought about my programming language “Compost”. It might have its own solution to the old problems of object oriented programming, mainly class inheritance.
One of the problems class inheritance tries to solve is code reuse between classes. In OOP, you’d solve this by creating a superclass which contains the methods which are shared between classes. The classes that use those method then need to inheritd from that super class. Using class inheritance comes with many problems, and is not seen as an ideal solution by many people today.
I want to show an example of the classic “Shapes” library that is often used to explaing object oriented programming, but implemented in Compost. Read the comments as you go through the code comments to see what Compost offers.
mod Vec2
class
x: Float
y: Float
traits
X: Float
Y: Float
Multiply: (factor: Float) -> Vec2
Add: (other: Vec2) -> Vec2
defs
X: x
Y: y
Multiply: Vec2(X * factor, Y * factor)
Add: Vec2(X + other.X, Y + other.Y)
# Converts it to a Point. The Point trait is auto-declared by the Point module
.Point: Point(X, Y)
# For 'public' fields I might short-hand this:
mod Vec2
class
x: Float
traits
X: Float
defs
X: x
# To this: (the uppercase first character would auto-define all of the above)
mod Vec2
class
X: Float
# A type of Vec2 with the same x and y. By defining the X and Y traits of Vec2,
# it auto-defines all other Vec2 traits. You can use Vec2 and DiagonalVec2 instances
# completely interchangeably because they have the same interfaces of traits.
mod DiagonalVec2
class
value: Float
defs
Vec2
X: value
Y: value
mod Point
class
x: Float
y: Float
traits
X: Float
Y: Float
Translate: (offset: Vec2) -> Point
defs
X: x
Y: y
Translate: Point(x + offset.X, y + offset.Y)
.Vec2: Vec2(x, y)
# A function to create a 'diagonal' point. This would work the same as
# creating a whole DiagonalPoint class, since a class is just used a function.
let DiagonalPoint(value: Float): DiagonalVec2(value).Point
# Shape declares some traits but doesn't define them. It works like an interface.
mod Shape
traits
Center: Point
Area: Float
Perimeter: Float
# Rectangle declares some traits but doesn't define all of them.
# It defines some of Shape's traits using those declarations.
mod Rectangle
traits
TopLeft: Point
BottomRight: Point
Size: Vec2
defs
# Just to make clear we purposely haven't defined this: ? means undefined.
TopLeft: ?
BottomRight: TopLeft.Translate(Size)
Size: ?
# We define Shape's traits here. So if any class defines Rectangle's traits,
# Shape's traits will also be defined for that class.
Shape
Center: Rectangle.TopLeft.Translate(Rectangle.Size.Multiply(0.5))
Area: Rectangle.Size.X * Rectangle.Size.Y
Perimeter: (Rectangle.Size.X + Rectangle.Size.Y) * 2
# Declares but doesn't define a Square.Size trait. Square is another interface.
# It automatically defines the Rectangle.Size trait if the Square.Size trait is defined
mod Square
traits
Size: Float
defs
Size: ?
Rectangle:
Size: Vec2(Square.Size, Square.Size)
# One type of Square class, created by giving a top_left corner and a size.
# This will define all of Square's, all of Rectangle's and all of Shape's traits.
mod TopLeftSquare
class
top_left: Point
size: Float
defs
Square
Size: size
Rectangle
TopLeft: top_left
mod CenterSquare # Another type of Square class
class
center: Point
size: Float
defs
Square
Size: size
Rectangle
Center: center
TopLeft: center.Translate(Rectangle.Size.Multiply(-0.5))
# A constant definition.
let Pi: 3.14159265359
mod Circle
class
center: Point
radius: Float
defs
Shape
Center: center
Area: Pi * radius * radius
Perimeter: 2 * Pi * radius
# A function definition that takes any shape.
let AreaPlusPerimeter(shape: Shape.Area & Shape.Perimeter)
shape.Area + shape.Perimeter
# The main function
let Main
# Creates a square by specifying the top left corner and the size.
# Then returns the Shape.Center trait of it.
TopLeftSquare
top_left: Point(x: 10, y: 10)
size: 100
Center
Defining functions or variables
A thing I wasn’t sure about before is how to define functions and “variables”. I came to the conclusion that those two are really the same, since “variables” won’t change. They are really constants due to the fact that we’re a functional language. When “changing” a “variable” we’re just defining a new constant.
It was difficult to find a common keyword to define a function or a constant. I’ve come to the realisation that we don’t even need a keyword. Functions or constants will be defined like this:
Pi: 3.14
AddOne(x: Int): x + 1
LongerFunction(instance: MyInterface)
instance
Method1
Method2
Method1
Functions can be defined using a semicolon or by adding one or more indented lines below the name.
In the last function I showcase how method chaining is done. We simply put them on the next line.
So how do we define constants inside functions? Like this:
AnotherFunction(instance: MyInterface)
ConstantOne: instance.Method1
ConstantTwo: 3
ConstantThree
instance
Method3(ConstantTwo)
Method1
ConstantOne.Method4(ConstantThree)
To explain: it creates ConstantOne
, which will contain the result of instance.Method1
.
ConstantTwo
is simply 3
.
ConstantThree
contains Method3(3)
called on our instance, and then Method1
called on that.
The function returns ConstantOne
with Method4(ConstantThree)
called on it.
I just realized it might be difficult the notice the difference between chaining methods and defining functions/constants.
I’ll have to find a solution for that. Maybe I should just use let
to define anything. That would look like this:
let AnotherFunction(instance: MyInterface)
let ConstantOne: instance.Method1
let ConstantTwo: 3
let ConstantThree
instance
Method3(ConstantTwo)
Method1
ConstantOne.Method4(ConstantThree)