Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_ placeholder #19

Closed
paulmillr opened this issue May 21, 2012 · 71 comments
Closed

_ placeholder #19

paulmillr opened this issue May 21, 2012 · 71 comments
Assignees

Comments

@paulmillr
Copy link
Contributor

What do you think about using it as a placeholder var in languages like Scala? Not sure it's very usable in current way. Or maybe i'm looking the wrong place.

[1 2 3].map(_ * 2).filter(_ > 5).reduce(_ + _)
[1, 2, 3]
  .map(function($0) {return $0 * 2;})
  .filter(function($0) {return $0 > 5;})
  .reduce(function($0, $1) {return $0 + $1;})
@michaelficarra
Copy link
Contributor

Wouldn't partially-applied operators be more useful?

[1 2 3].map (2 *) .filter (> 5) .reduce 0, (+)

@paulmillr
Copy link
Contributor Author

It isn't just operators. Useful in object methods case too

['aids ' ' cancer' 'hitler ' 5123].map(_.toString!).map(_.trim!).filter(_.length > 5)
['aids ', ' cancer', 'hitler ', 5123]
  .map(function($0) {return $0.toString();})
  .map(function($0) {return $0.trim();})
  .filter(function($0) {return $0.length > 5;})

@satyr
Copy link
Contributor

satyr commented May 22, 2012

The Scala way looks nice and concise, but also is quite complex to properly understand/implement.

Coco's solutions are it and @@, each of which required one line patch (lazy me):

[1 2 3].map(-> it * 2).filter(-> it > 5).reduce(-> @@0 + @@1)

@gkz
Copy link
Owner

gkz commented May 22, 2012

I think the current way (see satyr's post) is pretty good, but I have some ideas for improving it further in the future.
If only operators were just functions as in Haskell. Unfortunately that wouldn't be very efficient.

@gkz
Copy link
Owner

gkz commented May 26, 2012

@paulmillr I've just pushed up prelude.ls to GitHub.

With it you can do:

[1 2 3] |> map times 2 |> filter gt 5 |> fold1 add
#=> 6

I've added it to http://gkz.github.com/LiveScript/ - you may need to refresh to clear you cache.

@paulmillr
Copy link
Contributor Author

@gkz sure, i'll try it, still not sure about things like _.toString (prop access).

@satyr if _ would be a keyword, if will be super simple to implement (of course, this would disallow underscore.js usage, also I don't care, but for those who care _ can be other keyword etc).

@satyr
Copy link
Contributor

satyr commented May 26, 2012

will be super simple to implement

The rule for Scala's _ is actually pretty complex.

See: https://github.com/taku0/placeholder_syntax_for_coffeescript/blob/master/src/placeholder.coffee

@paulmillr
Copy link
Contributor Author

oh. I overguessed this.

@gkz
Copy link
Owner

gkz commented May 29, 2012

I've added partially applied operators, you can now do

[1 2 3] |> map (* 2) |> filter (> 5) |> fold1 (+)

@gkz
Copy link
Owner

gkz commented May 29, 2012

@paulmillr also you can use lookup (gets property) and call (calls property) from prelude.ls to do

['moo  ' ' dog' 'cat ' 5123] |> map call \toString |> map call \trim

@michaelficarra
Copy link
Contributor

I've added partially applied operators

Wow, I love this feature. Amazing! Though I don't think you implemented it for all operators:

$ livescript -bce "map (&&& 1) [1 2 3]"
Error: Parse error on line 1: Unexpected 'BITWISE'

@paulmillr
Copy link
Contributor Author

Yep, pretty awesome thing.

What do you think about making this lazy for high performance if more than one |> is used?

@gkz
Copy link
Owner

gkz commented May 30, 2012

@michaelficarra glad you like it, and yeah I haven't done all the operators yet. Will do that soon.

@paulmillr not really sure what you mean - could you explain further?

@paulmillr
Copy link
Contributor Author

haskell evaluates result not immediately, maybe there's some way to do the same with livescript. but nvm, it ain't a problem.

The problem I see is pretty unreadable / hard-to-debug code. See #24.

@gkz
Copy link
Owner

gkz commented May 30, 2012

@michaelficarra I added the rest of the binary ops

@gkz
Copy link
Owner

gkz commented Jun 21, 2012

@paulmillr
No need to use call and lookup anymore, you can do:

(.length)

equivalent to

((x) -> x.length)

and

(.join \*)

is equivalent to

((x) -> x.join \*)

unlike call you can use arguments.

So now

['moo  ' ' dog' 'cat ' 5123] |> map (.toString!) |> map (.trim!)

Currently on master - I will release a new version of LiveScript soon.

@paulmillr
Copy link
Contributor Author

@gkz yep, just saw the commit, looks cool, thanks.

@gkz
Copy link
Owner

gkz commented Jun 21, 2012

What other functionality of Scala's _ can we tackle?

@paulmillr
Copy link
Contributor Author

any way to write this?

edit 3: changed examples to better

val b = fn(1, _, 3)
val c = b(2)

@satyr
Copy link
Contributor

satyr commented Jun 21, 2012

What about:

  • (x.) => -> x[it]
  • (?.p) => -> it?.p
  • ((a)) => -> it(a)

@satyr
Copy link
Contributor

satyr commented Jun 21, 2012

Also: (a +=) => (-> a += it) etc.

cf.

var a = 0
1 to 3 foreach (a += _)
println(a)  // 6

@vendethiel
Copy link
Contributor

One thing I just thought ... I'd like to note that this introduces a regression.
it[(* * 3)-2]
Coco produces it[it.length * 3 - 2];
while LiveScript gave me Parse error on line 1: Unexpected ','
I don't have any perfect solution on the top of my head, maybe switch * to something else.

@gkz gkz reopened this Jun 21, 2012
@ghost ghost assigned gkz Jun 21, 2012
@vendethiel
Copy link
Contributor

If you do want to change : $ or _, when alone. Can be modifed, but usually refers to some jQuery/zepta & Underscore, so I don't see the point of using ary[window.nderscore], but anyway ...
My proposition is the bare "@", which IMHO has a meaning (it's a this.length somehow).

@adrusi
Copy link

adrusi commented Jun 22, 2012

@satyr

  • (x.) => -> x[it]
  • (?.p) => -> it?.p
  • (a +=) => a += it

all make sense, but ((a)) => it(a) makes me cringe. Since livescript will at some point introduce a low-precedence application operator like haskell's $, that should be used instead.

@adrusi
Copy link

adrusi commented Jun 22, 2012

@Nami-Doc I like bare @ as a placeholder if a placeholder syntax is implemented (which I am mostly indifferent to, I prefer Haskell style operator currying to Scala style implicit placeholder functions). @ has some semantic relevance as a placeholder "fill in the value at this location," albeit not as much as _

@vendethiel
Copy link
Contributor

I'm affraid $ and _ are over-used

@adrusi
Copy link

adrusi commented Jun 22, 2012

@Nami-Doc yes, definitely. $ isn't even supposed to be used by people, the spec says it's intended for use by automatic code generators. I believe it was prototype.js that started the trend. No idea why _ is used so much, maybe just because people can't use $ since jQuery is a near-mandatory include for browser javascript.

@gkz
Copy link
Owner

gkz commented Jun 29, 2012

@satyr (a ?) compiles correctly now:

(function(__x){
  return typeof a != 'undefined' && a !== null ? a : __x;
});

@gkz
Copy link
Owner

gkz commented Jun 29, 2012

@paulmillr for your example

callbacks.forEach (_ generatedFiles)

There is:

callbacks.forEach (<| generatedFiles)

Which compiles to

callbacks.forEach(function(__x){
  return __x(generatedFiles);
});

I think I'm done with this issue. I'll make separate issues for any unresolved items I want to do.

@satyr
Copy link
Contributor

satyr commented Jun 29, 2012

@satyr Yes, there are problems with ? meaning so many different things. What do you propose the various partially applied forms of it should be? I'm not sure yet.

I guess this is where Scala's approach would work better than Haskell's. Our operators are too inpure and/or whitespace-sensitive.

I'm more confident with sticking to it/@@ seeing the mess.

@vendethiel
Copy link
Contributor

I must agree :/.
Or write tons and tons of tests

@gkz gkz reopened this Jun 29, 2012
@gkz
Copy link
Owner

gkz commented Jun 29, 2012

I just pushed up 5-6 commits.

The |>> pipe operator is gone. Use pipes, partial applied ops and functions.

The placeholder for both partial application and backcalls is the underscore _

Eg.

three-add = (x, y, z) -> x + y + z
g = three-add 2, _, 10
20 == g 8

and

(x, y) <-- map _, [2 3 4]
x + y

You can use the underscore as alias to otherwise in a switch statement:

switch
| empty xs => []
| n < 0    => []
| _        => xs[n]

@paulmillr
Copy link
Contributor Author

yaaaaay

thanks dude! looking forward for more stuff it will be doing

@gkz
Copy link
Owner

gkz commented Jun 29, 2012

What more could it be used for?


The tendons in my right hand have been hurting for several days, so I think I'll need to rest them. No new stuff or docs for a bit.

@paulmillr
Copy link
Contributor Author

The main reason of using _ is a complex lambdas, that cannot be simply shimmed by prelude, but that are still referentially transparent. So, just compiling it to lambdas will be cool.

nodes
  |> map (parseInt _.innerText.trim!toString!)

This will replace strange ? syntax:

nodes
  |> map (_?.innerText)

I think i'll find more uses of this in a few days, looking around real code. Scala doesn't has stuff like list |> fold1 (+), so the simple cases are already covered by livescript.

@adrusi
Copy link

adrusi commented Jun 30, 2012

How about this:

  • no placeholder-based function I can think of is a valid identifier
  • backticks are only valid when around a single identifier
  • wrap the placeholder function in backticks

This solves a number of issues.

  • _ can be overloaded because it's in a special context so the only place where a variable named _ couldn't be accessed is within backquotes, which is ok with me at least
  • it makes it very clear where the placeholder function starts and ends
  • you can also overload a few other syntaxes within the backquotes to make other argument accessors shorter (ie: a 1-char version of @@; I'll use % in my examples for consistency with clojure, and it will be 1-indexed instead of 0 indexed like @@)

examples of the beauty:

fold1 `_ + _`, [1 to 5]

fold1 `%1 + (%1 - %2)`, [1 to 5]

nodes
  |> map `parseInt _.innerText.trim!toString!`

to allow nesting, it could detect parens inside backticks and not close the placeholder-defined function until all inner parens are closed. I think this would be the trivial way to do it by defining the grammar as 'BACKTICK Expression BACKTICK'.

the one problem I can think of is backtick-delimited infix function within these backquotes:

`4 `plus` 5`

but I think it's fine to just not allow that infix syntax.

@paulmillr
Copy link
Contributor Author

@adrusi meh. 2complex syntax. placeholder.coffee distinguishes vars and real placeholders.

@adrusi
Copy link

adrusi commented Jun 30, 2012

@paulmillr but it's not always clear at what point the function begins and ends.

how is it too complex. you don't need to have the additional elements that I suggested, I was just saying that they're possible.

besides, you often have to surround placeholder-defined functions with parens, which the backticks take care of for you, so often it's the same number of chars too.

edit: and, no placeholder.coffee does not distinguish between ALL vars and placeholders. if global._ or window._ is defined in another source file, it won't detect it. and guess what underscore.js does...

@satyr
Copy link
Contributor

satyr commented Jun 30, 2012

Scala doesn't has stuff like list |> fold1 (+)

Dunno why, but Scala also allows:

scala> 1 to 3 map (2*)
res1: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6)

@paulmillr
Copy link
Contributor Author

@satyr oh. I didn't know. Interesting.

@adrusi function begins at ( and ends at ). That's simple and consistent.

@paulmillr
Copy link
Contributor Author

@satyr more interesting, I didn't even saw any code with this. All folks use _. Maybe it's considered as more readable or so.

@adrusi
Copy link

adrusi commented Jun 30, 2012

@paulmillr I think that the more haskell-like syntax is newer and that the old way remained a best practice to maintain consistency with older code.

function's beginning and ending with ( and ) is okay-er I guess, but there's a lot of ( and ) to account for why not backticks instead, since they're not used for (almost) anything else.

Edit: functions delimited by () doesn't allow stuff like

`_ + (_ - _)`

@adrusi
Copy link

adrusi commented Jun 30, 2012

also, I'd like to point out that

nodes
  |> map (parseInt _.innerText.trim!toString!)

can already be written in a fairly concise and readable way:

nodes
  |> map (.innerText.trim!.toString!) >> parseInt

which I'd say is even more readable because the order that things are done in flows from left the right.

@paulmillr
Copy link
Contributor Author

real-world code. what I mean is this _ will allow to write code in consistent style without bullshit.

# Beautiful, innit

# 1
Object.keys(config.files)
  |> filter (_ in types)
  |> map (config.files[_].order)
  |> fold {before: [], after: []}, (memo, array) ->
    array ?= []
    {
      before: memo.before +++ (array.before or [])
      after: memo.after +++ (array.after or [])
      vendor-paths: config.paths.vendor
    }

# 2
files
  |> map (_.path)
  |> filter (/_test\.[a-z]+$/.test _)
  |> map (_.replace //\\//g, '/')
  |> map (path) -> path.substring 0, path.last-index-of '.'
  |> map ("window.require('#{_}');")
  |> lines

# 3
plugins
  |> map (_.include)
  |> map (call-function-or-pass)
  |> filter (_?)
  |> fold [], (_ +++ ensure-array _)

@adrusi
Copy link

adrusi commented Jun 30, 2012

# Beautiful, innit

# 1
Object.keys config.files
  |> filter (in types)
  |> map (-> config.files[it]) >> (.order) # I created an issue to allow for `(config.file.)` since that is a syntax error ATM.
  |> fold do
       before: []
       after: []
       (memo, array ? []) ->
         before: memo.before +++ (array.before ? [])
         after: memo.after +++ (array.after ? [])
         vendor-paths: config.paths.vendor

# 2
files
  |> map (.path)
  |> filter /_test\.[a-z]+$/.test
  |> map (.replace //\\//g, '/')
  |> map -> it.substring 0, it.last-index-of '.'
  |> map ("window.require('" +) >> (+ "');") # in this case `_` placeholder is indisputably better
  |> lines

# 3
plugins
  |> map (.include)
  |> map call-function-or-pass
  |> filter (!== null) # meh...
  |> fold [], (-> it +++ ensure-array @@1) # also here

if you introduce my proposed syntax:

# Beautiful, innit

# 1
Object.keys config.files
  |> filter (in types)
  |> map `config.files[_]` >> (.order) # I created an issue to allow for `(config.file.)` since that is a syntax error ATM.
  |> fold do
       before: []
       after: []
       (memo, array ? []) ->
         before: memo.before +++ (array.before ? [])
         after: memo.after +++ (array.after ? [])
         vendor-paths: config.paths.vendor

# 2
files
  |> map (.path)
  |> filter /_test\.[a-z]+$/.test
  |> map (.replace //\\//g, '/')
  |> map `%1.substring 0, %1.last-index-of '.'` # notice that this works a lot better
  |> map `"window.require('#{_}');"`
  |> lines

# 3
plugins
  |> map (.include)
  |> map call-function-or-pass
  |> filter `_?`
  |> fold [], `_ +++ ensure-array _`

the advantages of my proposal (I don't want to sound like a salesperson, but you know) are that you can have placeholder-defined functions that will work even if they need a parameter within nested parens, it doesn't overload _ globally, and it allows for more fine-grained control over argument order. I still suggest that point-free style be used where possible and readable.

@gkz
Copy link
Owner

gkz commented Jul 3, 2012

You can do

(config.files.)

for

(-> config.files[it])

also,

(?)

is

(-> it?)

Okay, I think I'm done with this now. If you want stuff more complex than what is availible, you can just use -> it or -> @@0 etc.

@gkz gkz closed this as completed Jul 3, 2012
@gkz gkz reopened this Jul 3, 2012
@gkz
Copy link
Owner

gkz commented Jul 4, 2012

& is the alias for arguments instead of @@ now. The cons operator is gone.

Thus, for a contrived example

add-three-numbers = -> &0 + &1 + &2

That's it, for real this time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants