gkz / LiveScript

LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming.
http://livescript.net
MIT License
2.32k stars 155 forks source link

Special syntax for `return cb err if err?` #980

Closed askucher closed 7 years ago

askucher commented 7 years ago

I noticed that it is too often we need to write

export getFrontendData = (data, cb) ->
   err, totalInUsd <-! contract-api.getPresaleTotalInUsd
   return cb err if err?
   err, totalEth <-! contract-api.getPresaleBalanceInEth
   return cb err if err?
   err, totalSales <-! contract-api.getTokenTotalSales
   return cb err if err?

I think we need special syntax for return if

export getFrontendData = (data, cb) ->
   cb!err, totalInUsd <-! contract-api.getPresaleTotalInUsd
   cb!err, totalEth <-! contract-api.getPresaleBalanceInEth
   cb!err, totalSales <-! contract-api.getTokenTotalSales

I can pay for this feature 50$

ozra commented 7 years ago

You could pick a different way of coding to achieve it more naturally, without adding strange syntax. Use Promises and/or async / await.

Something like this should work (untested):

getFrontendData = (data) ->>
   totalInUsd = await contract-api.getPresaleTotalInUsd!
   totalEth = await contract-api.getPresaleBalanceInEth!
   totalSales = await contract-api.getTokenTotalSales!
   return 47

(->>
   try
      ret = await getFrontendData data
      do-stuff-with ret

   catch err
      do-stuff-with err
)()

[edit: changed case-style to reflect OP's]

ozra commented 7 years ago

What's up with the negative reactions? XD

The OP obviously wants to handle any error from any of the executed statements, called in sequence, at the call site rather than individually. This example does that, and as a bonus, any stack traces will be natural, unlike when using the callback-style.

The example of course presumes that the contract-api is Promise.promisify'd (or re-written as async manually).

vendethiel commented 7 years ago

mostly just use a bit of sugar...

protect = (err-cb, fn, cb) ->
  fn (err, result) ->
    err-cb err if err?
    cb result
export get-frontend-data = (data, cb) ->
  presale <-! protect cb, contract-api.get-presale
  total <-! protect cb, contract-api.get-total
  cb null, total + presale
tcrowe commented 7 years ago

What about trying /caolan/async?

require! async

getFrontendData = (data, cb) -> # data unused?
  steps =
    total: contract-api.getPresaleTotalInUsd
    balance: contract-api.getPresaleBalanceInEth
    sales: contract-api.getTokenTotalSales
  async.auto steps, cb

getFrontendData {}, (err, res) ->
  if err? then return console.error 'error getting front end data', err
  {total, balance, sales} = res
rhendric commented 7 years ago

Now that async/await has landed in master, I think that encouraging use of those is much, much better than inventing another syntax quirk that only people staying on the bleeding edge of LS can read, especially if that syntax quirk looks like other unrelated language features (anywhere else, cb! would mean invoke cb unconditionally with no arguments). @askucher, do you have problems with Promises?

askucher commented 7 years ago

@rhendric Guys, promise requires to create a memory slot. I am working in blockchain development where we cannot use memory in order to make code look better because it costs GAS. But we still need to handle each error specifically and do not allow process go on.

You have issues in your examples:

  1. You still execute the second statement when previous is failed.
  2. You cannot obtain the value of failed statement and use +operator it produces new error.
  3. You require to use await feature where it can be omitted.
  4. You propose promise monad. It means my statement "please use monads everywhere where it is possible is right" but it is actually not. Did you ever try to use list, maybe, promise monads together?

In our case the syntax sugar is a benefit rather than problem.

rhendric commented 7 years ago

@askucher, this feature doesn't fit with existing LiveScript functionality. As written, it's a terrible syntax—overloading ! to mean something completely unrelated to ‘return nothing’ or ‘invoke with nothing’ would be a head-scratcher for anyone hearing about this feature who isn't on your team, and the trailing err is baffling to me—what use is that? Is that a keyword? Is it a variable that you're binding even though you can't use it? My most generous interpretation honestly is that you left it in by accident; even then, making cb! <- foo mean ‘invoke foo with a function that tests its first argument and, if truthy, passes it to cb and returns early’ is far too much extra information content to imbue to one extra character that, again, usually means something completely different.

I recognize that you have a need here that LiveScript isn't addressing; you want to reduce some boilerplate code and eliminate a common source of errors, and you don't want performance to be impacted. If LiveScript had macros, this would be an ideal use for them. Unfortunately, it doesn't, and in this iteration, likely never will (at least before JavaScript gets them). And in the absence of that, when making language design decisions, we can't simply come up with a new, unique shorthand for every possible repeated pattern of code. We have to choose the patterns that are common and cause a lot of pain, yes; but just as importantly, we have to choose shorthands that work well together, that can be composed with each other to deliver far more power than they would separately. LiveScript doesn't have the building blocks for marking the parameter of a function as something that causes early return, or as something that gets passed to another function, or as something that does either of the previous if truthy. If it had two of those three building blocks, I could see a compelling argument for adding the third so that you'd have a good shorthand for your use case. Designing and adding all three is a daunting proposal for a language that gets as little maintenance as LiveScript is likely to for the foreseeable future.

Furthermore, if LiveScript is going to be evolving in any direction, it should evolve with JavaScript, not away from it; and the JavaScript ecosystem is in the process of embracing Promises. In time, Promises will be made more efficient by V8 and other JavaScript engines; they may not be efficient enough now, but it's not forward-looking to assume that they'll never be and instead double down on legacy language features that attempt to solve the same problem with less powerful primitives. So I can't endorse enhancing the backcall operator to something like <-? to support your use case either; it's chasing the wrong target.

I know this isn't what you and your team want to hear. I'm sorry to disappoint. You've received several quite reasonable compromise suggestions here that could improve your situation. I recommend that you take one—vendethiel's has an easily correctable bug in it, but is otherwise probably the best tradeoff between succinctness and performance. Or, you can try writing a transformer that adapts either await syntax or backcalls to the functionality you want—@bartosz-m has published some interesting hacks in this vein here, or you can always apply the transform in JavaScript using Babel or jscodeshift or the like.