Skip to content

Instantly share code, notes, and snippets.

@jaz303
Last active August 29, 2015 14:09
Show Gist options
  • Save jaz303/4f4b34a60bf5119f09b9 to your computer and use it in GitHub Desktop.
Save jaz303/4f4b34a60bf5119f09b9 to your computer and use it in GitHub Desktop.

I'm designing a programming language for beginners/kids and am trying to keep the syntax for basic usage as simple as possible, while still providing useful features for more advanced users such as first-class functions and lambda expressions.

In particular I'm keen that it should be possible to call functions without parentheses:

print "hello world"

This turns out to be quite complicated when the language is dynamically typed and supports first-class functions. Given the following:

-- Function definition
-- This is immutable (i.e. any other assignment of foo in this scope
-- is an error)
def foo(a, b, c)
  ...
end

-- This is a lambda
bar := .[ a,b,c| ... ]

When making a call with arguments the same forms are valid for both static functions and lambdas:

foo 1, 2, 3     -- call, without parens
foo(1, 2, 3)    -- call, with parens
bar 1, 2, 3     -- call, without parens
bar(1, 2, 3)    -- call, with parens

The situation is more complicated with no-args because both static functions and lambdas are first-class (i.e. you can reference them without calling).

For static functions, call always wins. Rationale: using functions as first-class values is a relatively advanced feature for beginners so just use call semantics. Bonus: static analysis means this can be identified at compile time so we can just spit out a CALL opcode.

foo     -- call
foo()   -- call

For lambdas:

bar     -- reference
bar()   -- call

Rationale: when dealing with first-class functions, it's common to pass them around, assign etc.

Now, for nested calls, there's a bit of ambiguity. Generally nested calls require parens, so:

foo a(1,2), b(2,3)

But what using no-arg functions:

def baz()
  ...
end

-- are these calls?
foo baz, baz

-- or should this be required?
foo baz(), baz()

2 rules are in conflict here so not sure what to do... for now I think making them calls is the best thing to do.

The only elephant in the room is the situation where you want to take a first-class reference to a statically defined function. e.g.:

def sort(lambda, ary)
  ...
end

def reverse_compare(l, r)
  r - l
end

-- because reverse_compare is a static def'd function this
-- will actually call it, rather than pass it in...
sort(reverse_compare, [1, 2, 3])

To counter this, we introduce the no-call unary operator (&) which forces a reference to the function:

sort(&reverse_compare, [1, 2, 3])

This is a bit messy but I feel it's justified because it's unusual to use static functions as callbacks - much more usual to use lambdas.

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment