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.31k stars 155 forks source link

Feature proposal: Enums #725

Open haskellcamargo opened 9 years ago

haskellcamargo commented 9 years ago

An enum is a very useful structure for data abstraction and I miss them in LiveScript. In Haskell, although, we can use algebraic data types to abstract our problem. TypeScript already has good support for them.

Proposed syntax

enum := "enum", ident, { "," variable }

Example

enum UserState
  Active
  Inactive
  Removed

With usage

state = UserState.Active

Transcompiling to

var UserState;
(function (UserState) {
    UserState[UserState["Active"] = 0] = "Active";
    UserState[UserState["Inactive"] = 1] = "Inactive";
    UserState[UserState["Removed"] = 2] = "Removed";
})(UserState || (UserState = {}));

I open the discussion for implementation. If approved, I accept to implement it by myself and make a pull request.

chisui commented 9 years ago

Livescript has enough arcane syntactic sweets as it is. Also this could easily be achieved with a small function. The syntax would be nearly as concise as the proposed one. Example:

Enum = ->
  elems = if typeof! &0 is \Array
    &0
  else
    &
  i = 0
  r = {}
  for e in elems
    r[i] = e
    r[e] = i++
  r

with usage

UserData = Enum <[Active Inactive Removed]>

or

UserData = Enum \Active \Inactive \Removed
robotlolita commented 9 years ago

Sum types would be more interesting:

union-stmt ::= "union" INDENT <union-case> DEDENT;
union-case ::= <identifier> "(" <identifier-list> ")" | <identifier>
identifier-list ::= <identifier> ("," <identifier>)*

Example

AST = union
  Id name
  Lambda arg, body
  App op arg

Usage

{ Id, App, Lambda } = AST
App (Lambda (Id \x), (Id \x)), (Lambda (Id \x), (Id \x))

Possible implementation

AST = ->
  class UNION;
  UNION.Id = class Id extends UNION
    (@name) ->
  UNION.Lambda = class Lambda extends UNION
    (@arg, @body) ->
  UNION.App = class App extends UNION
    (@op, @arg) ->
  UNION

But you can also implement this in userland reasonably well:

items = (o) ->
  [[k, v] for k, v of o]

get = (o, n) --> o[n]

union = (cases) ->
  class UNION
  (items cases).forEach ([name, args]) ->
    UNION[name] = class extends UNION
      (...ys) ->
        a = ^^UNION[name].prototype
        for x in args
          a[x] = ys.shift!
        return a
      toString: ->
        "#{name}(#{args.map get(this) .join ', '})"
  UNION

And use it as:

AST = union do
  Id: <[ name ]>
  App: <[ op arg ]>
  Lambda: <[ arg body ]>

{ Id, App, Lambda } = AST

console.log((Id "foo").to-string!)
console.log <| (App (Lambda (Id \x), (Id \x)), (Lambda (Id \x), (Id \x))).to-string!
haskellcamargo commented 9 years ago

I wonder why enum is a reserved word if the language natively has no support for it.

vendethiel commented 9 years ago

because it's a JS reserved word

robotlolita commented 9 years ago

Couldn't it be solved by prefixing all identifiers with _?

haskellcamargo commented 9 years ago

In the language that I'm designing, RawrLang, all the identifiers that are keywords of the target language and are unused by the source language are prefixed by "_" while transcompiling, allowing to use words like class or interface as identifier names by hygienized keywords.

haskellcamargo commented 9 years ago

This can be simply implemented in the compiler by, while consuming an identifier, verify if it is in a unused keywords table and, if true, prefixing it by an underscore and, to avoid ambiguities, verify the identifiers table and deal with ambiguous values, such as enum => _enum to _enum => $_enum and thus continuously.