marianoguerra / efene

OFICIAL REPO IS AT https://github.com/efene/efene
https://github.com/efene/efene
Other
145 stars 32 forks source link

Feature request: Function calls without parens #48

Closed DavidMikeSimon closed 12 years ago

DavidMikeSimon commented 12 years ago

This one might be tricky to implement in the parser, but I'd be super pleased if Efene had it. It makes a lot of Ruby code so much more readable, particularly when writing domain-specific languages.

The basic idea is that this:

some_function foo, bar

Should be equivalent to this:

some_function(foo, bar)

This would only work at the statement level in Efene. In Ruby, you can nest paren-less function calls inside other paren-less function calls, although you shouldn't since it is confusing to read. But with Efene, that wouldn't even be possible, because Erlang and Efene syntax doesn't distinguish at the lexing level between a call to a function named x and a symbol/atom named x.

Besides making simple statement calls (particularly common ones like io.format) more readable, this syntax is especially nice because it allows you to create functions that look and act rather like language-level control structures. For example, I could write in Ifene a function to repeat an action X times:

repeat = fn(N, Proc)
  if N <= 0
    ok
  else
    Proc()
    repeat(N-1, Proc)

And use it like this:

repeat 5 fn
  io.format "Intruder Detected!"
DavidMikeSimon commented 12 years ago

Argh, on further thought it occurs to me that this might simply not work, at least for function calls with no arguments. Consider:

foobar = fn()
  ok

Under my proposed syntax, it's ambiguous if this is returning the atom ok or calling a function ok/0.

:-(

marianoguerra commented 12 years ago

yes, it's ambiguous and with yecc can't be done since it generates lot of conflicts,

what I did added is the possibility to declare a function that takes no parameters omitting the parenthesis:

repeat = fn(N, Proc)                                                       
 if N <= 0                                                                 
    ok                                                                     
 else                                                                      
    Proc()                                                                 
    repeat(N - 1, Proc)                                                    

@public                                                                    
run = fn                                                                   
    repeat(5, fn                                                           
        io.format("Intruder Detected! ~n")                                 
    ) 
DavidMikeSimon commented 12 years ago

Cool, thank you for looking into this and making that change, little conveniences add up. :-)

Please allow me to suggest an alternate approach for doing blocks parameters outside parens:

repeat(5) <- fn
  io.format("Intruder Detected!~n")

This is very similar to the -> arrow expression syntax; where -> supplies the prior expression to the following function call as the first argument, <- instead supplies the following expression to the prior function call as the last argument.

DavidMikeSimon commented 12 years ago

Er, on further thought, it might need to be a different operator than <-, since it could be ambiguous in expressions like this one:

somefunc()<-3

Does that compute if the return value of somefunc/0 is less than negative 3, or does it call somefunc(-3)? :-/

Maybe better would be -<

marianoguerra commented 12 years ago

maybe some similar syntax like ruby blocks?

http://www.skorks.com/2009/09/using-ruby-blocks-and-rolling-your-own-iterators/ http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/

marianoguerra commented 12 years ago

maybe something like (imagine lists.map takes the arguments in the reverse order)

lists.map([1,2,3]) fn (X)
    X + 1
DavidMikeSimon commented 12 years ago

Yes, Ruby blocks were exactly what I was thinking of! :-)

I don't like how Ruby uses a special hidden block argument for such things, but their syntax (and the example syntax you provide) is perfect.

marianoguerra commented 12 years ago

here you have :D

https://github.com/marianoguerra/efene/commit/05e48b96fd6b0940804e16122e87e69b5c1482f7

play with them and tell me if there is any problem

marianoguerra commented 12 years ago

note that there is a special case in ifene, if you make a function call and in the next expression you return a fun all by itself then it will be appended as last argument to the last function call

@public                                                                        
run = fn                                                                       
    repeat(5)                                                                  

    fn                                                                         
        io.format("hi there~n")

this is a corner case we should document

DavidMikeSimon commented 12 years ago

Cool, thank you for implementing this!

The corner case you mention may be a good reason to change the syntax to use an operator.

marianoguerra commented 12 years ago

maybe I can change fn for do like ruby (that would add a new reserved word)

DavidMikeSimon commented 12 years ago

Sounds good to me.

marianoguerra commented 12 years ago

changed for do

https://github.com/marianoguerra/efene/commit/fc9f0dc40e2efd71b52da6382cc91c587460512e

DavidMikeSimon commented 12 years ago

I'm getting a problem compiling struct.ifn in a clean checkout; it seems to have a function named 'do', which won't work anymore.

marianoguerra commented 12 years ago

renamed it to do_on_attr in the last commit, thanks

DavidMikeSimon commented 12 years ago

Cool, thanks.

I've found another issue. The following Ifene code fails to parse:

run = fn
  L = lst.map([1,2,3]) do (X)
    X*3
  -> lst.map() do (X)
    X+1
  io.format("~w~n", [L])
  init.stop()

And if I convert it to Efene code, then it outputs the wrong result, printing [3,6,9] instead of [4,7,10]:

@public
run = fn {
  L = lst.map([1,2,3]) do (X) {
    X*3
  } -> lst.map() do (X) {
    X+1
  }
  io.format("~w~n", [L])
  init.stop()
}
marianoguerra commented 12 years ago

about the ifn code, I could remove the new line before -> if it comes after a }

or more generically I could remove the newline before any arrow? an arrow on itself on a new line should always be related to the previous line I guess. you seem to be good finding this corner cases, what do you think?

but it was hard for my brain to parse that too :)

about the fn code, it works for me

mariano@ganesha:~/tmp$ cat thing.fn                                        
@public                                                                    
run = fn  {                                                                
    L = lst.map( [1, 2, 3])do(X) {                                         
        X * 3                                                              
    } ->lst.map()do(X) {                                                   
        X + 1                                                              
    }                                                                      
    io.format("~w~n",  [L])                                                
    init.stop()                                                            
}                                                                          

mariano@ganesha:~/tmp$ fnc -t erl thing.fn                                 
-module(thing).                                                            

-export([run/0]).                                                          

run() ->                                                                   
    L = lst:map(lst:map([1, 2, 3], fun (X) -> X * 3 end),                  
                fun (X) -> X + 1 end),                                     
    io:format("~w~n", [L]),                                                
    init:stop().                                                           

mariano@ganesha:~/tmp$ fnc thing.fn                                        
Compiling thing.fn                                                         

mariano@ganesha:~/tmp$ fnc -r thing run                                    
[4,7,10]                                                                   

mariano@ganesha:~/tmp$  
DavidMikeSimon commented 12 years ago

Hm, one nice general solution would be to remove any newline that immediately precedes a binary operator. That would allow me to use any operator I wanted, not just -> but also:

X = some_function() do
  foobar
+ some_other_function() do
  borknarf

And also the simpler case of:

Y = some_long_expression
+ some_other_long_expression

However, this would be an problem to parse when using operators that are ambiguously binary or unary, like -.

Maybe a better idea is to introduce a new syntax element that prevents meaningful interpretation of an adjacent newline? Sort of like the \ at the ends of lines in shell scripts, or the use of | in Haml. I'd prefer something that goes at the start of lines, making the above examples:

X = some_function() do
  foobar
| + some_other_function() do
  borknarf

Y = some_long_expression
| + some_other_long_expression
DavidMikeSimon commented 12 years ago

By the way, you are correct that the .fn example works correctly. Today I learned that it helps a great deal if you actually recompile code before testing it again. :-)

marianoguerra commented 12 years ago

just commited a fix for the arrow after a new line.

thanks for the report

PS: busy january :)

DavidMikeSimon commented 12 years ago

Cool, thank you. But, what did you think of my more general idea for having a syntax to ignore the previous CR?

On Tue, Jan 31, 2012 at 3:07 PM, Mariano Guerra < reply@reply.github.com

wrote:

just commited a fix for the arrow after a new line.

thanks for the report

PS: busy january :)


Reply to this email directly or view it on GitHub: https://github.com/marianoguerra/efene/issues/48#issuecomment-3746878

David Simon

marianoguerra commented 12 years ago

On Tue, Jan 31, 2012 at 9:09 PM, David Simon reply@reply.github.com wrote:

Cool, thank you. But, what did you think of my more general idea for having a syntax to ignore the previous CR?

I like the idea, never used a language with it (I've seen some of them) I'm thinking on how many cases will make it useful and how it plays with ifene indentation..

DavidMikeSimon commented 12 years ago

I'm not sure about how to implement the interaction with ifene indentation, but conceptually, it should be consistent if the "|" makes the parser ignore the prior newline in terms of seeing it as a "statement end", but not skip the comparison of the current and prior lines' indentation to check for an implicit block close.

Useful cases would mostly binary operators that tend to get chained together, which are: addition, concatenation, the -> operator, and logical boolean operators. Though when it comes to addition and concatenation, there is a possible case to be made that it's more "functional-style" to instead list the stuff in an array and use sum or inject.