Open kasperpeulen opened 7 years ago
To me this looks very promising. Even thinking about JSX.
To better understand the first example, I see you use if (cond) { 10; }
, without yield
ing. Is this intentional? Maybe something like:
const result = if (cond) {
yield 100;
}
else {
yield 10;
}
Sounds better?
Digging deeper, the yield
solves some odd behaviours shown in CoffeeScript about nested list comprehensions. Wondering if CS history (considering differences) would help in understanding this proposal eventual downsides/upsides.
If you put any of these in an outer generator function things start getting subtle.
function* generator() {
if (users[0].name.startsWith('A'))
yield user;
}
const iterable = for (let user of users) {
if (user.name.startsWith('A'))
yield user;
}
const result = if (cond) {
yield 100;
} else {
yield 10;
}
yield 123;
}
That's something that the extra * { }
helps avoid since there is another boundary to indicate that something special is going on and where the yield
will end up.
This would be awesome.
im not sure, but is what you trying to propose is a substitute for Arrow Function?
const num
= params => params.cond1
&& params.cond2
? 100
: 10
generators dont mix, but direct yield*
of map, filter or reduce would probably be sufficient
const firstLetterIsA = _ => _.name.startsWith( 'A' )
function* iterable()
{ yield* users.filter( firstLetterIsA ) }
I don't think so...
const num = params => params.cond1
&& params.cond2
? 100
: 10
console.log(num) // [Function]
console.log(num()) // 100 or 10
const num2 = if (params.cond1 && params.cond2) {
100
} else {
10
}
console.log(num2) // 100 or 10
A programming example that really exemplifies the utility of this is Ruby. In fact one reason arrow function offer so much utility is that single line arrow function have the implicit return. It leads to better readability. Consider this
const hasNoRemainder = n => d => !n ? n : n % d === 0
const fizzBuzz = fizzNum => buzzNum => num => {
const numHasNoRemainder = hasNoRemainder( num )
return if ( numHasNoRemainder( fizzNum * buzzNum ) ) { 'fizzBuzz' }
else if ( numHasNoRemainder( fizzNum ) ) { 'fizz' }
else if ( numHasNoRemainder( buzzNum ) ) { 'buzz' }
else { num }
}
There is a single return statement. Rather than this example, where each condition has an return statement.
const hasNoRemainder = n => d => n % d === 0
const fizzBuzz = fizzNum => buzzNum => num => {
const numHasNoRemainder = hasNoRemainder( num )
if ( numHasNoRemainder( fizzNum * buzzNum ) ) { return 'fizzBuzz' }
else if ( numHasNoRemainder( fizzNum ) ) { return 'fizz' }
else if ( numHasNoRemainder( buzzNum ) ) { return 'buzz' }
else { return num }
}
The first example conveys the idea in a much clearer way, the function returns the value resolved from the condition check; this versus each condition check returns some value (or maybe not, resulting in bugs, etc.)
here come the imperative vs functional again..
I think that better to put an asterisk like if*
, switch*
, for*
, while*
.
const num = if* (cond) {
100;
} else {
10;
};
const iterable = for* (let user of users) {
if (user.name.startsWith('A'))
yield user;
}
but, I feel that extra "let" and "yield" appear compared to the comprehensions.
[for(n of "1234567890") if(n != "0" && n != "1") for(op of "+-/*") eval(`${n} ${op} ${n}`)]
[for(let n of "1234567890") if (n != "0" && n != "1") for(let op of "+-/*") yield eval(`${n} ${op} ${n}`)]
can let
and yield
be omitted?
I think that better to put an asterisk like if, switch, for, while.
That makes very much sense to me if the indicates an implicit `doexpression. And without, is an implicit
do` expression.
That would also solve the problem that @sebmarkbage describes above.
So then we would write this:
const iterable = for* (let user of users) {
if (user.name.startsWith('A'))
yield user;
}
Writing without a star (justfor
) would then raise an error, as yield
is not defined in that context.
You could then also generate an iterable
like this:
const numbers = if* (cond) {
yield* [100,200];
} else {
yield* [10,20];
};
The usecase is probably very little for the last example, but it would make this all very consistent and predictable.
@kasperpeulen
That makes very much sense to me if the indicates an implicit do expression.
Yes. I believe that it is implicit do expression, but may be confusing indeed. Also, there may be problems that I overlook.
Please don't add *
to each if*
etc.. that is, in my opinion, clunky and ugly.
Since inception JavaScript if
returns void. So if it started to return the last evaluated value, no old code will break, since that code is just ignoring that value.
Anyway, it isn't too hard to just build one's own ifElse
. For example, here is one that behaves similarly to Ramda
's ifElse
const ifElse = testCondition => trueCondition => falseCondition => input => (
testCondition(input) ? trueCondition(input) : falseCondition(input)
)
example:
const halveOnlyEvenValues = ifElse
( num => num % 2 === 0 ) // test condition
( num => num / 2 ) // true condition
( num => num ) // false condition
const val1 = halveOnlyEvenValues(10) // 5
const val2 = halveOnlyEvenValues(11) // 11
To emulate the if
/ else
discussed here, one could do
const noInputIfElse = testCondition => trueCondition => falseCondition =>
ifElse(testCondition)(trueCondition)(falseCondition)()
For example this
const num1 = 5
const val3 = if(num1 % 2 === 0) {
num1 / 2
} else {
num1
}
is essentially just
const num2 = 5
const val4 = noInputIfElse( _ => num2 % 5 === 0 )( _ => num2 / 2 )( _ => num2)
Which, upon looking over, probably isn't as nice as the way Ramda
does it. Also, it isn't hard to build a variadic version of our ifElse.
// first, some standard helper functions
const always = a => b => a
const head = arr => arr[ 0 ]
const tail = arr => arr.slice( 1 )
const pipe = ( ...funcs ) => input => tail( funcs ).reduce( (a,b) => b(a), head( funcs )( input ) )
const compose = ( ...funcs ) => pipe( ...funcs.slice().reverse() )
const chunk = n => arr => !arr.length
? []
: [ arr.slice( 0, n ) ].concat( chunk( n )( arr.slice(n) ) )
// group list items into pairs
const pairListItems = chunk(2)
// multiIfElse
const multiIfElse = ( ...conditions ) => defaultCondition => compose(
...pairListItems( conditions )
.map( pairs => ifElse( pairs[ 0 ] )( pairs[ 1 ] ) )
)( defaultCondition )
Side note, this mulitIfElse
can also serve as the basis to create a pretty neat switch
.
Now the fizz-buzz example is just
const hasNoRemainder = n => d => !n ? n : n % d === 0
const fizzBuzz = fizzNum => buzzNum => num =>
multiIfElse(
numHasNoRemainder => numHasNoRemainder( fizzNum * buzzNum ),
always('fizzBuzz'),
numHasNoRemainder => numHasNoRemainder( fizzNum ),
always('fizz'),
numHasNoRemainder => numHasNoRemainder( buzzNum ),
always('buzz'),
)( always(num) )(hasNoRemainder( num ))
or perhaps
const hasNoRemainder = n => d => !n ? n : n % d === 0
const numHasNoRemainder = num => hasNoRemainder( num )
const fizzBuzz2 = fizzNum => buzzNum => num => multiIfElse(
always( numHasNoRemainder( fizzNum * buzzNum ) ), always('fizzBuzz'),
always( numHasNoRemainder( fizzNum ) ), always('fizz'),
always( numHasNoRemainder( buzzNum ) ), always('buzz'),
)( always(num) )()
Anyway this
const hasNoRemainder = n => d => !n ? n : n % d === 0
const numHasNoRemainder = hasNoRemainder( num )
const fizzBuzz2 = fizzNum => buzzNum => num =>
multiIfElse(
always( hasNoRemainder( num )( fizzNum * buzzNum ) ), always('fizzBuzz'),
always( hasNoRemainder( num )( fizzNum ) ), always('fizz'),
always( hasNoRemainder( num )( buzzNum ) ), always('buzz'),
)( always(num) )()
Would be awesome. It just feels like what JavaScript should do...
I just don't see the advantage to adding an unnecessary asterisks
const hasNoRemainder = n => d => n % d === 0
const fizzBuzz = fizzNum => buzzNum => num => {
const numHasNoRemainder = hasNoRemainder( num )
return if* ( numHasNoRemainder( fizzNum * buzzNum ) ) { 'fizzBuzz' }
else if* ( numHasNoRemainder( fizzNum ) ) { 'fizz' }
else if* ( numHasNoRemainder( buzzNum ) ) { 'buzz' }
else { num }
}
Seriously why do this? (yuck!) What an eye sore! From the return
function it is pretty clear what is going on. I'm not a huge fan of the generator syntax in Javascript. I pretty much avoid them. But at least there the function behaves quite differently. Here the if just implicitly returns. Why add line noise?
No asterisks! :-)
@babakness you might be wrong about “no old code breaking”:
if (true) { [1,2] }
[1] // is this an array with `1`, or the index 1 on the array literal, `2`?
@babakness I think that noise is generated due to too many functions names and function applications. If do not need reusability, arrow function feels noisy.
Btw, may not need to put an asterisk in else if.
/*
* code not to reuse
*/
function* fizzBuzz(max) {
for (let i = 1; i < max; i++) {
if ( (i % 3 == 0) && (i % 5 == 0) ) {
yield 'FizzBuzz'
} else if (i % 3 == 0) {
yield 'Fizz'
} else if (i % 5 == 0) {
yield 'Buzz'
} else {
yield i
}
}
}
console.log(...fizzBuzz(1000))
console.log(...(for* (let i = 1; i < 1000; i++) {
if* ( (i % 3 == 0) && (i % 5 == 0) ) {
yield 'FizzBuzz'
} else if (i % 3 == 0) {
yield 'Fizz'
} else if (i % 5 == 0) {
yield 'Buzz'
} else {
yield i
}
}))
@ljharb Hey Jordan, this must be a warning from a linter, in Node I get
> if (true) { [1,2] }
[ 1, 2 ]
@babakness that’s not in node, that’s in the node repl - repls already have the behavior you want; as such, testing in a repl is often useless.
@tasogare3710 This syntax would really confuse a newcomer to JavaScript. The asterisk is taking too many personalities. function*
is a generator. With for*
it returns an implicit
First off, I'm going to argue that the entire generator syntax is unnecessary on top of being ugly. Take a n iterative solution for the Fibonacci sequence
function* notElegantFibGen() {
let current = 0;
let next = 1;
while (true) {
yield current;
[current, next] = [next, current + next];
}
}
Which is used as follows
const iterativeFibGen = *notElegantFibGen()
iterativeFibGen.next() // {value: 0, done: false}
iterativeFibGen.next() // {value: 1, done: false}
iterativeFibGen.next() // {value: 1, done: false}
iterativeFibGen.next() // {value: 2, done: false}
....
Yet this is not necessary. Its also a bad idea. The algorithm to generate Fibonacci numbers is not re-usable. What if we just want the 100th number in the sequence? Here is a functional approach that gives both a "generator" like function using a state machine as well as a ready to use, efficient, standalone Fibonacci function.
const fibCalculator = (a, b, n) => n
? fibCalculator(b, a + b, n - 1)
: a
const fib = index => fibCalculator(0,1,index)
const getFibGen = () => {
let index = 0
return () => fib(index++)
}
The "generator" part can be used as follows
const genFib = getFibGen()
genFib() // 0
genFib() // 1
genFib() // 1
genFib() // 2
...
Now if we want the 100th Fibonacci number directly, its just
fib(100) // 354224848179262000000
One could argue that a generator function can be used with a function as well. Which is true, but just compare the functions side by side
function functionalStyle() {
let index = 0
return () => fib(index++)
}
Versus
function* generatorStyle() {
let index = 0
while(true){
yield fib(index++)
}
}
No strange syntax, less code.
As for the whole for*
business and making it assignable or return values. One could just abstract the for loop away. For example
const times = callback => numberOfTimes => {
let returnValues = []
for( let index=0; index < numberOfTimes; index++ ){
returnValues.push( callback( index ) );
}
return returnValues
}
Now if we want the first 15 Fibonacci numbers its just
const getFibSequence = times( getFibGen() )
getFibSequence( 15 )
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
And we get a fib sequence generator for free 😄
With our fizzBuzz examples it's simply
const threeFizzFiveBuzz = fizzBuzz( 5 )( 3 )
const sequenceFizzBuzz = times( threeFizzFiveBuzz )
sequenceFizzBuzz( 16 )
@ljharb Hey Jordan
index.js
if(true) {
[1,2]
}
$ node index.js
$
no errors
Your example of “fib” only works when you can compute the yielded value in advance.
@babakness right, there’s no errors - if suddenly it yielded a value, and it was followed by another set of square brackets, it would start to throw errors. That’s what’s meant by “breaking old code”, and that’s why making if
suddenly start implicitly returning a value would break the web.
Give me an example how it breaks old code if if
starts returning values
Give me an example of how the functional fib example doesn't work. It works, tested.
https://github.com/sebmarkbage/ecmascript-generator-expression/issues/1#issuecomment-343636788
Your fib example works - im saying that your overall approach wouldn’t work to eliminate all the use cases of generators.
Ah, ok, give me an example where a generator function does something that can't be done using functions.
Anything iterative can be done using recursion, this is a proven mathematical fact. Generators translate into some machine code that can be implemented in a Turing machine and Lambda Calculus is Turing complete. Ergo, it works.
But you may have a point if you can find an example where the generator example that is more elegant. Maybe there are cases out there. So, to support your statement, please produce this example. Thanks.
@babakness I don't think this is the place to argue that generator functions shouldn't have been added to the language, that ship has sailed already.
@TehShrike True... anyway, the point is that the syntax is confusing especially with for*
switch*
being proposed here. If they all return an implicit value, great, sound good to me. Let's not add more ugly syntax.
As has been stated, they can't all return an implicit value - that would break old code. Either there's an explicit marker (like do { }
), or there's no new feature.
If you don't inject new syntax into old code that doesn't expect a value, it doesn't break old code. Again, please enlighten me on how this breaks existing code.
Just detect assignment (or return) then return const foo = if(true){ [1,2] }
. Nobody wrote old working JavaScript that does this. So writing this moving forward can't break old code.
Where const foo = if(true){ [1,2] }
breaks so does const foo = if*(true){ [1,2] }
. So why add *
?
@babakness again you're missing the point. Obviously if an if
statement is used in expression position, that's new code. I'm talking about ASI issues around it being used for decades now in statement position. Please re-read https://github.com/sebmarkbage/ecmascript-generator-expression/issues/1#issuecomment-343636788 until you understand it before objecting again.
@ljharb No points are being missed on this end. So are you saying that
if (true) { [1,2] }
[1]
The old javascript, now in a new parser would become something like
if (true) { [1,2] }[1]
If so, as I said above, there is no return
or assignment so that example stays the same as before
if (true) { [1,2] }
[1]
in the new parser. Nothing is changed because no value is utilized. But this
const foo = if (true) { [1,2] }
[1]
Because const foo = if (true) { [1,2] }
in old JavaScript is a syntax error, this would never happen in old code. In new code we detects assignment. Which should become
const foo = do {
if (true) {
[1,2]
}
}
[1]
In the new code foo
is 2
which it should be. If the user wants to rely on ASI they need to prefix [...]
and (...)
with a semicolon. Again, this can't happen in old code. The proposal posted at the top of this thread proposes turning this:
const num = if (cond) {
100;
} else {
10;
};
into:
const num = do {
if (cond) {
100;
} else {
10;
}
}
Which is rad, lets do
this. Forgive the pun. 😄
Summary:
return
before if
is a syntax error in old code. It doesn't occur there.if
is not assigned or returning any value, nothing changes. Consequently, old code works just fine.*
is necessary.Cheers. B
@babakness this specific issue is about implicit returns. That means that this issue is about changing if (true) { [1,2] }
from evaluating to void
, into evaluating to [1, 2]
, without any return
/yield
statements.
@TehShrike Right. No yield
should be necessary. The behavior of the existing yield
inside a generator would be the same.
The main concern @ljharb has is with old code. Old code can't assign to if
or switch
statements. No need to return a value where void is expected. For the implicit do
statement you have to either return
or make an assignment.
I think a more interesting discussion is the explicit return inside the if assignment.
// bad style but worth discussion
function foo(bar) {
const whatever = if(bar <= 0 ) {
'too little'
} else if (bar >9000) {
return over9000(bar)
}
return `${ whatever } is just enough`
}
The line return over9000(bar)
is returning from foo
the value returned by said function and not the value ${ whatever } is just enough
. It is bad style to write code like his but what this does is worth discussion. My notion of what this does is borrowed from ruby
which also has implicit returns
It would be absurd to only detect assignment or returns; there's statement position and expression position, and if
can't appear in expression position. Allowing it to return a value anywhere would mean it could appear anywhere an expression can, including foo(if (true) { [2] });
for example.
@ljharb Look at the original post at the top of this page.
Even if you did it everywhere there is no conundrum. In your example it in fact would become
do {.... } [1]
which if we assumed you could start a line with do
is 2
and equivalent to the number 2 sitting in a line of JavaScript not assigned, not returned, and doing nothing at all. It does nothing.
In Babel stage-0, a do
expression not being assigned result in an error.
So we are only putting in an implicit do
in accordance to stage-0
where it is acceptable syntax at the moment. Again even if it were acceptable everywhere, still, no issue. The end result is that you don't even need do
or ugly *
symbols polluting Javascript code from now onward. It isn't absurd. We disagree there.
edit:
I am not suggesting doing this everywhere and was only responding to the notion that context sensitivity is absurd. It is not correct that blindly returning everywhere is harmless (see below) and my previous summary was correct. To jackhammer a *
into Javascript is not necessary when context does matter
As a follow up to my last comment, I stick by saying that only where a value is expected should there be an implicit do. It is possible to concoct code that would work before and break now if there is no assignment in the form of
if(true) {
function doBadThings(){}
}
(1) // not sure why someone would do this in OLD javascript but there you have it.
Not sure why someone would do that in old code; however, sticking with how do
works today, it isn't valid on a standalone line. So this
// this is invalid code, because of context
do {
if(true) {
function doBadThings(){}
}
}
(1)
should not happen. do
is not implicit where it can't be and it can't be there (see above link). I see no problem with implicit statements. For example
() => 1
is implicitly
() => return 1
Because of context.
1;
doesn't become
return 1;
Likewise an implicit do is only applied where an if
, switch
, or for
statement is being assigned to or returned. It is worse to include a cryptic and ugly *
everywhere than the do have implicit do
statements when assigning directly to an if
, switch
etc....
If someone wanted this effect
if(true) {
function doBadThings(){}
}
(1) // not sure why someone would do this in OLD javascript but there you have it. In this context, `if` does what it would do under any case
In NEW Javascript, they could do what would make do
valid here, wrap it in parenthesis
(if(true) {
function doBadThings(){}
})
(1)
which becomes
(do {
if(true) {
function doBadThings(){}
}
})
(1)
Again, we are opening up the if
statement to a new context use. Old cases do not allow this context and so there is no issue. To object please provide a clear example that would cause problems in the old Javascript and that only a modifier could solve.
Note that in existing (old) Javascript this is valid
(function foo() {
return 1
})()
but this is not
function foo() {
return 1
}
()
Context matters.
So its not that we're wrapping if
statements in do
when used in assignments.
Instead we are adding an implicit do
when a statement is used in an expression context. The validity of which is differed to the validity of do
itself. Do
is evaluating a statement as an expression.
So this we are actually proposing here that this
myAwesomeFunct(
(if (x > 10) {
true
} else {
false
})
)
becomes
myAwesomeFunct(
(do{(if (x > 10) {
true
} else {
false
})})
)
which in turn becomes just
myAwesomeFunct( x> 10 ? true : false)
However the example given by @ljharb in https://github.com/sebmarkbage/ecmascript-generator-expression/issues/1#issuecomment-343636788
is not a problem because the statement is not inside of an expression. Currently that is not allowed in Javascript, which is as the heart of this proposal.
(if (true) { [1,2] })
[1]
is not valid in Javascript as it currently stands. So old code can't do this. Moving forward that would become
( do { if (true) { [1,2] } })
[1]
which is in turn
(true ? [1,2] : undefined)[1]
Assignments to statements should therefore be in expression context
const foo = (if ( x > 10 ) { true } else { false })
In a second part of the proposal, everything on the right side of an assignment is implicitly an expression or in an expression context moving forward. Old code can't do this. But new code see this
const foo = if ( x > 10 ) { true } else { false }
as this
const foo = (if ( x > 10 ) { true } else { false })
which is this
const foo = ( do { if ( x > 10 ) { true } else { false } })
and becomes (in this case) simply
const foo = x > 10 ? true : false
@babakness Is * so ugly? I think that it is not much different from =>.
I can understand your idea, but explicit do expr is too nested. At least, if we do not adopt *, I think that we need other suggestions.
// explicit do expr too nested
function* gen(arr) {
yield* do* {
if (cond) {
yield* do* {
for(let v of arr) {
yield v * v
}
}
} else {
yield* do* {
for(let v of arr) {
yield v + v
}
}
}
}
}
@tasogare3710 this same issue can happen today if a regular function with many nested if statements the return a value for various conditions. The only remedy is breaking the code into smaller composable functions.
Why reinvent the wheel? Let's look at something that already exists. The ruby
language already does this and the community has its own style guide and best practices. I see no advantage in adding superfluous line noise into JavaScript. A language has already gone the way of line noise, called perl
and many people jumped ship to python
. A language with minimal syntax noise.
Consider php
. A language that borrowed for perl
It adds $
everywhere. Methods are called using ->
. It's ugly. Super ugly.
Now consider CoffeeScript or coffee
. It tries to combine elements of ruby
and python
and move JavaScript forward in this philosophy. This is where the fat arrow syntax of es6 comes from. CoffeeScript already does implicit returns and it works. Writing deeply nested if
statements is often a sign that the code can be refactored. If anything, it should not be encouraged; and certainly not with line noise as manditory syntax.
We can add implicit return too and do it in a way that doesn't break old code by focusing on statement context as described above. Let's not litter symbols into JavaScript. We have an opportunity to make JavaScript a cleaner language. Just as fat arrow functions and implicit returns have done. The new pipe operator now moving to stage-0 helps remove the noise of nesting function calls, etc.
Here too we can improve readability. Some people act like it's too good to be true that we can do implicit returns without add noise.
We can.
We are adding the ability for the parser to evaluate statements as expressions in contexts where not before possible. And if statement in a standalone line is not in an expression context. Wrapping it in parenthesis is and that doesn't work in old JavaScript. Since old code Would break if it did this, we can safely move forward as a clean and natural extension of the language.
My biggest problem with the *
isn't the esthetics per se. It's that it is very unusual coming from other languages. No one else does that.
It looks like something weird that you have to learn to understand.
I'm concerned about people coming from other languages being turned off. JavaScript has the nice property that once you read it you can mostly figure out what's going on.
Without the star the edge cases are weird but if you read the code you can more or less understand what's going on. If you've seen the yield keyword in other languages.
With the star, it becomes something exotic and strange.
@babakness Again, I can understand your idea, but I think that idea can say the same for generator expressions as well as asterisked implicit do expressions.
You before, said generator expressions that there is unnecessary. I no longer know whether generator expressions is really necessary.
@sebmarkbage
It's that it is very unusual coming from other languages. No one else does that.
It looks like something weird that you have to learn to understand.
With the star, it becomes something exotic and strange.
* {Yield 2};
also I think can say the same thing. I no longer know more and more.
in the end, if explicit do expressions can return generators, does'nt it need generator expressions?
I originally stated generators themselves were not particularly necessary when doing functional programming. I was illustrating that we should help JavaScript become more expressive and grow into its functional roots rather than keep changing it to conform to a procedural way of thinking. This way we can stop adding line noise to the language. Javascript is a misunderstood language. It stems from Scheme not Java. It is often taught incorrectly.
Consider for example the "callback hell" problem that async/await and Promises solve. Essentially async/await is operating like a promise then/catch pattern. Yet you could do the same thing by currying functions that take callbacks and passing the input of one function from the output of another. Indeed my multiIfElse
example above is sort of like how promises work under the hood. The multiIfElse
function is basically nested ifElse
functions. The reason they can be expressed in a more linear fashion is because we are composing curried functions. And compose is basically a glorified reduce
! So async/await is ultimately syntax sugar for reduce
:-p
Anyway I agree that syntax sugar is helpful--so long as it makes the language become more beautiful, easier to read, and so on. Often times, form is function.
But if you write ES6 expressions today, yes then yield
it is necessary as is the unfortunate *
symbol. Generator functions yield
. So you would yield
instead of return
if-else statements from do
expressions. If we automatically wrap if-else statements in expression context with do
then you don't need do
since it's automatically there and can return
or yield
the evaluated result.
@babakness In this issue no one has argued that the function is wonderful (About implicit do or do* expressions). Javascript is not functional programming. Function is just a first class citizen.
Originally, to me it does not have to be asterisk, do
,{
and}
instead marker.
Apparently, you seem to be in a different world from us.
@tasogare3710 hey there. So JavaScript doesn't have the functional tools that a language like Haskell offers, there is no lazy evaluation, enforcement of immutability, etc. but you can totally write in a functional style. There are also proposals and libraries to get it there. Libraries like Ramda and Lazyjs. Pattern matching proposals
https://github.com/tc39/proposal-pattern-matching
Pipe operators
https://github.com/tc39/proposal-pipeline-operator/blob/master/README.md
You can enforce immutability. You can even make JavaScript a typed language with TypeScript.
I like JavaScript. There is a growing movement in working with the language in an ever more functional style. There are now many libraries for handeling asynchronous events as streams, reactive programming libraries and so on. Also consider the growing popularity of Vue and React... So I'm definitely with "us". Consider joining the other "us" :-)
Back on topic, please explain why any marker or astrerisk is necessary. All that I see as needed is paranethesis to explicitly define an expression context when it not already implied.
Because things without a marker already have a meaning, and conflating two meanings with the same syntax is unclear and hard to understand, and is also a very un-functional approach. Explicit > implicit.
Context driven behavior is already JavaScript and parenthesis indicate context
const foobar = a => b => a + b
The return is implicit. This is still declarative. You maybe confusing declarative for explicit.
Example from Haskell
a b c d = e f g h
What does this do in Haskell? Obviously you have to know some implicit syntax rules. How about this
a b c d = e f $ g h
More rules.
Back to the issue at the top of the page. In Haskell
isEven n = if mod n 2 == 0 then True else False
That's pretty explicit, one could also write
isEven n = mod n 2 == 0
Still declarative.
Back to Javascript, using the ifElse
function I've defined in my previous post
const isEven = ifElse( n => n % 2, always( true ), always( false ) )
Looks pretty declarative and functional to me
// compare to Haskell example
// isEven n = if mod n 2 == 0 then True else False
const isEven = n => if (n % 2 ) { true } else { false }
Looks pretty declarative and functional to me. Would be nice to have this above example. Notice this mirrors the Haskell example. Its also more verbose and explicit.
// compare to Haskell example
// isEven n = mod n 2 == 0
const isEven = n => n % 2 === 0
Yep, still declarative and functional.
Look, I don't want to get caught up in a semantical debate. That would be fruitless. Many languages don't rely on line noise to make "clear" what's going on.
Check this out in Kotlin
val x = if (a > b) a else b
Check this out in Scala
val x = if (a > b) a else b
Check this out in Ruby
x = if a > b then true else false end
Check this out in Rust
let x = if a > b { true; } else { false; };
etc...
In short it's more clear and consistent by not adding line noise to JavaScript when assigning in expression context. Using parenthesis to explicitly declare an expression context, which currently is not allowed for statements in JavaScript like it is in many other language. This extends to each respective language's equivalent for doing things like switch
which might be by using guards or otherwise. Point is, they let you assign directly.
Using parens to declare an expression context is not possible in JS; it would break too much code - way more than "making certain statement-based control structures expressions" would.
Lol.
@ljharb I didn't propose making parenthesis always express expression context. Parenthesis already group expressions. We can however group if
to imply it is an expression where you'd have to use a line noise characters instead.
I was also posting this in the do proposal: https://github.com/tc39/proposal-do-expressions/issues/9
To me, it sounds most logical that if any of the
if
,switch
,for
,while
,try
keywords are used in an expression context, then it will be an implicit do expression construction:So the following
would be equivalent to:
Similarly, the following
would be equivalent to