Skip to content

Commit

Permalink
Merge pull request #258 from Morpho-lang/type
Browse files Browse the repository at this point in the history
Multiple Dispatch
  • Loading branch information
softmattertheory committed Jun 1, 2024
2 parents 6434493 + 2af0d5a commit d5485a2
Show file tree
Hide file tree
Showing 132 changed files with 3,813 additions and 276 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/NoNanBoxing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: No NANBoxing

on:
push:
branches: [ "main" ]
branches: [ "main", "dev" ]
pull_request:
branches: [ "main" ]
branches: [ "main", "dev" ]

jobs:
build:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Build

on:
push:
branches: [ "main" ]
branches: [ "main", "dev" ]
pull_request:
branches: [ "main" ]
branches: [ "main", "dev" ]

jobs:
build:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/buildandtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Build and Test

on:
push:
branches: [ "main" ]
branches: [ "main", "dev" ]
pull_request:
branches: [ "main" ]
branches: [ "main", "dev" ]

jobs:
build:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nonanboxing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: No NANBoxing

on:
push:
branches: [ "main" ]
branches: [ "main", "dev" ]
pull_request:
branches: [ "main" ]
branches: [ "main", "dev" ]

jobs:
build:
Expand Down
34 changes: 34 additions & 0 deletions benchmark/Metafunction/metafunction.morpho
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Call a meta function


fn f(Int x) { return x }

fn f(Float x) { return x }

fn f(String x) { return x }

fn g(x) { return x }

var a = "Hello"

var n = 10000000

var start = System.clock()
for (i in 1..n) {
g(1)
g(2.0)
g(a)
}
var end = System.clock()

print "Regular function ${end-start}"

var start = System.clock()
for (i in 1..n) {
f(1)
f(2.0)
f(a)
}
var end = System.clock()

print "Metafunction ${end-start}"
2 changes: 1 addition & 1 deletion examples/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

sys.path.append('../test')
ext = "morpho"
command = 'morpho5'
command = 'morpho6'
stk = '@stacktrace'
err = '@error'

Expand Down
20 changes: 20 additions & 0 deletions help/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ See also `Object`.

[showsubtopics]: # (subtopics)

## Methods
[tagmethods]: # (methods)

Classes in morpho can define *methods* to manipulate the objects defined by the class. Like functions, multiple implementations can be defined that accept different parameter types [see also topic: `signature`]:

class Foo {
a(List x) { print "A list!" }
a(String x) { print "A string!" }
a(Matrix x) { print "A matrix!" }
}

Having created an object with the class,

var x = Foo()

the correct implementation is selected at runtime:

x.a([1,2]) // expect: A list!
x.a("Hello") // expect: A string!

## Is
[tagis]: # (is)

Expand Down
65 changes: 65 additions & 0 deletions help/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ Once a function has been defined you can evaluate it like any other morpho funct

print sqr(2)

Functions can accept optional parameters:

fn fun(x, quiet=true ) { if (!quiet) print "Loud!" }

Morpho functions can also be defined to restrict the type of accepted parameters:

fn f(List x) { print "A list!" }

Multiple implementations can be defined that accept different numbers of parameters and parameter types:

fn f() { print "No parameters!" }
fn f(String x) { print "A string!" }
fn f(Tuple x) { print "A tuple!" }

The correct implementation is then selected at runtime:

f("Hello World!") // expect: A string!

[show]: # (subtopics)

## Variadic
Expand Down Expand Up @@ -57,6 +75,8 @@ Each optional parameter must be defined with a default value (here `1`). The fun
func() // a == 1 due to default value
func(a=2) // a == 2 supplied by the user

Note that optional parameters may not be typed.

## Return
[tagreturn]: # (return)

Expand All @@ -69,6 +89,51 @@ The `return` keyword is used to exit from a function, optionally passing a given

by returning early if `n<2`, otherwise returning the result by recursively calling itself.

## Signature
[tagsignature]: # (signature)

The *signature* of a function is a list of the types of arguments in its definition:

fn f(x) {} // Accepts one parameter of any type
fn f(x,y) {} // Accepts two parameters of any type
fn f(List x, y) {} // The first parameter must be a list
fn f(List x, List y) {} // Both parameters must be lists

While you can define multiple implementations of a function that accept different parameter types, you can only define one implementation with a unique signature.

Note that optional and variadic parameters are not typed.

# Multiple dispatch
[tagmultiple]: # (multiple)
[tagdispatch]: # (dispatch)
[tagmultipledispatch]: # (multipledispatch)

Morpho supports *multiple dispatch*, whereby you can define several implementations of a function that accept different types:

fn f(List x) { return "Accepts a list" }
fn f(String x) { return "Accepts a string" }

Morpho chooses the appropriate implementation at runtime:

f([1,2,3]) // Selects the List implementation

Any classes you define can be used as types:

class A {}
class B is A {}
class C is B {}

fn f(A x) { return "A" }
fn f(B x) { return "B" }

Morpho selects the *closest match*, so that:

print f(A()) // expect: A
print f(B()) // expect: B
print f(C()) // expect: B

Class `C` inherits from both `B` and `A`, but because it directly inherits from `B`, that's the closer match.

# Closures
[tagclosures]: # (closures)
[tagclosure]: # (closure)
Expand Down
42 changes: 15 additions & 27 deletions modules/graphics.morpho
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ class TriangleComplex {
self.transmit = transmit
}

accept(visitor, out) {
visitor.visittrianglecomplex(self, out)
}
}

/** Draws a cylinder
Expand Down Expand Up @@ -178,9 +175,6 @@ class Cylinder {
return TriangleComplex(vertices, normals, col, conn)
}

accept(visitor, out) {
visitor.visitcylinder(self, out)
}
}

var _sphere = [ PolyhedronMesh(
Expand Down Expand Up @@ -257,9 +251,6 @@ class Sphere {
return TriangleComplex(v, normals, col, conn)
}

accept(visitor, out) {
visitor.visitsphere(self, out)
}
}

/** Draws an arrow
Expand Down Expand Up @@ -371,9 +362,6 @@ class Arrow {
return TriangleComplex(vertices, normals, col, conn)
}

accept(visitor, out) {
visitor.visitarrow(self, out)
}
}

/** Draws a tube
Expand Down Expand Up @@ -517,9 +505,6 @@ class Tube {
return TriangleComplex(vertices, normals, col, conn, filter=self.filter, transmit=self.transmit)
}

accept(visitor, out) {
visitor.visittube(self, out)
}
}

/* **************************************
Expand Down Expand Up @@ -564,9 +549,6 @@ class Text {
return m.transpose()
}

accept(visitor, out) {
visitor.visittext(self, out)
}
}

class Font {
Expand Down Expand Up @@ -646,11 +628,21 @@ class Show {
out.write("W \"${graphic.title}\"")
}

/* The visit method for a generic `item`.

Here, we define this separate `visitgeneric` method that performs the intended generic task, and have the actual generic `visit` method call `visitgeneric`. This enables calls to the generic fallback from *within a specific implementation*.
For example, the `visit` method for a `Sphere` object can call `self.visitgeneric(item, out)` to visit the `TriangleComplex` representation of the sphere, but calling `self.visit(item, out)` would result in an infinite loop.
*/
visitgeneric(item, out) {
self.visittrianglecomplex(item.totrianglecomplex(), out)
self.visit(item.totrianglecomplex(), out)
}

visitsphere(item, out) {
// Will be used by Cylinder, Arrow and Tube
visit(item, out) {
self.visitgeneric(item, out)
}

visit(Sphere item, out) {
var n = item.refinementlevel()

if (item.color) {
Expand All @@ -670,10 +662,6 @@ class Show {
out.write("d ${self.spheres[n].id}")
}

visitcylinder(item, out) { self.visitgeneric(item, out) }
visitarrow(item, out) { self.visitgeneric(item, out) }
visittube(item, out) { self.visitgeneric(item, out) }

trianglecomplexobjectdata(item, out) {
var x = item.position
var n = item.normals
Expand Down Expand Up @@ -718,7 +706,7 @@ class Show {
}
}

visittrianglecomplex(item, out) {
visit(TriangleComplex item, out) {
var id = self.uid()
out.write("o ${id}")
self.trianglecomplexobjectdata(item, out)
Expand Down Expand Up @@ -795,7 +783,7 @@ class Show {
return self.addfont(font, size, out)
}

visittext(item, out) {
visit(Text item, out) {
var font = item.font
if (!font) font = self.defaultfontname()
var id = self.findfontid(font, item.size, out)
Expand All @@ -819,6 +807,6 @@ class Show {
write(graphic, out) {
self.preamble(graphic, out)

for (item in graphic.displaylist) item.accept(self, out)
for (item in graphic.displaylist) self.visit(item, out)
}
}
16 changes: 8 additions & 8 deletions modules/povray.morpho
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class POVRaytracer {
return arg
}

visittext(item, out) {
visit(Text item, out) {
var arg = self.optionalarg(item)
out.write("text {" +
" ttf \"cyrvetic.ttf\" \"${item.string}\" 0.1, 0 \n" +
Expand All @@ -85,7 +85,7 @@ class POVRaytracer {
out.write(str)
}

visitsphere(item, out) {
visit(Sphere item, out) {
var arg = self.optionalarg(item)
out.write("sphere {"+
" ${self.vector(item.center)}, ${item.r}"+
Expand All @@ -94,7 +94,7 @@ class POVRaytracer {
"} } }")
}

visitcylinder(item, out) {
visit(Cylinder item, out) {
var radius = 0.5*(item.end - item.start).norm()*item.aspectratio
var arg = self.optionalarg(item)
out.write("cylinder {"+
Expand All @@ -104,7 +104,7 @@ class POVRaytracer {
"} } }")
}

visitarrow(item, out) {
visit(Arrow item, out) {
var dx = (item.end - item.start).norm()
var radius = 0.5*dx*item.aspectratio
var cylend = item.start + (item.end - item.start)*(1-item.aspectratio)
Expand All @@ -121,11 +121,11 @@ class POVRaytracer {
"} } }")
}

visittube(item, out) {
self.visittrianglecomplex(item.totrianglecomplex(), out)
visit(Tube item, out) {
self.visit(item.totrianglecomplex(), out)
}

visittrianglecomplex(item, out) {
visit(TriangleComplex item, out) {

var arg = self.optionalarg(item)

Expand Down Expand Up @@ -197,7 +197,7 @@ class POVRaytracer {
"up <0,1,0> right <-1.33,0,0> angle ${self.viewangle}"+
"look_at ${self.vector(self.look_at)} sky ${self.vector(self.sky)} }")

for (item in self.graphic.displaylist) item.accept(self, out)
for (item in self.graphic.displaylist) self.visit(item, out)

var shadowless = ""
if (self.shadowless) shadowless = " shadowless"
Expand Down
4 changes: 2 additions & 2 deletions src/build.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
* Version
* ********************************************************************** */

#define MORPHO_VERSIONSTRING "0.6.0"
#define MORPHO_VERSIONSTRING "0.6.1"

#define MORPHO_VERSION_MAJOR 0
#define MORPHO_VERSION_MINOR 6
#define MORPHO_VERSION_PATCH 0
#define MORPHO_VERSION_PATCH 1

/* **********************************************************************
* Paths and file system
Expand Down
Loading

0 comments on commit d5485a2

Please sign in to comment.