ozra / onyx-lang

The Onyx Programming Language
Other
97 stars 5 forks source link

Fragments - The Goofy Kid on the Block #14

Open ozra opened 8 years ago

ozra commented 8 years ago

Soft-Lambdas Fragments - The Goofy Kid on the Block [pun not...]

And pleeeease - anyone - a better term than 'soft-lambda' would be nice! Further alternatives to the current term "fragment" is appreciated!

"Soft Lambdas" "Fragments", called simply "Blocks" in Crystal lingua (which is way too generic and ambiguous a term), was not a primary idea for Onyx, but they do serve well. They act basically like a kind of macros. It's a deeply used part of Crystal stdlib, and it also solves iterators in a good way - so it must be embraced in Onyx in a good fashion.

A "fragment" is like a lambda in that it can take arguments. They are however never typed, because the code is literally pasted into the using context code, and so it is type checked in that context. Since it is evaluated in the context it is used, it also means that statements like return, continue, break and next operate in the final context! This means you can stop a map operation half way through by using break, or returning from the function where the soft lambdas is declared using return, which is not possible when using a standard lambda (where those control actions take place in the context of the lambda). As is obvious from all this, a primary purpose for them are for iterating, but they're not limited to that.

Syntax

Much like lambda/function definition, but type restrictions cannot be given to parameters. If there are no parameters or if auto-parametrization is used, ~> alone begins the soft lambda.

The number of params don't have to match the number of arguments, much like in JavaScript. Extraneous params are set to nil and extraneous args are simply thrown away.

list = [1, 2, 3, 4]
mapped-list = list.map (v) ~> v + 1     --> [2, 3, 4, 5]
mapped-list = list.map ~> _1 + 1        --> [2, 3, 4, 5]

-- Example of using `break`
mapped-list.each-with-index (v, i) ~>
   break if i is 2
   say "val at {i} = '{v}'

say "All done!"

--> "val at 0 = '1'"
--> "val at 1 = '4'"
--> "All done!"

Note. Initially I tried a variation of Crystal syntax for this:

list = [1, 2, 3, 4]
mapped-list = list.map |v| v + 1        --> [2, 3, 4, 5]
-- same thing, just with an additional explicit nest-start symbol:
mapped-list = list.map |v| => v + 1     --> [2, 3, 4, 5]

But, I felt it better to go with the style of functions and lambdas, distinguishing with the ~> (because of the fuzzy nature of "operating in the target context")

Automagic parametrization

Inspired by KDB/Q, I implemented auto-parametrization through using _1..._x arg names in the code: ~> (_1 + _2) / _3, this would be the same as (a, b, c) ~> (a + b) / c

Shorthand Fragments

Invoking a method directly on a single argument can be done very terse, since it is rather common. Since operators in Onyx maps to methods internally, the methods can be invoked explicitly when wanted. This can be exploited in this case (a = x + 5 is the same as a = x.+ 5 - note that the +-operator is used as a method in the second case)

list = [1, 2, 3, 4]
mapped-list = list.map ~.* 2       --> [2, 4, 6, 8]

Making fragments as terse as possible is a good target. Any ideas on syntax improvement?

stugol commented 8 years ago

Seems obvious to me that a better name for soft blocks would be "closures".

stugol commented 8 years ago

Suggest allowing type restrictions on parameters. Simply throw a compiler exception if the types don't match the arguments. It'd be handy for avoiding bugs.

Suggest a finish keyword that operates a little bit like break, but only breaks the loop, not the entire function:

reducer() ->
    results = []
    while true
        results << yield
    end                         -- "finish" keyword will exit this loop
    return results

values = [1, 2, 3]
copied-values = reducer ~>
    finish if values.empty?     -- "break" would exit the reducer() call completely here
    values.shift+1
puts copied-values.join         -- "234"

A contrived example, true, but I've actually had a real use case for this before.

next would simply perform another iteration of the loop; while break would return from reducer() immediately; neither of which I want.

ozra commented 8 years ago

Finally I'm back from the dead :)

1) soft-lambdas -> "closure"

No. The primary peculiarity of them are the control keywords and what context they operate in. "closure" generally regards a lambda with common return pattern closuring variables. Which incidentally lambdas does in Onyx too. So then both could be called closures instead. But then what, "soft closure"? ;-)

2) Yes, it's very reasonable to consider type restrictions on the params, if you open that as it's own issue it can get it's own attention, and follow through.

3) "the loop" is a competely arbitrary subject. There might be no, or several, loops in the function using the "slam". The control keywords break, next and return follow well defined rules that work no matter what nodes are or aren't in the func. I can't think of anything that would require 'finish' and couldn't be solved with the existing tools.

stugol commented 8 years ago

What is a "common return pattern"?

What is a "slam"?

The finish keyword would come in handy for writing a capturing loop - similar to reduce, but with an end condition. Rubyish example:

def accumulate
end

results = accumulate do
   finish if [condition]
   [some operation that returns a value]
end

This would be ugly to write otherwise. I'd have to create a temporary array and such.

Better idea:

If more than one block could be passed to the function, that would be another solution. But that would benefit a lot from syntactic sugar. Rubyish example:

def accumulate(&block, &while)
   ...
end

accumulate { ... } while { ... }

Would you be willing to consider this feature? I'm not sure how that would work with your braceless ~> syntax though.

ozra commented 8 years ago

0) Ok - here's a craaaazy idea (but I got it from your joke @stugol ;-) ). What about calling soft lambdas "lego"'s!?

1) With common return pattern I meant that exiting the scope of that code is via return-points, simply the way routines/procesures/functions as we know them work.

2) "slam" was just an in the spur of the moment abbreviation for soft lambda B-)

3) I'll have to let these conceptual ideas roll around in the back of my head for a while, and we'll see what comes out of it.

stugol commented 8 years ago

Wouldn't work. It's trademarked.

ozra commented 8 years ago

While out on my cigar promenade in the snowy woods, I came up with a new angle on this [pun..]: The backslash is really wasted as it is now, so what about:

x = l.map (a)\ a + 1   -- explicit args
x = l.map \ _1 + 1     -- auto-arged
x = l.map \.+ 1        -- terse-form

Or, ditching unnecessary spaces:

x = l.map (a)\a + 1   -- explicit args
x = l.map \_1 + 1     -- auto-arged
x = l.map \.+ 1       -- terse-form

Perhaps args, when used, should change position to go after the slant:

x = l.map \(a) a + 1   -- explicit args
x = l.map \ _1 + 1     -- auto-arged
x = l.map \.+ 1        -- terse-form

Some more examples to get the feel of it:

x = l.map(\.* 2).fold(\_1 + _2)

10.times \
   say "Righty-o"

l.each (v)\
   do-stuff-with v
   break if v is 47

The new name would "obviously" be "slant lambda" (which is apt in many ways).

As for proposition #49 on anonymous types, they would then be:

my-thing.accept (some, construction, params) <\ VisitorOrSuch
   visit(x Stuff) -> do-stuff x

Prior art: Haskell (more?) uses \ for "common lambda".

stugol commented 8 years ago

No, I really don't like this syntax. ~> is vastly better.

ozra commented 8 years ago

In which case it of course could be called "wavy lambda" (it can waiver its' full execution) :-)

stugol commented 8 years ago

Lol

stugol commented 8 years ago

Other options: functor, delegate, func, chunk, sub

ozra commented 8 years ago

Thanks for suggestions. functor = instance with call method used with call syntax delegate = forwarded calls in composed type func = to commonly used abbr for function sub = subroutine which is far from what this is

Leaves us with chunk from that list.

Please pile up more if you come up with some :-)

stugol commented 8 years ago

Fragment.

ozra commented 8 years ago

Now we're talking, that's a good suggestion!

ozra commented 8 years ago

Fragment is now the project term used, thanks for the suggestion! I think we can come up with something even more fitting, but if not, this is way better than "soft-lambdas".

ozra commented 7 years ago

The "Cigar Walk Syntax" Revisited.

list = [1, 2, 3]

x = list.map \a\ a + 1   -- explicit args
x = list.map \\ _1 + 1   -- auto-arged - TWO BS are required (empty arg)
x = list.map \.+ 1       -- terse-form - uses only ONE (dot disambiguates)

x = list.map \           -- for indented body, ONE is sufficient
    %1 + 1

10.times \\              -- but TWO can be used
    say "Righty-o"

list.each \v\            -- with args, it's the same for indented
    do-stuff-with v
    break if v is 47

-- chaining, just use call-parenthesis as usual
list.map(\.* 2).map(\v\
    v + 1
).each(\v\
    do-stuff-with v
    break if v is 47
)
Sod-Almighty commented 7 years ago

The _x syntax here seems to conflict with your recent decision to use _ for char literals.

I really don't see the advantage of \ over |. Especially as to everyone on the planet, \ is the char-escape character!

ozra commented 7 years ago

The _1 syntax is the older (compared to %1). However, it does not conflict. _\d vs _"." (simplified).

The reason for \x, y\:

Isolated comparison:

Sod-Almighty commented 7 years ago

I really don't see some-fun \ x \ body x being any more readable. It's just as bad as the |, with the drawback that it will be unfamiliar to everyone, rather than just to people who have never used Ruby.

ozra commented 7 years ago

I find the | much more confusing, thinking of binary operators, while the \ clearly states that what follows is special. I will keep this in consideration however! The down-side is that we must use the Haskell-like .|. then, something I've been wanting to reverse, since | is good as a generic binary operator for run-time unions, etc (keeping syntax the same).

For completion: seen from human view (instead of above machine-view): some-fun \x\ body x vs. some-fun |x| body x And, when used with parentheses calls, it's clear in both cases: some-fun(\x\ body x) vs. some-fun(|x| body x)

Sod-Almighty commented 7 years ago

Why not just somefun x ~> x*2? And if there's more than one param, mandate parens?

ozra commented 7 years ago

In the case of just one arg, there's already short-cut notation:

I don't think one more variant for named one arg is needed; then just use the common construct.

ozra commented 7 years ago

Here's the latest naming iteration. Actually, I've been using it in my head since soon after @Sod-Almighty 's suggestion of "Fragment", namely: "Figment".

"Block" is obviously a really bad name, which is why I never wanted to use that, when I finally realized the concept of them is rather fucking neat (I despised everything Ruby before) - it's way to ambiguous.

So, "Soft Lambdas" came to life as a place holder until better words fell in place.

Then came the "Fragment" suggestion, great.

The term still has several other connotations - especially in web programming, so...

To give the concept a specific term, where there's no question it refers to a specific linguistic concept, it unimaginatively morphed into "Figment". They are kind of the figments of a lambdas imagination ;-)

Fragment sounds better, but specificity makes life and docs easier in referring to language constructs.

Sod-Almighty commented 7 years ago

Don't like figment.

Sod-Almighty commented 7 years ago

pod

ozra commented 7 years ago

Conceptually it clashes completely with the meaning of pod, however: I really like it! Terse, one syllable, no confusion with other coding terms - and cool sounding! This is the primary candidate for the final name change in my eyes now :)