Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider reworking keyword arguments using an "undefined" type #244

Closed
yorickpeterse opened this issue Jul 15, 2019 · 2 comments
Closed
Labels
compiler Changes related to the compiler

Comments

@yorickpeterse
Copy link
Collaborator

Summary

Keyword arguments currently use a hash map to map the names to their corresponding
positional arguments. Instead of using a hash map, the compiler should generate a list
of positional arguments. Optional arguments not provided will be set to an "undefined"
value.

Motivation

Mapping keyword argument names to their positional arguments is expensive, and complicates
the code used for running blocks. This complexity affects all method calls to a certain
degree, even when no keyword arguments need to be looked up.

Using "undefined" values allows us to fill up unspecified optional parameters, removing
the need for hash map lookups. For dynamic calls one would have to manually fill up
arguments, or pass the appropriate values. This will make dynamic calls more cumbersome,
but they are likely to be used rarely due to Inko's gradually typed nature. This could
also be made easier by having the compiler take care of this at runtime somehow.

In short: this setup will speed up method calls, without having to change the code
generated for optional arguments.

Implementation

The "undefined" type is a NULL pointer in the VM. Registers and locals are zeroed
out, removing the need for writing any additional values to set them to the "undefined"
type by default. This means we don't have to change the code generated for optional
arguments, and we only have to change LocalExists to use something like
if local.is_undefined() instead of if local.is_null().

The compiler will have to generate a list of positional arguments. For example, this:

def test(foo, bar = 10, baz = 20) {}

foo(10, baz: 30)

Would be translated into this:

def test(foo, bar = 10, baz = 20) {}

foo(10, undefined, 30)

For dynamic method calls (= sending a message to Dynamic) we would have to disallow
the use of keyword arguments. Allowing this would result in arguments being set
incorrectly, without this being clear to the developer. This means that this code is
no longer valid:

let x = 'foo' as Dynamic

x.slice(length: 2, start: 0)

This would be invalid because slice is defined as slice(start: Integer, length: Integer) -> String,
and in this new setup the arguments would be mapped incorrectly.

Drawbacks

Dynamic method calls no longer support keyword arguments, and the Ruby compiler would have to be adjusted.

@yorickpeterse
Copy link
Collaborator Author

An entirely different approach is to make argument names part of the message name, and remove optional arguments. This would be similar to Smalltalk. To illustrate, consider the following method:

def slice(start: Integer, length: Integer) {}

Right now the message name is slice. In the new setup, the message name would be slice:start:length: (or something to that extend). Optional arguments would be
implemented using method overloading:

def slice(start: Integer, length: Integer) {}
def slice(start: Integer) {}
def slice(length: Integer) {}

The benefit of this approach is no runtime overhead for optional arguments, as we no longer need to check if they are given a value. This setup is also useful when your method's implementation differs a lot based on the presence of an optional arguments.

A downside is that it might be more difficult to read the code, as the code for a message is now spread across multiple methods. Another downside is that keyword arguments can no longer be passed in arbitrary order, but I don't find this a very useful feature to begin with.

Because init methods can now be defined multiple times, the compiler would have to somehow ensure they all set the same instance variables. This likely requires two passes over the list of init methods:

  1. One pass to gather all unique names of the defined instance variables.
  2. One pass to verify all init methods to see if they set all these variables.

@yorickpeterse
Copy link
Collaborator Author

This is done in 0619de8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler Changes related to the compiler
Projects
None yet
Development

No branches or pull requests

1 participant