6 ms·
Am I the only one whose soul hurts looking at this syntax? Not a ruby user, but as soon as I saw the use of = to define the function body I immediately thought
by polygamous_bat 3y ago
Am I the only one whose soul hurts looking at this syntax? Not a ruby user, but as soon as I saw the use of = to define the function body I immediately thought of all the ambiguous statements one could write with that. And voila, the whole irks and quirks section validated those fears.
Please explain to this ruby noob if you have time, why use =? Why not some other less used symbol or symbol pair instead? What are they trying to achieve with this?
- stouset 3y agoI’ve been using Ruby professionally for 15 years, and have a deep love for the language. This entire syntax was unnecessary, undesired, and is flat-out a mistake. I’ve felt this way about most of the syntax changes since 2.0, with the exception of named parameters.
- progne 3y agoJust 12 years full time with Ruby here, I'm a noob. But when I see that I could replace def initialize(text) @text = text end def inspect "#<#{self.class} #{text}>" end def ==(other) other.is_a?(Word) && text == other.text end with def initialize(text) = @text = text def inspect = "#<#{self.class} #{text}>" def ==(other) = other.is_a?(Word) && text == other.text I start drooling a little. That's 3 lines to replace 11. We have a soft limit on class length of 100 lines, and I like the extra conciseness this allows. You can also do it like define_method(:inspect) { |text| "#<#{self.class} #{text}>" } and we do that in some places, but that extra verbosity makes for more lines.
- Pxtl 3y agoI feel like I'd still want the body on its own line from the signature definition just for readability. And with the parser problems described in TFA, I'd say that parens should be mandatory coding-style enforced by linter. def initialize(text) = (@text = text) def inspect = ("#<#{self.class} #{text}>") def ==(other) = (other.is_a?(Word) && text == other.text) seems like a good compromise. edit: this is similar to our standard when writing one-liner methods in C#. A bit noisier because of types and visibility, but still pretty concise, imho. public void Initialize(string text) => Text = text; // I actually hate the above a bit because "=>" // originally implied functional/no-side-effects // in C#, but the world moves on. public string Inspect() => $"{this.GetType().Name} {text}>"; public static bool operator== (Word a, Word b) => a.Text == b.Text;
- vidarh 3y agoPersonally I'd never let your first example through a code review. If you're going to break it up, use end. Where the one line format makes sense is when the body is trivial and often when there are multiple related methods that can be lined up to highlight their similarities and differences in the single line format.
- Pxtl 3y agoHah, I'm a firm "no alignment allowed, indent only" reviewer, so that use case would never even occur to me.
- vidarh 3y agoI can't stand that. It dramatically reduces code readability to me.
- Pxtl 3y agoI tend to agree with Google on this one: https://google.github.io/styleguide/jsguide.html#formatting-horizontal-alignment https://google.github.io/styleguide/jsguide.html#formatting-... > Terminology Note: Horizontal alignment is the practice of adding a variable number of additional spaces in your code with the goal of making certain tokens appear directly below certain other tokens on previous lines. > > This practice is permitted, but it is generally discouraged by Google Style. It is not even required to maintain horizontal alignment in places where it was already used. > > Here is an example without alignment, followed by one with alignment. Both are allowed, but the latter is discouraged: { tiny: 42, // this is great longer: 435, // this too }; { tiny: 42, // permitted, but future edits longer: 435, // may leave it unaligned }; > Tip: Alignment can aid readability, but it creates problems for future maintenance. Consider a future change that needs to touch just one line. This change may leave the formerly-pleasing formatting mangled, and that is allowed. More often it prompts the coder (perhaps you) to adjust whitespace on nearby lines as well, possibly triggering a cascading series of reformattings. That one-line change now has a "blast radius." This can at worst result in pointless busywork, but at best it still corrupts version history information, slows down reviewers and exacerbates merge conflicts.
- mschuster91 3y ago> We have a soft limit on class length of 100 lines, and I like the extra conciseness this allows. The "compact" line format is something that is very information dense and really really hard to parse for a human's eye, compared to the clearly visually structured prior format. Personally, if I'd see something like that outside of a code golf tournament, I'd run because that kind of code density means that at least one of the developer(s) believes themselves to be some kind of code wizard who loves Matrix-style display, and it means that new developers have a very steep learning curve ahead of them.
- kbenson 3y agoWhat can be good, but requires discipline, is to replace some of the syntax that's no longer needed with a comment to help explain the what and why and goal. You reduce or eliminate the line savings, but overall may increase the information and understanding of intent. I do this commonly in the Perl I write. If I'm using a complex set of maps and greps to reorganize a structure, doing that inline can be useful to the developer because it can match mental state, but the later reader may be somewhat lost when seeing it depending on how much of the data state they've internalized. A nice comment to explain the intent is useful, and when you're already saving space by eliding syntax, keep a good ratio of action to code on the screen (which is really what we're optimizing for here most times anyway, right?)
- stouset 3y agoUnnecessarily cramming more density into one line to avoid soft limits seems like a poor tradeoff. Ask any typesetter. Blank space used well gives text breathing room, making it easier to parse visually. In the former example, it is extremely easy to pick out what each method name is and a decent idea of what it does at a glance. In the latter, I have to read to even see the method names.
- kazinator 3y agoAsk a typesetter? No thank you. What is a typesetter? Someone or something which takes tree-structured sentences, cranks them into a linear sequence of words, which is arbitrarily chopped into a rectangular form, with the goal being that the rectangle appear evenly gray from a distance. Sometimes ugly hyphens are inserted into the middle of tokens.
- sdf4j 3y ago> We have a soft limit on class length of 100 lines, and I like the extra conciseness this allows I'd recommend your team to stop code-golfing... Even better: with this new syntax you MUST reduce the soft limit to around 50 lines per class, right?
- progne 3y agoI agree that it shouldn't be used for anything complex, even if it reduces to one line. But I do like it for simple things. Maybe it's staring at very similar code all day, but such short statements read fluently for me. And even if an extra line of separation is used, it's still 6 lines instead of 11.
- jlarocco 3y agoSeems counter productive to me. The first snippet is much easier to read, and using this technique to get around your 100 line rules seems to be missing the point of the rule - or purposely subverting it.
- kazinator 3y agoTXR Lisp: 1> (defstruct (word text) () text (:method print (me stream pretty-p) (put-string `#<@(typeof me) @{me.text}>` stream)) (:method equal (me) me.text)) #<struct-type word> 2> (new (word "abc")) #<word abc> 3> (equal *2 "abc") t You wouldn't define a print method for a struct like this, because it's counterproductive. You're throwing away print-read consistency in exchange for no benefit. I usually define print methods for complex structures with many slots, that are not expected to be externalized. Particularly if they are linked in a graph structure, where (even if you have the circular printing turned on to handle the cycles) the prints get large. You know, you wanted to just print the banana, but it was pointing to a gorilla, and basically a dump of the whole jungle ensued.
- vidarh 3y agoYou wouldn't define an `inspect` method for this simple example in Ruby either, because Ruby provides a default inspect that'd do just fine in this example. The exact same tradeoff for when to define inspect as for when you'd define print methods exist. In other words, that wasn't the point.
- vidarh 3y ago19 years here, and my terminal, editor, file manager and X window manager are all pure Ruby so as you can tell I love Ruby a lot, and frankly I love these changes - they've allowed me to reduce the size of my code in ways that makes it clearly more readable. Nobody forces you to use them. I for one is extremely happy with most of the changes in recent years.
- stouset 3y agoI am forced to read them and fix bugs related to the ambiguous syntax parsing. “Nobody forces you to use it” can justify just about any insane addition to a programming language. Nobody forces you to use any of C++’s zillion and one features, but because they exist you have to contend with their consequences even if you try to limit yourself to modern conventions. We didn’t need two ways to define methods, one of which parses insanely in order to save a few keystrokes. Adding this didn’t make the language better, it made it more complicated for virtually zero net gain.
- vidarh 3y agoIf you're not in a team where you're in a position to influence the choices used, this will be far down the list of your problems. My sympathies. We have several more ways to define methods already, and always have. If you think this gives us two perhaps the problem is you don't know the language very well. Choices like this, to create options to adjust how the code reads has always been central to the design of Ruby. It's an odd thing to be annoyed with Ruby over. "Parses insanely" is highly subjective. It parses exactly as you should expect it to if familiar with Ruby. Some of it is not ideal, and will likely be addressed with adjustments to the grammar. In the meantime the simple fix is to add parentheses whenever in doubt.
- stouset 3y ago> We have several more ways to define methods already, and always have. If you think this gives us two perhaps the problem is you don't know the language very well. Resorting to semantic-lawyering doesn’t add to your argument, it detracts from it. You know the point I was making.
- deleted 3y ago[deleted]
- giraffe_lady 3y agoI've never seen this used, it's obscure and seems to be disliked/avoided by most people who do know of it. Flat out against the style guides of a lot of ruby codebases.
- SideburnsOfDoom 3y agoC# has something similar, but uses the existing lambda syntax, with "=>" to the left of the body or expression, e.g. int AddOne(int x) => x + 1; or void SetXToZero() => x = 0;
- polygamous_bat 3y agoExactly what I am talking about when I say use some other unambiguous token. = is so incredibly overused in a programming language, why give it even more jobs?
- Pxtl 3y agoimho the "def" keyword saves it from being too ambiguous, since it's clearly defining a function/method. def [functionname] = [body] seems fine to me. The big flaw (that it can't tell the difference between the end of the body and the end of the statement line) would be the same with any token, I think. From what I'm seeing, though, they should've made parentheses mandatory around [body] until they could fix the parser problem. They could easily make them optional in some hypothetical future where they've corrected the issue. That said, it seems like C# wins at solving this problem, where the `=>` syntax is used for both lambdas and one-liner functions.
- deleted 3y ago[deleted]
- Someone 3y agoNot a Ruby user, I expect it is because they want as few implementation details as possible to seep into the syntax. If you’re thinking functional, everything looks like a function. Examples (pseudocode that may be valid Ruby) x = 3 def x = 3 def x() return 3 end all define a parameterless function called that always returns 3. So why would you use different syntax for them? There’s only one reason: impure functions. For these 3: x = rand() def x = rand() def x() return rand() end The first would call rand() once, the other two each time they get called. That, I expect, is why Ruby still has def. In scala, there’s similar thinking, which also let to them using () not only for specifying function arguments, but also for indexing into arrays or dictionaries. After all, an immutable array x = [10,20,30] behaves the same as def x(i) switch i case 0 return 10 case 1 return 20 case 2 return 39 otherwise throw indexOutOfBounds end Of course, if you unify notation, you’d also have to make these things perfectly interchangeable everywhere. Not being able or wanting to do that is a good reason for keeping syntax different. Another one is that you may want to expose the pesky detail that indexing into an array typically is a lot faster than calling a function to the programmers, so that they can get a decent idea about the performance of code they read. That can only work if you keep your execution model simple, though, and if your programmers can reasonably predict what the compiler does, and how that will run on the cpu. That worked fine for C in the 1970s, but not so well anymore today. I think Golang is an attempt to get back there.
- ysavir 3y agoThis is my reaction as well. I like the idea of collapsing a simple method into a single line, but the `=` makes it such a weird thing to actually parse. I wonder what prevented them with using a proc-like braces syntax, which would be intuitive for those familiar with the language (and the `def` to set it apart from procs)
- vidarh 3y ago{} is already overloaded for both blocks and Hash literals, both of which commonly occur at the end of the argument lists. It seems like it'd create far more opportunities for making the code hard to read.
- kagakuninja 3y agoNot a Ruby guy, but Scala uses that syntax for all method declarations: // type annotations are optional, if the compiler can infer them def foo = 42 def bar(x: Int): String = { // stuff } It works fine IMO. Scala 3 introduced Python-style syntax, in which {} is optional: def bar(x: Int): String = // stuff end bar // end is optional