7 ms·
Allowing @1 and @2 instead of |a, b| is a horrible change to Ruby. In the ruby tracker Matz himself states this is a compromise. So now Ruby is getting half-bak
by intertextuality 8y ago
Allowing @1 and @2 instead of |a, b| is a horrible change to Ruby. In the ruby tracker Matz himself states this is a compromise. So now Ruby is getting half-baked compromises that look out of place, all to appease some people making requests.
If the requested functionality is so pertinent, then a proper solution should be made, in line with Ruby's style. Not cryptic sigil soup with @ and .:, etc.
If a feature exists, it will get abused down the line. I do not want to read Ruby codebases that have @1, @2, @3, etc instead of named variables. We all know that temporary code is permanent. Just making this syntax possible is terrible.
I have to say I'm disappointed. I have enjoyed using Ruby for the last ~5 years but I disagree strongly with this syntax change.
- 0815test 8y agoThese are just De Brujin indexes! https://en.wikipedia.org/wiki/De_Bruijn_index https://en.wikipedia.org/wiki/De_Bruijn_index Far more elegant than literal lambda variables, since they save you the trouble of dealing with variable renaming and/or defining a valid syntax for local identifiers.
- intertextuality 8y agoVariable names are a good thing. When I look at someone else's code (or my own, 6 months later) I do not want to see @1, @2, @3, and so forth. I don't care if it's some existing mathematical notation concept or not, we're not discussing lambda calculus here. Naming things properly is a skill we all need to have as programmers. There are (multiple) reasons we don't call things k, d, j, z, o all over the place anymore. And for the record, I actually prefer λx. λy. x instead of λ λ 2. We do not need to be as terse as possible when writing programs or mathematical statements. This is especially egregious for Ruby, a language that has been valued for being quote, "elegant".
- yebyen 8y agoYou know, naming things is hard, and it's not reasonable to argue that your code should always be in the optimal state. There are "on the way" states, and it seems like this feature can be used well by saving you from choosing a wrong name too early in the development stages. Sometimes you haven't decided what a name should be yet. I do not want to see @1 @2 @3 in code 6 months later, either. That's the kind of thing that I'd search for in my codebase before I closed the project, and assign a name before it's too late for me to remember what that bit of code was for. (And look, how handy it's a completely original arrangement of characters that you can even grep for without straining!) But sometimes you don't have a name ready and it's actually better to defer naming the thing until you know what it is. This feature enables that. You can save yourself from picking a wrong name, come back later when the code around that @# has solidified and it's clearer what to call it. I have never seen this feature before this article, but seeing the ways that people are arguing against it has given me a better understanding of the ways that it can be used well. I've just been catching up on my tech lore and I'm watching Sandi Metz talks from RailsConfs gone by. There's one talk in particular "all the little things" where the subject is the gilded rose exercise, and how she would refactor it. In the course of the exercise the code complexity goes up (almost doubles) before the gains are realized, and suddenly there's this huge bit of code that can be safely deleted, now the complexity has gone way down and the code is good. My only point in bringing this up is that there are intermediary states that, if observed in a vacuum, we'd all agree are badly formed and simply not exemplary. This strikes me as an example of one of those features. You can come back and make it better. This is just how it will look "on the way" to something better. (I know, "you won't come back" but that's not an argument that I'm ready to accept.)
- intertextuality 8y ago> it's not reasonable to argue that your code should always be in the optimal state. This is a strawman and not what I believe. I never said your codebase should always be optimal in every way. I believe @1 instead of even the most basic of variable names is actively an anti-pattern. > That's the kind of thing that I'd search for in my codebase before I closed the project, and assign a name before it's too late for me to remember what that bit of code was for. Maybe you might, but plenty of other people will not. I have also lied to myself and added "TODO" comments in some small projects I've worked on. Months and years later I sometimes see "TODO" comments because I ended up working on something else or forgot. It's simply way too easy to not go back and refactor or fix things. There is nothing more permanent than temporary code. Sometimes you have to go back and rename things multiple times. This is vastly better than what you know full well will happen: People will immediately gravitate towards the easiest option of @1, and then never change it because they've moved onto other parts of the code or different projects, etc. We should not be adding ugly, terse syntax to programming languages because it's "just more convenient" to not name things. That is not a tenable argument for a permanent change to a mature language. Being lazy is not an argument.
- yebyen 8y ago> Maybe you might, but plenty of other people will not. I have also lied to myself and added "TODO" comments in some small projects I've worked on. So it was cheaper to leave the TODOs in the perfectly OK code, sounds like you dodged a bullet there. This strikes me as a slippery slope. Start with "@1 is not an appropriate way to represent this variable" then move onto "I actually don't like the name you've chosen" and finally arrive at "this code isn't important enough for me to take care of those TODOs I placed before I'm old and gray, remind me why are we even worried about how this variable is called, now renaming it for the _third time_?" I'll usually take a collection of "xs" and iterate over each "x" without any pretense that I'm coming back to make it better. We can agree to disagree, but I'm arguing that it's not any better to do that, and it's actually harder to grep for which makes it actively worse. This is a better option.
- 8y ago
- derefr 8y agoHere's something I write all the time in Elixir: a = some_list_of_tuples() |> Enum.map(&elem(&1, 1)) |> # more pipeline here The alternative, without De Brujin indices, is: a = some_list_of_tuples() |> Enum.map(fn x -> elem(x, 1) end) |> # more pipeline here Are you going to tell me that the second example is more readable? Personally, I find that the tiny little elem/2 expression gets lost in the line noise of the closure syntax. Reducing that syntax using an "anonymous closure" like in the first example makes it clearer to me what's going on. And, as well, in such closures, there really is no "name" for the thing that I'm processing. It's an intermediate in a destructuring expression that I'm only holding onto in order to further rip it apart. (The output has a name, say, `foo`; but if the input is just, say, `{:ok, foo}`... then what is the name of said tuple? `result`? `maybe_foo`?) Whatever name you make up there, it would only distract a future reader by making them think that it might be something important to the domain. Oh, and also, at least in Elixir, De Brujin anonymous closures "flow well" with function handles: &List.first/1 # function handle &List.first(&1) # equivalent anonymous closure --- But to put a finer point on it, in practice, 99% of the use I get out of (De Brujin) anonymous closures is just using them as "function handles but with the parameters reordered"—i.e. as a way of wrapping a closure in a combinator, without having to remember the names of combinators. Instead, with De Brujin indices, such "combinator" effects are self-evident descriptions. Elixir code: &(x.(&2, &1)) What is that? An expression that returns a version of the closure x (of arity 2) where the two input parameters are swapped. In other words, that's the C (cardinal) combinator, or the Haskell function `flip`. Except you don't need a special name for it; you just describe the effect you want then and there. Anyone who reads it can see what it does. Is the non-De-Brujin equivalent any clearer? fn a, b -> x.(b, a) end &1 and &2 are metasyntactic variables. a and b here are also metasyntactic variables. There might be a better name for what they are, but frequently there isn't, if this code is e.g. sitting in a library that does something generic with a data structure or algorithm, rather than something specific with business logic. Personally, if I'm going to be using metasyntactic variables anyway, I prefer to use ones that look different in a way that highlights them as metasyntactic variables. Just like I prefer languages that require some sigil on class-instance variables in a method to differentiate them from lexical variables. It allows both regular syntax highlighting, and the "syntax highlighter in my brain", to work more efficiently.
- kazinator 8y agoProperly used, it's just a short-hand for small expressions, where adding formally declared parameters adds 50% or more bulk to the code. {|a, b| a + b} versus {@1 + @2} type of thing. Note also that a and b are not more informative than @1 and @2. Both these examples convey one message: "I'm a function that adds two arguments". Here is a useful power: omit the smaller numbers: # TXR Lisp: 5> (mapcar (aret @3) '((a n 1 10) (b m 2 20))) (1 2) 6> (mapcar (aret @2) '((a n 1 10) (b m 2 20))) (n m) aret: a)pply list to function, which ret)urns an expression. Simply by using @20, we get a twenty-argument function with 19 don't-cares.
- ricardobeat 8y agoPerl is still available if you want to use it. Naming arguments “a” and “b” is widely considered bad practice either way, of course it’s not more informative.
- philwelch 8y agoSo you would prefer {|dividend, divisor| dividend/divisor} over {@1/@2}?
- rajangdavis 8y agoOn a team, I would prefer the former over the latter. As a single contributor that's trying to hack together an idea, the second option is not so bad; however, it will probably need to be changed far off in the future. I think named block parameters are cool in the sense that they enable some expressiveness but bad in the sense that they lose some readability. I won't know how I truly feel until I have to maintain/read a large codebase where they are used.
- philwelch 8y agoHow do you feel about the Groovy/Kotlin style that would translate (roughly speaking): [1,2,3].map{|n| n * 2} to: [1,2,3].map{it * 2} ? It's not generalizable to multi-argument lambdas, but multi-argument lambdas are the ones that are more important to name. (e.g. with reduce, {|memo, it| memo += it})
- jacobsenscott 8y agoI've never really seen a code base that is hard to work with due to "language feature abuse" (excepting inheritance). "Don't use this language feature" or your code will be hard to read is sort of the bogeyman of the programming world. Code is hard to work with because of too much coupling, or bad architecture, or no tests, or bad abstractions.
- philwelch 8y ago"Cryptic sigil soup" has been in Ruby forever. Remember that Ruby was envisioned as the successor to Perl.
- dragonwriter 8y ago> If a feature exists, it will get abused down the line. I do not want to read Ruby codebases that have @1, @2, @3, etc instead of named variables. We all know that temporary code is permanent. Just making this syntax possible is terrible. @1 and friends, as I see it, are fine in permanent code, in the right role. Like braces instead of do...end, they are good for blocks that amount to single-line lambdas, where declaring and using the variable on the same line is excessive noise that impairs rather than aids visibility. Can it be used in contexts where it's bad? Sure, but the existence of that potential is better than forcing everyone to use a worse solution in a common case.
- vidarh 8y agoRuby has always been a compromise where purity has almost always been sacrificed over convenience. It's not always obvious, until you start poking around the murkier parts of the Ruby grammar. Keep in mind Ruby has a ton of Perl/Awk style global-like-but-actually-global variables already, for example (and awk-inspired command line Switches) - it's the bastard child of Perl and Smalltalk. In this case I think that while it's slightly ufortunate to end up with "@", it will overall be clear - so much Ruby code with chained blocks are just using meaningless names on the basis that "everyone knows" what gets passed to map or collect etc. anyway, and especially in a chain where the same values often gets passed through multiple steps a positional argument will make no difference, as long as people strictly use them with common methods with known parameters.