#248 Closure Expression and Function Type syntax

tompalmer Fri 20 Jun 2008

Another controversial subject from me. I hope to stop soon, before I wear out my welcome. But I think these (breaking) changes could help improve consistency in Fan.

I'll start with some examples of Fan code and potential alternative forms (including the notion mentioned on the roadmap of planning to introduce implicit types on closure expressions):

["ant", "bear"].all |Str v->Bool| {return v.size >= 3}
["ant", "bear"].all |Str v| {return v.size >= 3}
["ant", "bear"].all |v| {return v.size >= 3}

And here's my recommended syntax (though it risks using do differently than Java, but I like that better than ECMAScript's function or shorter forms like fun):

["ant", "bear"].all do(Str v) {return v.size >= 3} // Return type always implied?
["ant", "bear"].all do(v) {return v.size >= 3}
["ant", "bear"].all do(v) v.size >= 3 // Single-expression syntax omits return.

It's two chars longer than the Ruby-esque | notation, but it looks more C-like for a more consistent syntax, and the increased parseability allows for the single-expression syntax (something adapted from ES4 plans). Also do() {...} seems more obvious than |,| {...}.

And then function type syntax needs to change to look more like normal methods, too. Instead of this:

Bool all(|V, Int -> Bool| c)

... you'd have this:

Bool all(Bool(V, Int) c)

Without parameters it would look like ReturnType(), and expressions like Int().type should be fine. Until semantic analysis, you don't know the type, but that's already true with things like Int.type. The AST should work out, I think. Not that I've coded any of this to verify.

And as my usual disclaimer, I don't think things will live or die over this, but I think it will make the system as a whole more coherent. Just some thoughts.

helium Fri 20 Jun 2008

Instead of this:

Bool all(|V, Int -> Bool| c)

... you'd have this:

Bool all(Bool(V, Int) c)

Just: NO!

Maybe "(V, Int) -> Bool" or whatever but please not the C-functionpointer syntax. It quickly get's very ugly if you want to pass higher order functions to other functions or stuff like that.

And for the lambda-syntax ... I'm OK, with both the smalltalk-like |...| or your do(...) or maybe ?... ;)

edit: I didn't enter a questionmark but rather the unicode lambda symbol, but it seems not to be stored correctly here :(

andy Fri 20 Jun 2008

@helium - looks like a bug in Sidewalk - in the meantime you should be able to use an XML char ref:

ƛ

JohnDG Fri 20 Jun 2008

I think the closure syntax is fine, although I wonder, if the parameters are not needed, why use || at all? Similarly, if you have only one line, why are the curly braces needed? Throw away those requirements and you can come close to implementing, if, while, and other constructs using closures alone. Ideally, there would be no control structures, just functions, as in some functional languages.

if(a > b).then doX

if(a > b).then {
   doX
}.else {
   doY
}

The first example is a function called if that accepts a closure returning Bool, which returns a function called then which accepts another closure, etc.

andy Fri 20 Jun 2008

@tom

The problem with a JavaScript-esque style is how to model the return type. I'm not crazy about the current syntax - but I think the Type(Type,Type..) syntax is a bit confusing given its similarities b/w a method call and declaration. I think the |Type,Type->Type| syntax sets the code off cleanly right now, but I'm definitely open to other suggestions.

@JohnDG

I think it might make the code a bit more cryptic if you axed the || for no-arg closures:

foo.bar(x) { doSomethingCool() }
foo.bar(x) |,| { doSomethingCool() }

Its minor, and since Fan doesn't support anonymous types, its clearly a closure, assuming the parser could handle that. Though I'd still side on the |,| syntax as I think it improves code readability and makes it consistent with closures that do have arguments.

Using closures purely vs control structures is quite interesting. Though I think thats too big a jump to take, given Fan's role as an evolution to Java/C# vs an entirely new language.

cbeust Fri 20 Jun 2008

On a related note, what do you guys think of Groovy's convention to implicitly call the unique parameter of a closure it?

["ant", "bear"].all {return it.size >= 3}

I've found that this syntax covers quite a few cases, improves readability quite a bit and is fairly unambiguous.

tompalmer Fri 20 Jun 2008

I'm behind on this conversation. Here are my thoughts:

  • Is (for example) Int()(Int(Str), Obj) hard to read as a type name than ||Str -> Int|, Obj| -> |-> Int||? I think common cases would be OK, but I guess that's a question worth answering.
  • I think there are three options for return types with do:
    1. Always let return type be implicit. I really think this might be OK, or at least it doesn't matter that it's ugly if it's rarely seen.
    2. Use do Type(Type arg, Type arg) {...}.
    3. Use (following helium) do (Type arg, Type arg) -> Type {...}.
  • I've toyed with an ANTLR grammar for a C-like (or actually more Scala-like) language that has no built-in control structures but rather relies entirely on closures with nice multipart method schemas for things like if/elseIf*/else rather than call chaining. I think Smalltalk is like this (if I understand correctly, since I don't know it well), just not with C-like syntax. But I agree that that all-out style isn't Fan. However, if there's no conflict with Fan constructor blocks (for setting field values), then I'd still love to see no do()/|,| required. It's not general to make all pretty control structures, but I think it's OK and not too hard to grok.
  • I like Groovy's it (and better than the multi-param _ in Scala), but only if nested closures aren't allowed. Otherwise it can get ambiguous. In my toy grammar, I used an indicator to say I wanted an implicit arg: {^ return it.size >= 3}. But that looks cryptic, so I'd love to see it with a no-nested closure limitation.

JohnDG Fri 20 Jun 2008

@andy

I agree it may be too big a step for Fan.

I like using closures instead of control structures for two main reasons:

Firstly, because the less magic you have to use -- that is, the more of the language that can be implemented in itself -- the simpler and more elegant the grammar and the resulting code, and the more expressive the language is.

And secondly, Ruby's closures, as bulky as they are, are increasingly being used for custom control structures (for example, testing DSLs). But since they were designed with that end in mind, the syntax is quite ugly and unlike normal control structures (for one example, look at Dave Thomas' attempt to squeeze a custom while loop into the latest version of Ruby: http://pragdave.blogs.pragprog.com/pragdave/2008/05/new-lambda-synt.html).

brian Sat 21 Jun 2008

Until semantic analysis, you don't know the type, but that's already true with things like Int.type. The AST should work out, I think. Not that I've coded any of this to verify.

Actually Fan already uses a two pass parser - the first pass is super quick and used to find all the "using" statments and "class Foo" and "mixin Bar" definitions. Once we have those we can do the real parser pass, but we can unambiguously resolve identifiers to types at parse time.

I think the closure syntax is fine, although I wonder, if the parameters are not needed, why use || at all?

This was the original design. But we have to require |,| so that we could free up expr {...} to mean with-blocks. Either one or the other needs to use a different syntax. Although I hate the |,| - we just haven't come up with anything better.

Similarly, if you have only one line, why are the curly braces needed?

I think I need the curly braces if we ever support closure type interface because I can scan for "| {". I run into a lot of problems supporting "|" as a bitwise OR. I floated the idea of loosing the C bitwise operators to help with the grammar but I got shot down. It's open for debate though.

On a related note, what do you guys think of Groovy's convention to implicitly call the unique parameter of a closure it?

I've thought about it, but not sure I'm ready to take that plunge. My thoughts have tended more to full type inference on closures.

And here's my recommended syntax (though it risks using do differently than Java, but I like that better than ECMAScript's function or shorter forms like fun):

I like do for a closure, but I don't like the proposal for function signatures. What I like about the current syntax is the consistency between the type signature and the "literal":

Str[] list := ["a", "b"]
Str:Str map := ["a:"b"]
|Int a->Bool| func := |Int a->Bool| { return a == 0 }

I don't feel I get that with the do proposal. One of the things about designing languages is that you really wish a standard keyboard had more symbols. The way Fan is designed is we do "grouping":

  • {} type blocks, control blocks, with blocks
  • () evaluation grouping, calling
  • [] list/map literals, list/map get/set accessors
  • || function types, closure params

My issue with () is that those symbols are used so heavily in normal code, that they don't work well to set-off function types and closures in Fan's C like code. Put another way I like that () is used for calling and that || is used for declaring.

harishkswamy Sat 21 Jun 2008

Here again -1 to the new proposal, I like current Fan's syntax except for the |,| for no args which seems a little ugly. May be something as simple as changing the comma to another character might make it better if its required to appease the parser.

tompalmer Sat 21 Jun 2008

Actually, () is used for declaring when it comes to methods, and that's as least as common (and earlier in the learning phase) than closures and function types. But I can live either way.

Login or Signup to reply.