Interfaces, Structs and Classes
As in the case with other low-level, Julia and Golang store collection of fields in a data structure called structs.
Dart, Python and JavaScript use classes. A class is a template definition of the methods and variables in a particular kind of object.

Let's define a collection of fields, Vertex, for two values, X and Y, both integers while observing the respective data structure:
go
js
dart
python
julia
package main
import "fmt"

type Vertex struct {
    X int
    Y int
    // if fields are of same type
    // they could be written as
    // X, Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(v)
}
class Vertex {
    X: number
    Y: number
    constructor(X: number, Y: number){
        this.X = X
        this.Y = Y
    }
    toString(){
        return `{${this.X} ${this.Y}}`;
    }
}

let v: Vertex = new Vertex(1, 3)
console.log(v) // Vertex { X: 1, Y: 3 }
console.log(`${v}`) // using string literals in log prints toString,  {1 3}
class Vertex {
    int x;
    int y;
    Vertex(this.x, this.y);

    @override
    String toString() => "{$x $y}";
}

void main() {
    var v = Vertex(1, 3); 
    print(v); // {1 3}
}
class Vertex:
    def __init__(self, X: int, Y: int) -> None:
        self.X = X 
        self.Y = Y
    def __str__(self) -> str:
        return "{%d %d}" %(self.X, self.Y)

v = Vertex(1, 3)
print(v)
struct Vertex
    X::Int
    Y::Int
end

Base.show(io::IO, vert::Vertex) = print(io, "{$(vert.X) $(vert.Y)}")

v = Vertex(1, 3)
println(v)
Struct fields are accessed using a dot (the dot notation)
v_x = v.X

# To update a value, use the syntax below
v.X = 34
In Julia, structs are immutable. To make them mutable, you have to add mutable to the struct
mutable struct Vertex
    X::Int
    Y::Int
end
Let's now see how to initialize classes with default values:
go
js
dart
python
v1 = Vertex{1, 2}  // all fields initialized
v2 = Vertex{X: 1}  // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
class Vertex {
    X: number
    Y: number
    constructor({X = 0, Y = 0} = {}){
        this.X = X
        this.Y = Y
    }
}
// let v: Vertex = new Vertex({ Y: 78 })
class Vertex {
    int x;
    int y;
    Vertex({this.x = -1, this.y = 0});
}
//var v = Vertex(x:7, y:15);
class Vertex:
    def __init__(self, X: int = 0, Y: int = 0) -> None:
        self.X = X 
        self.Y = Y

# v = Vertex(Y=5)
Methods
A method in object-oriented programming (OOP) is a procedure associated with a message and an object. An object consists of state data and behavior; these compose an interface, which specifies how the object may be utilized by any of its various consumers. A method is a behavior of an object parametrized by a consumer.

Go does not have classes. However, you can define methods on types. A method is a function with a special receiver argument. The receiver appears in its own argument list between the func keyword and the method name.
go
js
dart
python
julia
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}
class Vertex {
    X: number
    Y: number
    constructor({X = 0, Y = 0} = {}){
        this.X = X
        this.Y = Y
    }

    Abs(): number {
        return Math.sqrt(this.X ** 2 + this.Y ** 2)
    }

    toString(){
        return `{${this.X} ${this.Y}}`;
    }
}
let v: Vertex = new Vertex({ X: 8, Y: 15})
console.log(v.Abs())
import 'dart:math';

class Vertex {
// final does not allow modification 
// of value after initialization
final int x;
final int y;
Vertex({this.x = 0, this.y = 0});


double Abs() {
    return sqrt(pow(x, 2) + pow(y, 2));
}
@override
String toString() => "{$x $y}";
}

void main() {
    var v = Vertex(x:8, y:15);
    print(v.Abs());
}
from math import sqrt, pow

class Vertex:
    def __init__(self, X: int = 0, Y: int = 0) -> None:
        self.X = X 
        self.Y = Y
    
    def __str__(self) -> str:
        return "{%d %d}" %(self.X, self.Y)
    
    def Abs(self) -> float:
        return sqrt(pow(self.X, 2) + pow(self.Y, 2))


v = Vertex(X=8, Y=15)
print(v.Abs())
mutable struct Vertex
    X::Int
    Y::Int
end

function Abs(vert::Vertex)::Float64
    return sqrt(vert.X ^ 2 + vert.Y ^ 2)
end

Base.show(io::IO, vert::Vertex) = print(io, "{$(vert.X) $(vert.Y)}")

v = Vertex(8, 15)

println(Abs(v))
Structs in Julia without mutable is like declaring variables in Dart class with final
Methods continued
In Golang, you can declare a method on non-struct types, too. In this example we see a numeric type MyFloat with an Abs method.
You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).
package main

import (
    "fmt"
    "math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}
Pointer Receivers
In Go, methods with pointer receivers can modify the value to which the receiver points (as Scale does here below). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
In other languages, passing an object/struct as a parameter to a function actually 'passes it as reference', so modifying the fields will change the value in the passed struct/object
go
js
dart
python
julia
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

// Value Receiver
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// Pointer Receiver
func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
    v.Scale(5)
    fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
class Vertex {
    X: number
    Y: number
    constructor({X = 0, Y = 0} = {}){
        this.X = X
        this.Y = Y
    }

    Abs(): number {
        return Math.sqrt(this.X ** 2 + this.Y ** 2)
    }

    Scale(f: number): void {
        this.X = this.X * f
        this.Y = this.Y * f
    }

    toString(){
        return `{${this.X} ${this.Y}}`;
    }

}

let v: Vertex = new Vertex({ X: 3, Y: 4})
console.log(`Before scaling: ${v}, Abs: ${v.Abs()}`)
v.Scale(5);
console.log(`After scaling: ${v}, Abs: ${v.Abs()}`)
import 'dart:math';

class Vertex {
    double x;
    double y;
    Vertex({this.x = 0, this.y = 0});


    double Abs() {
        return sqrt(pow(x, 2) + pow(y, 2));
    }

    void Scale(double f) {
    x = x * f;
    y = y * f;
    }
    @override
    String toString() => "{$x $y}";
}

void main() {
    var v = Vertex(x:3, y:4);
    print("Before scaling: $v, Abs: ${v.Abs()}");
        v.Scale(5);
    print("After scaling: $v, Abs: ${v.Abs()}");
}
from math import sqrt, pow

class Vertex:
    def __init__(self, X: int = 0, Y: int = 0) -> None:
        self.X = X 
        self.Y = Y
    
    def __str__(self) -> str:
        return "{%d %d}" %(self.X, self.Y)
    
    def Abs(self) -> float:
        return sqrt(pow(self.X, 2) + pow(self.Y, 2))

    def Scale(self, f: float) -> None: 
        self.X  = self.X * f 
        self.Y = self.Y * f

v = Vertex(X = 3, Y = 4)

print(f"Before scaling: {v}, Abs: {v.Abs()}")
v.Scale(5)
print(f"After scaling: {v}, Abs: {v.Abs()}")
module Fourier

mutable struct Vertex
    X::Int
    Y::Int
end

function Abs(vert::Vertex)::Float64
    return sqrt(vert.X ^ 2 + vert.Y ^ 2)
end

function Scale(vert::Vertex, f::Float64)
    vert.X = vert.X * f 
    vert.Y = vert.Y * f
end

Base.show(io::IO, vert::Vertex) = print(io, "{$(vert.X) $(vert.Y)}")

v = Vertex(3, 4)

println("Before scaling: $v, Abs: $(Abs(v))")
Scale(v, convert(Float64, 5))
println("After scaling: $v, Abs: $(Abs(v))")
end
go
js
dart
python
julia
            [Golang]$ go run test.go 
Before scaling: {X:3 Y:4}, Abs: 5
After scaling: {X:15 Y:20}, Abs: 25
[Golang]$
          
Rewriting the methods Abs and Scale as functions in Go, they will be as below:
func Abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func Scale(v *Vertex, f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}
Methods and Pointer indirection
In Golang, functions with a pointer argument must take a pointer, while methods with pointer receivers take either a value or a pointer as the receiver when they are called. The equivalent thing happens in the reverse direction. Functions that take a value argument must take a value of that specific type:
var v Vertex
fmt.Println(AbsFunc(v))  // OK
fmt.Println(AbsFunc(&v)) // Compile error!
The codebase below shows a method taking both a value and a pointer and working ok.
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
// In this case, the method call p.Abs() 
// is interpreted as (*p).Abs().
There are two reasons to use a pointer receiver.
~ The first is so that the method can modify the value that its receiver points to.
~ The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.
In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.

Note: If you want to print struct with its field names, use %+v in fmt.Prinf
Interfaces
An interface or protocol type is a data type describing a set of method signatures, the implementations of which may be provided by multiple classes that are otherwise not necessarily related to each other. A class which provides the methods listed in a protocol is said to adopt the protocol, or to implement the interface.
Interfaces in Julia are very much different from the other languages.
go
js
dart
python
julia
package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}
type MyFloat float64
type Vertex struct {
    X, Y float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser
    fmt.Println(a.Abs())
    a = &v // a *Vertex implements Abser
    fmt.Println(a.Abs())

    /* In the following lines, v is a Vertex (not *Vertex)
    and does NOT implement Abser.
    a = v
    fmt.Println(a.Abs()) // Error
    */
}

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
interface Abser {
    Abs(): number
}

/* To use Abser interface for object literals 
you have to extend Abser interface */
interface MyFloat extends Abser {
value: number 
}
/* OR
type MyFloat = Abser & {
value: number 
} */

class Vertex implements Abser {
X: number
Y: number
constructor({X = 0, Y = 0} = {}){
    this.X = X
    this.Y = Y
}
Abs(): number {
    return Math.sqrt(this.X ** 2 + this.Y ** 2)
}
toString(){
    return `{${this.X} ${this.Y}}`;
   }
}

var f: MyFloat = {
value : -Math.sqrt(2),
Abs(): number {
    if (this.value < 0) {
    return -this.value
    }
    return this.value
}
};

let v: Vertex = new Vertex({ X: 3, Y: 4})
console.log(v.Abs())
console.log(f.Abs())
import 'dart:math';

class Abser {
    double Abs() => 0;
}

class Vertex implements Abser {
    final double x;
    final double y;
    Vertex({this.x = 0, this.y = 0});

    @override
    double Abs() {
        return sqrt(pow(x, 2) + pow(y, 2));
    }

    @override
    String toString() => "{$x $y}";
}

void main() {
    var v = Vertex(x:3, y:4);
    print(v.Abs());
}
from abc import ABC, abstractmethod

from math import sqrt, pow

class Abser(ABC):
    @abstractmethod
    def Abs(self) -> float:
        pass


class Vertex(Abser):
    def __init__(self, X: int = 0, Y: int = 0) -> None:
        self.X = X 
        self.Y = Y
    
    def __str__(self) -> str:
        return "{%d %d}" %(self.X, self.Y)
    
    def Abs(self) -> float:
        return sqrt(pow(self.X, 2) + pow(self.Y, 2))

v = Vertex(X = 3, Y = 4)

print(v.Abs())
module Fourier
#= We'll talk about installing third-party packages
in the lesson Custom Packages. You can skip to it 
first if you like
=#
# You must call the line below within module, else functions won't be accessible
using Statistics

struct Squares
    count::Int
end

#= The loop below won't work until
we define a method iterate from the 
Base.iterate for the struct Square =#
Base.iterate(S::Squares, state=1) = state > S.count ? nothing : (state*state, state+1)

for i in Squares(7)
    println(i)
end

println(25 in Squares(10))
println(mean(Squares(100))) # mean from Statistics
#= for collect to work, we need to define 
two methods for the struct Squares =#
Base.length(S::Squares) = S.count
#=
Defining the method above, we are to able to 
call collect. However the vector formed will 
have type Any. We know the values of Iterable 
will always be int so we can constrain the type 
to int with the line below =#
Base.eltype(::Type{Squares}) = Int
#=The result will be vector of type int=#
println(collect(Squares(4)))
# This would be it with interfaces for now
end
The empty interface
In Golang, the interface type that specifies zero methods is known as the empty interface. An empty interface may hold values of any type. (Every type implements at least zero methods.) Empty interfaces are used by code that handles values of unknown type. For example, fmt.Print takes any number of arguments of type interface{}
package main

import "fmt"

func main() {
    var i interface{}
    describe(i)

    i = 42
    describe(i)

    i = "hello"
    describe(i)
}

func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}
            [Golang]$ go run test.go
(<nil>, <nil>)
(42, int)
(hello, string)
[Golang]$
          
Type Assertions
A type assertion provides access to an interface value's underlying concrete value.
t := i.(T)
This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.
If i does not hold a T, the statement will trigger a panic.
To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.
t, ok := i.(T)
If i holds a T, then t will be the underlying value and ok will be true.
If not, ok will be false and t will be the zero value of type T, and no panic occurs.
package main

import "fmt"

func main() {
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)

    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64)
    fmt.Println(f, ok)

    f = i.(float64) // panic
    fmt.Println(f)
}
            [Golang]$ go run test.go
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64

goroutine 1 [running]:
main.main()
        /Users/siliconSavanna/Projects/Golang/test.go:17 +0x14c
exit status 2
[Golang]$
          
Type switches
A type switch is a construct that permits several type assertions in series.
A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value
package main

import "fmt"

func do(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }
}

func main() {
    do(21)
    do("hello")
    do(true)
}
            [Golang]$ go run test.go
Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!
[Golang]$
          
Stringers
One of the most ubiquitous interfaces is Stringer defined by the fmt package.
type Stringer interface {
    String() string
}
A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values.
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person{"Arthur Dent", 42}
    z := Person{"Zaphod Beeblebrox", 9001}
    fmt.Println(a)
    fmt.Println(z)
}
            [Golang]$ go run test.go
Arthur Dent (42 years)
Zaphod Beeblebrox (9001 years)
[Golang]$
          
Inheritance
Inheritance enables you to define a class that takes all the functionality from a parent class and allows you to add more. Using class inheritance, a class can inherit all the methods and properties of another class. Inheritance is a useful feature that allows code reusability.

In the codebase below, we have a (base) class Television. It is just a normal class so to say. Now, we'd like to borrow all it's attributes (variables) and methods and use them in another class, SmartTelevision. We don't want to rewrite all this since it would be verbose code and unconventional hence just inherit them. Hence we extend the class Television to SmartTelevision

Now, we have access to turnOn method and attribute brand in Base class Television from our new class SmartTelevision

The super in the constructor is used to access the Base attributes
js
dart
python
julia
class Television { 
    brand: string
    constructor(brand: string) {
        this.brand = brand;
    }

    turnOn() {
        console.log(`TV Brand: ${this.brand}`);
    }
}

class SmartTelevision extends Television{
    memory: string
    constructor(brand: string, memory: string) {
        super(brand)
        this.memory = memory
    }
}

let smartTV = new SmartTelevision("LG Nanocell", "128GB")
smartTV.turnOn()
void main(){
    var smartTV = SmartTelevision("LG NanoCell", "128GB");
    smartTV.turnOn();
}

class Television { 
    final String brand;
    Television(this.brand);
    /* works with named arguments too
    * Television({required this.brand});
    */
    void turnOn() {
        print("TV Brand: ${brand}");
    }
}

class SmartTelevision extends Television{
    final String memory;
    /* Before Dart 2.17
    * SmartTelevision(String brand, this.memory) : super(brand);
    */
    // New way
    SmartTelevision(super.brand, this.memory);
    /* For named arguments
    * SmartTelevision({required super.brand, required this.memory});
    */
}
class Television:
    def __init__(self, brand):
        self.brand = brand
    
    def turnOn(self):
        print(f"TV Brand: {self.brand}")
        

class SmartTelevision(Television):
    def __init__(self, brand, memory):
        super().__init__(brand)
        self.memory = memory

smartTV = SmartTelevision("LG NanoCell", "128GB")
smartTV.turnOn()
module Fourier

abstract type BaseTV end

mutable struct Television <: BaseTV
    brand::String
    year::Int64
end

mutable struct SmartTelevision <: BaseTV
    tv::Television
    memory::String
end

function turnOn(genericTV::BaseTV)
    brand = isa(genericTV, Television) ? genericTV.brand : genericTV.tv.brand
    println("TV Brand: $brand")
end

tv = Television("LG Nanocell", 2022)
smartTV = SmartTelevision(Television("LG Smart Nanocell", 2022), "128GB")
turnOn(tv)
turnOn(smartTV)

end