ID:2365701
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
Sparked partially by this thread, http://www.byond.com/forum/?post=2364859, but also due to other issues that not doing this causes. Most of these apply to the maintainability of larger projects.

So, what am I asking for: compile-time analysis of proc calls. Specifically for the following features.
  • The validation of named arguments and the count of provided arguments.
    This would greatly help with the maintenance of large projects. Wherein one major pitfall when it comes to doing refactoring is the fact that you have to manually find and validate all call signatures. While I understand that enforcing things like type-safety doesn't necessarily support BYOND's point of being beginner friendly, I fully believe that this would not have a major effect on the ability to learn the language.

    I am certain that this would also slightly speed up the runtime by lowering the amount of work done before the proc is called.

    Now, the only issue with this would be the use the variadic argument list feature. However, this could easily be remedied in a way similar to what python does: just implement a key-word argument name, "..." for example, which signals to the compiler that, "Validate every arg before this, but stop caring afterwards".
  • Compile-time handling of default argument values.
    Essentially, instead of handling default values in the runtime by basically compiling in a standard if condition, substitute missing default-valued arguments with the default value at the call site. This would mimic behaviour which is common to all major compiled languages (C++, Java) and can thus be expected by analogy. (Heck, even Python handles this as expected.)

    With the data required for the former suggestion, this should be relatively easy to implement in tandem with it. Which is why I'm sort of "packaging" these two together.


So, here are illustrations of what I'd like:
/proc/standard_proc(a, b, c)
world << a
world << b
world << c

/proc/default_valued_proc(a, b, c = 5)
standard_proc(a, b, c)

/proc/variadic_proc(a, ...)
world << a
for (var/i = 2; i < args.len; i++)
world << args[i]

/world/New()
standard_proc(5, 10, 9) // Compiles and works.
standard_proc(8, 3) // Currently compiles. New proposed behaviour: compile error.
standard_proc(2, 4, 6, 9) // Currently compiles. New proposed behaviour: compile error.
standard_proc(3, 4, k = 10) // Currently compiles. Runtimes!
// This is one of the worse offenders IMO, as it completely nukes the call-stack
// that's meant to follow. This should error on compile.

default_valued_proc(4, 5) // Works, should be compiled to a bytecode
// representation which is equivalent to writing:
// default_valued_proc(4, 5, 5)

default_valued_proc(3) // Currently compiles. New proposed behaviour: compile error.
default_valued_proc(3, c = 1) // Currently compiles. New proposed behaviour: compile error.

variadic_proc() // New proposed behaviour: compile error.
variadic_proc(3) // Compiles. Variadics are optional.
variadic_proc(4, 6, 7, 6) // Compiles.
</dm'>
Skull132 wrote:
The validation of named arguments and the count of provided arguments.
This would greatly help with the maintenance of large projects. Wherein one major pitfall when it comes to doing refactoring is the fact that you have to manually find and validate all call signatures. While I understand that enforcing things like type-safety doesn't necessarily support BYOND's point of being beginner friendly, I fully believe that this would not have a major effect on the ability to learn the language.

I am certain that this would also slightly speed up the runtime by lowering the amount of work done before the proc is called.

Now, the only issue with this would be the use the variadic argument list feature. However, this could easily be remedied in a way similar to what python does: just implement a key-word argument name, "..." for example, which signals to the compiler that, "Validate every arg before this, but stop caring afterwards".

That's not the only issue.

First, proc calls can be done in several ways: global, with src but type-checked (. operator), and with src but not type-checked (: operator). The last of those would be utterly unable to do any validation no matter what, because it wouldn't know the type of src in advance.

The others would be extremely iffy. First you have the fact that the argument list can be completely changed in an override proc; that's legal in DM. Second, finding the right node to check for the proc definition is not an easy task. Third, comparing the arguments in the call to that definition would also be on the nightmarish side. These things would get into the compiler's guts in some pretty invasive ways.

Besides that, validating the number of arguments to a user-defined proc is not really compatible with the language. User-defined procs are allowed to go over or under the official argument count, so there's no reason you can't call func(x,y) as func(x,y,z) or func(x).

Compile-time handling of default argument values.
Essentially, instead of handling default values in the runtime by basically compiling in a standard if condition, substitute missing default-valued arguments with the default value at the call site. This would mimic behaviour which is common to all major compiled languages (C++, Java) and can thus be expected by analogy. (Heck, even Python handles this as expected.)

With the data required for the former suggestion, this should be relatively easy to implement in tandem with it. Which is why I'm sort of "packaging" these two together.

I don't see that happening, really. What you're asking for with both pieces of this request is a fundamental change under the hood to the very nature of the language. That's just not possible.

DM is not a language with strict argument definitions on procs, just like it isn't a strictly typed language. Only the built-in procs really enforce things like argument counts, and with only a few exceptions they don't support named arguments. (That's largely because the built-in procs all compile to instruction codes.)

You mentioned C++ and Java, but a better comparison here would be JavaScript. Its function definitions are very loose, allowing for variadic arguments the same way BYOND does it. There are some big differences between the two obviously, one of the bigger ones being that JS has an undefined value that is distinct from null, whereas BYOND recognizes no such thing. And of course JS functions are objects, JS has closures, etc. But the function definition part is basically much the same--as is the fact that JS is a loosely typed language like DM. C++ and Java are much stricter in their function definitions and in their variable types; in fact Java is really just a highly portable C++ written by gotophobes with a fetish for overly complex design patterns.

Your request essentially is akin to asking JavaScript to become Java. They're simply too different.
To put it more loosely, strict type checking can never happen as long as proc's don't define a return type, lists don't declare a type, and untyped vars are allowed to exist.

Another point that alot of devs don't think of:


thing1/proc/dothing()
return 1
thing2/proc/dothing()
return "2"

/proc/gib_thing1
return new thing2
/proc/dothings
var/thing1/thing = gib_thing1
world << "res: thing.dothing()"


That is valid in dm (and even something /tg/ intentionally exploits). at compile time, the compiler thinks the proc call in the last line points to thing1/proc/dothing, but at runtime it points to thing2/proc/dothing, a different proc on a different type