amuletml / amulet

An ML-like functional programming language
https://amulet.works/
BSD 3-Clause "New" or "Revised" License
324 stars 14 forks source link

Best way to represent a Lua type that both has "static" functions and "instance" functions? #290

Closed s5bug closed 3 years ago

s5bug commented 3 years ago

I think a simplest useful example might be Vector2 from Core. How should this look as a binding in Amulet?

SquidDev commented 3 years ago

I'm writing this on mobile, so please forgive the inevitable formatting mistakes. I guess I'd do something like:

type t 

external val zero : t = "Vector2.ZERO"

external val lerp : t -> t -> float -> t = "Vector2.Lerp"

external val normalise : t -> t = "function(x) return x:GetNormalized()"

The optimiser/backend should be smart enough to inline the last one. Hopefully.

s5bug commented 3 years ago

Ah, this makes sense. What'd be a good way to go about "namespacing" this stuff in amulet? I'm assuming amulet wouldn't support something like

type vector2

external val vector2.zero : vector2 = "Vector2.ZERO"

?

Also, how do I get the x, y, size, sizeSquared values of the Vector?

SquidDev commented 3 years ago

I'd typically do something like this:

module Vector2 = struct
  type t

  external val zero : t = "Vector2.ZERO"
end

Then one just writes Vector2.zero. Alternatively you can split it into separate files, and then just do module Vector = import "./vector2.ml".

As far as getting fields goes, it's probably safer to write the accessors as functions (rather than declaring the field as a record). So something like:

external val x : t -> float = "function(v) return v.x end"
s5bug commented 3 years ago

How do I handle operators?

module Vector2 = struct
  type t

  external val zero : t = "Vector2.ZERO"
  external val one : t = "Vector2.ONE"

  external val new : float -> float -> t = "Vector2.New"
  external val new : float -> t = "Vector2.New"
  external val new : t -> t = "Vector2.New"

  external val lerp : t -> t -> float -> t = "Vector2.Lerp"

  external val get_normalized : t -> t = "Vector2.GetNormalized"

  external val ( + ) : t -> t -> t = "function(a, b) return a + b end"
  external val ( + ) : t -> float -> t = "function(a, b) return a + b end"
  external val ( - ) : t -> t -> t = "function(a, b) return a - b end"
  external val ( - ) : t -> float -> t = "function(a, b) return a - b end"
  external val ( * ) : t -> t -> t = "function(a, b) return a * b end"
  external val ( * ) : t -> float -> t = "function(a, b) return a * b end"
  external val ( * ) : float -> t -> t = "function(a, b) return a * b end"
  external val ( / ) : t -> t -> t = "function(a, b) return a / b end"
  external val ( / ) : t -> float -> t = "function(a, b) return a / b end"
  external val negate : t -> t = "function(a) return -a end"
  external val ( *. ) : t -> t -> float = "function(a, b) return a .. b end"
  external val ( *+ ) : t -> t -> float = "function(a, b) return a ^ b end"

As of current I can't represent these as infix operators, but I don't know how to handle them otherwise.

( * ) is of special concern: Vector2.( * ) only matches the last definition and not the other two.