How about new syntax elements

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

How about new syntax elements

Sergey Kovalev
Chain symbol "!"
It should expand as follow:

function_name!expression --> function_name(expression)
sin!x -> sin(x)
fn!function() end -> fn(function() end)
f1!f2!f3!f4 -> f1(f2)(f3)(f4)

example:
-- function scope: https://pastebin.com/8u4xfbYm
scope!function(auto,defer)
    f=auto(io.close){ io.open "test.txt" }
    defer!function() print "atexit" end
    print(f:read())
end

Or may be short function definition "@"
@ body end          --> function() body end
@() body end          --> function() body end
@(args) body end    --> function(args) body end

And add function call with out brackets as for strings and tables:
fn(1)               --> fn(1)
fn "text"           --> fn("text")
fn{ x=1, y=2 }      --> fn({ x=1, y=2 })
fn @ body end       --> fn(function() body end)
fn@(args) body end --> fn(function(args)) body end)

Example:
scope@(auto,defer)
    f=auto(io.close){ io.open "test.txt" }
    defer@ print "atexit" end
    print(f:read())
end

Reply | Threaded
Open this post in threaded view
|

Re: How about new syntax elements

Philippe Verdy


Le dim. 30 juin 2019 à 15:20, Sergey Kovalev <[hidden email]> a écrit :
Chain symbol "!"
It should expand as follow:

function_name!expression --> function_name(expression)
sin!x -> sin(x)
fn!function() end -> fn(function() end)
f1!f2!f3!f4 -> f1(f2)(f3)(f4)

You last statement gives a contradiction, because it is left-associative (it performs functions composition, similar to the circle "∘" math operator)
  f1∘f2∘f3∘f4 = f1(f2(f3(f4)))
  sin∘x = sin x = sin(x)

It is different from "chaining" which is normally right-associative and is written in the other direction (i note it with the "::" operator like in Lua calls of a member of the left-side table):
  f1::f2::f3::f4 = f4(f3(f2(f1))) = f4∘f3∘f2∘f1

But your last statement actually does not mean any composition, because:

  f1(f2)(f3)(f4)  

actually means:

  ( (f1(f2) )(f3) )(f4)  

and it is NOT a composition of the 4 functions. It just means that

  - f2 is an arbitrary parameter, which is processed first by calling the function f1 which returns some unknown function "u1",
  - which is then called with an arbitrary parameter f2 and returns another unknown function "u2",
  - which is then called with an arbitrary parameter f4.

Nothing in the Lua notation "f1(f2)(f3)(f4)" indicates that f2, f3, or f4 are functions, only f1 is assumed to be a function. However:

  - f1 and "u2" are assumed to RETURN functions,
  - "u1" and "u2" are assumed to accept a function in their first parameter (they may as well accept other types, including a number, string, table, userdata, thread, or nil, and could accept more parameters).

Hmmm... All this "chaining" is then counterintuitive. I prefer the Lua definition using "::" (in the reverse direction) for chaining method calls, which is conceptually a regular mathematic composition of function (except that with "::" it is written in the reversed order compared to the conventional mathematical notation using "∘")

----

I think that you really want to propose is a true function composition, like in maths and it will be more intituively written using right-associativity:

  f1!f2!f3!f4 = f1(f2(f3(f4))) = f4()::f3()::f2()::f1
  f1!f2!f3!f4 (x) = f1(f2(f3(f4(x)))) = f4(x)::f3()::f2()::f1

so that we also have:

  sin!x = sin x = sin(x) = x::sin()

Reply | Threaded
Open this post in threaded view
|

Re: How about new syntax elements

Philippe Verdy
Counter proposal:
    f1 !  expression2 ! expression1
or
   f1 ! (expression-list2) ! (expression-list1)

creates a anonymous function and you can call it with one or more parameters, and where express2 can be a function or simple value, or a list of expressions.

All parameters in expression-list1 are passed to f1, which will return one or more values (or nil). The first value returned is used as the function which will be called with all other values returned by f1.

This comes with an extension, not allowed with normal function calls as all these become valid:
  nil ! (expression or expression-list)
  false ! (expression or expression-list)
  'string' ! (expression or expression-list)
  {table} ! (expression or expression-list)
  userdata ! (expression or expression-list)
Their effect is to just return the left-hand-side value without performing any call because they are not a function. As well:
  (1,2,3)! (expressions...)
will just return the list 1,2,3 without performing any call, because 1 is not a function.

This can be used to perform composition (use "!" exactly like where you would use the circle math operator "∘" but write it in the reverse order):
  f1!f2!f3 (x)
will be identical to f3(f2(f1(x))). i.e. "a!b!c" is equivalent to the math notation "c∘b∘a"

Why do I add the "extension" above? This allows one of the functions given in the composition to not just return one value but also provide other computed values to the "chained" function (note that this still uses trailing calls, so it is efficient in Lua except that this is not a simple unconditional jump, but performs a type-test on the first "popped" returned value before jumping, and if it is not a function no jump occurs and this is a normal return of one or more values oncluding the first 1 which is not a function but can be nil, or a number, string, table...).

This allows an efficient way to create "filters" that can process an input and pass additional data to the next filter.

Note that "!" here becomes an unary operator, which creates an anonymous function from a left-side list of expression of any types. and writing "false!" is valid, it is the constant anonymous function that returns false. As well "nil!" is valid and is an anonymous function returning nil. The compiler can avoid creating these anonymous functions for "x!" when it knows that "x" is constant or is not a function, or if the anonymous function is used to perform a call immediately with the parameters given on the right side, so:
  "filter!f (x)" will do "(function() local temp,rest = filter() ; if type(temp)=='function' then return temp(rest); end)(x)"
  "filter!f(x)" may be compiled as "filter(f(x))" if the compiler knows that 'f' is a function.
  "false!( f(x) )" will just be compiled as "(function() f(x); return false end)()", i.e. it will compute f(x) then discard its value to return false directly.
  "math.atan ! math.sin" is just the math composed function "sin∘arctan"
  "math.atan ! math.sin ! y/x" is simply "sin(atan(y/x))"

Filters however can be much more useful when they return multiple values. their first parameter is the conventional "standard input", the other parameters are contextual information that an be passed (modified) to other filters written after the "!" unary operator.

Note that this "!" unary operator is always itself left-associative, when "chaining" them, the calls will always be performed from left to right, but the first call will be with the parameters at end of the "a!b!c!...!z" list where "a(z)" will be called first after evaluating "a" and "z" (the next call will be b(...) if a(z) returned a function.

  








Reply | Threaded
Open this post in threaded view
|

Re: How about new syntax elements

Sergey Kovalev
> Counter proposal:
>     f1 !  expression2 ! expression1
> or
>    f1 ! (expression-list2) ! (expression-list1)
>
> creates a anonymous function and you can call it with one or more parameters, and where express2 can be a function or simple value, or a list of expressions...

It's too complex. It just special symbol to identify next expression
is function like quotes or braces. And if previous value is function
it could be used as argument for it.
!function(...) body() end is absolutely same to function(...) body() end
except it could be passed to function as argument without brackets.

fn(1,2,3) --> fn(1,2,3)
fn"text" --> fn("text")
fn{x=1,y=2} --> fn({x=1,y=2})
fn!function() end --> fn(function() end)

It could be chained if function return function.

Symbol "@" just syntax sugar for brevity.
@ ... end --> !function() ... end
@(arg) ... end --> !function(arg) ... end

fn@ body() end -> fn(function() body() end)
fn@(args) body() end -> fn(function(args) body() end)

Them could be used to define block operations:

tree:for_all_node@(node) show(node) end
scope@(auto) f=auto(io.close){ io.open "file" } end
windows:setcallback@ body() end
try@ body() end
try@(except,finally)
  body()
  except@(err) onerror() end
  finally@ print "done" end
end
t1=task@ body() end
t1:resume()

function do_twice(fn) fn() fn() end
do_twice@ print "hello" end

function my_repeat(n) return function(fn) for i=1,n do fn(i) end end
my_repeat(10)@ print "+++" end

ignorable=pcall
ignorable@ body() end

function profile:measure(prm)
  if type(prm)=='table' then return function(fn) return
self:estimate(fn,prm) end
  if type(prm)=='function' then return self:estimate(fn) end
  error "invalid argument"
end
profile:measure@ body() end
profile:measure{ args }@ body() end

and so on.

Reply | Threaded
Open this post in threaded view
|

Re: How about new syntax elements

Philippe Verdy


Le lun. 1 juil. 2019 à 00:06, Sergey Kovalev <[hidden email]> a écrit :
> Counter proposal:
>     f1 !  expression2 ! expression1
> or
>    f1 ! (expression-list2) ! (expression-list1)
>
> creates a anonymous function and you can call it with one or more parameters, and where express2 can be a function or simple value, or a list of expressions...

It's too complex. It just special symbol to identify next expression
is function like quotes or braces.

Not at all, it simplifies a lot of things. It's not just syntaxic sugar like what you propose (which is incoherent) but implements the mathematical composition and filters. It allows functional programming, it can be used for multi-layered parsers, for safe I/O, for handling errors as return values instead of exceptions.

What you propose is just syntaxic sugar, but with additional ambiguities.