AdeptLanguage / Adept

The Adept Programming Language
GNU General Public License v3.0
123 stars 9 forks source link

Virtual methods? #64

Closed vtereshkov closed 2 years ago

vtereshkov commented 2 years ago

It looks like all polymorphic types in Adept are resolved at compile time. In other words, they are equivalent to C++ templates. Does Adept support virtual methods and run time polymorphism?

Consider a standard example: draw all the entities from a List of Shapes, some of which are Rectangles and others are Circles, according to the user's input. Any Shape can be draw()n, but with different method implementations for Rectangles and Circles. How to write this in Adept?

IsaacShelton commented 2 years ago

Currently, not really, but this is definitely a feature that should exist.

This is a glaringly obvious gap in the language, so I'll put high priority on developing this feature so it will be in the next release


The current solutions to this kind of problem are not very great. The closest you can get to something like this is with explicit v-tables or enums.

Explicit V-Tables

import basics
import Vector2f

// ----------- ShapeVTable -----------
record ShapeVTable (f_defer, f_render ptr)

func makeShapeVTable(_ *$T) ShapeVTable {
    return ShapeVTable(
        func null &__defer__(*$T) as ptr,
        func &render(*$T) as ptr
    )
}

// ----------- Shape -----------
struct Shape (vtable ShapeVTable) {
    func destroyAndFree {
        if this.vtable.f_defer != null {
            defer_function func(ptr) void = this.vtable.f_defer
            defer_function(this)
        }
        delete this
    }

    func render {
        render_function func(ptr) void = this.vtable.f_render
        render_function(this)
    }
}

// ----------- Rectangle -----------
struct Rectangle (struct Shape, size Vector2f) {
    func render {
        print("rectangle with size " + this.size.toString())
    }
}

func Rectangle(w, h float) *Shape {
    rect *Rectangle = new Rectangle
    rect.vtable = makeShapeVTable(rect)
    rect.size = vector2f(w, h)
    return rect as *Shape
}

// ----------- Circle -----------
struct Circle (struct Shape, radius float) {
    func render {
        print("circle with radius " + this.radius)
    }
}

func Circle(radius float) *Shape {
    circle *Circle = new Circle
    circle.vtable = makeShapeVTable(circle)
    circle.radius = radius
    return circle as *Shape
}

// ----------- main -----------
func main {
    shapes <*Shape> List

    defer {
        each *Shape in shapes, it.destroyAndFree()
    }

    shapes.add(Rectangle(4.0, 5.0))
    shapes.add(Circle(9.0))

    each shape *Shape in shapes {
        shape.render()
    }
}

C-Style Enum Approach

import basics
import Vector2f

enum ShapeKind (RECTANGLE, CIRCLE)
struct RectangleData (size Vector2f)
struct CircleData (radius float)

struct Shape (
    kind ShapeKind,
    union (
        rectangle RectangleData,
        circle CircleData
    )
) {
    func render {
        exhaustive switch this.kind {
        case ShapeKind::RECTANGLE
            print("rectangle with size " + this.rectangle.size.toString())
        case ShapeKind::CIRCLE
            print("circle with radius " + this.circle.radius)
        }
    }
}

func Rectangle(w, h float) Shape {
    shape POD Shape
    shape.kind = ShapeKind::RECTANGLE
    shape.rectangle.size = vector2f(w, h)
    return shape
}

func Circle(radius float) Shape {
    shape POD Shape
    shape.kind = ShapeKind::CIRCLE
    shape.circle.radius = radius
    return shape
}

func main {
    shapes <Shape> List

    shapes.add(Rectangle(4.0, 5.0))
    shapes.add(Circle(9.0))

    each shape Shape in shapes {
        shape.render()
    }
}
IsaacShelton commented 2 years ago

Resolved!

Proper classes and virtual dispatch added in Adept 2.7

import basics

class Shape () {
    constructor {}

    virtual func draw {}
}

class Rectangle extends Shape (w, h float) {
    constructor(w, h float) {
        this.w = w
        this.h = h
    }

    override func draw {
        printf("Rectangle %f by %f\n", this.w, this.h)
    }
}

class Circle extends Shape (radius float) {
    constructor(radius float) {
        this.radius = radius
    }

    override func draw {
        printf("Circle with radius %f\n", this.radius)
    }
}

func main {
    shapes <*Shape> List

    defer {
        each *Shape in shapes, delete it
    }

    shapes.add(new Rectangle(4.0, 5.0) as *Shape)
    shapes.add(new Circle(9.0) as *Shape)

    each *Shape in shapes {
        it.draw()
    }
}