Argument checking
Built in functions produce very comprehensible error messages for bad argument types
math.sin("a string that does not belong") -- Bad argument #1 to function 'sin'. Number expected, got string.
However, user-defined functions produce cryptic error messages when fed bad arguments or, sometimes, no error message at all
root = (n, exp)-> n^(1 / exp)
root(16) -- Attempt to perform arithmetic on 'exp' (a nil value).
A moonscript idiom to create well-formatted error messages for bad argument types would be very convenient.
root = (n as number, exp as number)-> n^(1 / exp)
root(16) -- Bad argument #2 to function 'root'. Number expected, got no value.
Also, for methods (fat arrow syntax), the argument index in the error message should be shifted by one, because
class Foo
bar: (a as string)=>
-- method body
Foo!\bar 2
should complain about the first argument to bar, even though, technically, the first argument is 'self'.
It should then support multiple types and have option to turn it off for performance reason (release build e.g.).
Kinda foo = (number a, Class1 | Class2 b) ->
for methods (fat arrow syntax), the argument index in the error message should be shifted by one
This can lead to even more confusion, because self is quite explicit in Lua.
It's better to just use param name instead of index: --Bad argument 'exp' to function 'root'
What about having expressions as types? Then the passed it type would be compared with the result of type or ideally moonscript's type.
http://moonscript.org/reference/standard_lib.html#typevalue
x = (arg as "string") ->
Right now when you ask for the type of a user defined class it returns class object:. So we can reference classes this way:
class Something
y = (a as Something, b as "string") ->
Which would be roughly:
local function Something() end
local function y(a,b)
local _type = moonscript.type
if _type(arg) ~= Something then
error("Bad argument...")
end
if _type(arg) ~= "string" then
error("Bad argument...")
end
end
I don't want to add new keywords unless necessary. Alternative syntax could just be this:
z = ("string" a, Something b) ->
I don't think this syntax collides with anything I want to add in the future.
I very much like the idea of arbitrary expressions. To take the abstraction one step further, how about a 'when' clause much like in a list comprehension or switch statement, for checking properties of arguments automagically.
factorial = (n when n >= 0 and n == math.floor(n)) ->
I agree with Fillest that there should be a flag to disable checking for release versions, for performance reasons.
Yes, some sort of pattern matching has been on my mind for a while. So if I went with that approach it would handle this implicitly. I just haven't though about the syntax yet.
But if there isn't pattern matching, putting that much logic into the argument list is probably not a good idea.
A sort of multimethod system might be good, although the only efficient thing to do would be to dispatch based on type. Something like this maybe?
dot_product = ?> -- ?> being the multimethod symbol (like -> or =>)
Vec2, "number": (a, b)->
-- scalar multiplication
Vec2, Vec2: (a, b)->
-- ...
Mat2, Vec2: (a, b)->
-- ...
Mat2, Mat2: (a, b)->
-- ...
becoming
dot_product = setmetatable({}, {__call = function(self, ...)
types = {}
for i, arg in ipairs{...} do
types[#types + 1] = type(arg)
if types[#types] == "table" and types[#types].__class then
types[#types] = types[#types].__class
end
end
local implementation = self
for i, t in ipairs(types) do
implementation = implementation[t]
if not implementation then
error("Bad argument #" .. i .. " to function dot_product.")
end
end
return implementation(...)
end})
dot_product[Vec2] = {
[Vec2] = function(a, b)
-- handle Vec2 * Vec2
end;
number = function(a, b)
-- handle Vec2 * number
end;
}
dot_product[Mat2] = {
[Mat2] = function(a, b)
-- handle Mat2 * Mat2
end;
[Vec2] = function(a, b)
-- handle Mat2 * Vec2
end;
}
This implementation of multimethods is easily modified by other code. In a Mat3by2 class file:
dot_product[Mat3by2] or= {}
dot_product[Mat3by2][Vec2] = (a, b)-> -- ...
dot_product[Mat3by2][Mat2] = (a, b)-> -- ...
What about reusing the syntax for argument type checking from above, so it could look like this:
func = ?>
(Vec2 a, Vec2 b) ->
--...
(Mat2 a, Vec2 b) ->
--...
(Mat2 a, Mat2 b) ->
--...
(a,b) -> print "default action?"
Reads very well, although it's a little bit conceptually at odds with the class syntax of having methods be fields.
I like the default action
It's just like an array table, just delimited by indentation.
also, it would be nice to have some symbol or word that represents not nil.
"Value"?
No, it conflicts with something that might actually have a type of "Value". And it's too verbose.
"$"?
I'm not sure
x = (? hello, ?world) ->
x = (~ hello, ~world) ->
x = (* hello, *world) ->
x = (! hello, !world) ->
x = ($ hello, $world) ->
x = (& hello, &world) ->
x = (+ hello, +world) ->
x = (| hello, |world) ->
Also, maybe the semantics should be more like Ruby's class statement: creating a multimethod if it doesn't already exist, and extending it if it does.
multi dot_product
(Mat2 a, Vec2 b)-> -- ...
(Vec2 a, Vec2 b)-> -- ...
-- ...
and in Mat3by2:
multi dot_product
(Mat3by2 a, Vec2 b)-> -- ...
(Mat3by2 a, Mat2 b)-> -- ...
Although the ?> syntax is more consistent with the rest of Moonscript.
IMHO a dollar sign is closest to the meaning (something with value, not just nil), but it does make everything look a little Perly.
Underscore for "any type"? It's used for pattern matching in functional languages, and in Lua it means "dummy" or "we don't care what it is, just that it exists," which is somewhat similar.
Ah yeah, underscore is good idea.
more options for not nil:
x = (not nil hello, not nil world) ->
x = (!nil hello, !nil world) ->
That's by far the most readable, although a bit tedious to write. Doesn't introduce any new keywords, which is always good.
Also, just so I don't forget. This type checking should probably also work with class inheritance.
Yes. I've written multi-dispatch systems in the past, and I've found the best way to do that is to have a dispatch function that queries itself recursively. IE if class B extends A and class D extends C, when dispatch(B, D) doesn't find anything, try dispatch(A, D), which in turn tries dispatch(A, C). If neither of those work, the original call will try dispatch(B, C). Once you've found one (or, if you've found multiple, the one that's the "closest" to the original types), cache it and return it.
Also, it should probably be able to handle extra arguments. If all those other dispatch functions failed, try dispatch(B, "..."), then dispatch(A, "..."), and finally, if all else fails, dispatch("...")
This as an extensible mechanic would be very nice (eg compiler hook that controls its meaning would add quite a bit of flexibility).
It seems to me, this is all a lot of complex syntactic overhead for a very specific feature. You should reconsider whether this is necessary. And rather, if it's necessary to add in as a special case, instead of providing facilities for the user to craft something themselves.
For instance, I think Moonscript could benefit in general from a preprocessor and some macro syntax. If implemented with enough flexibility, the user could trivially craft their own macro which the compiler expands into proper verbose type-checking if-statements. The benefit here is, you're not pigeon-holing the user into a type checking interface that might not be as flexible as they desire; and moreover you're still adding more syntactic rules to the language, but to serve a greater purpose than just one feature.
I vote on keeping with the Lua philosophy of simplicity.
I'll have to agree; mainly, I only commented on this because in Lua I'd end up awkwardly marking variables as eg function b(--[[Optional]] a)..., and there's no good way to get enforcement. But yes, I am neutral to how to make this possible.
I'm slightly unsure - though - how a macro facility would make something that looks like type annotations in the function 'signature'.
Here's an example of code possible with LuaMacro:
require_ "proto"
Function bonzo (a: number, b: string) : string
return a .. b
end
print (bonzo(10,"hello"))
print (bonzo("hello")) ---> blows up!
-- test-proto.lua:4 type mismatch argument 1: got string, expecting number
The implementation of Function is here:
https://github.com/stevedonovan/LuaMacro/blob/master/tests/proto.lua
The generated code for that function looks like this:
function bonzo (a,b)
_assert_arg (a ,1,"number");_assert_arg (b ,2,"string");return a .. b
end
LuaMacro works only on the lexical level, and since Moonscript is lexically similar to Lua, it should be possible to use it with Moonscript.