[HN Gopher] "Useless Ruby sugar": Endless (one-line) methods
       ___________________________________________________________________
        
       "Useless Ruby sugar": Endless (one-line) methods
        
       Author : todsacerdoti
       Score  : 99 points
       Date   : 2023-12-01 14:11 UTC (8 hours ago)
        
 (HTM) web link (zverok.space)
 (TXT) w3m dump (zverok.space)
        
       | polygamous_bat wrote:
       | 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 wrote:
         | I'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 wrote:
           | Just 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 wrote:
             | I 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 wrote:
               | Personally 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.
        
             | mschuster91 wrote:
             | > 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 wrote:
               | What 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 wrote:
             | Unnecessarily 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 wrote:
               | Ask 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 wrote:
             | > 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 wrote:
               | I 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 wrote:
             | Seems 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 wrote:
             | TXR 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 wrote:
           | 19 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 wrote:
             | I 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 wrote:
               | If 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.
        
         | giraffe_lady wrote:
         | I'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 wrote:
         | C# 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 wrote:
           | Exactly 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 wrote:
         | imho 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.
        
         | Someone wrote:
         | Not 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 wrote:
         | This 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)
        
         | kagakuninja wrote:
         | Not 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
        
       | lolc wrote:
       | It's amusing how they released it without nailing the arity
       | first.
       | 
       | Amusing in the sense of aaah good old Ruby still having the same
       | development style that made me stop using it long ago.
        
         | mcphage wrote:
         | > without nailing the arity first
         | 
         | What do you mean here?
        
           | lolc wrote:
           | Sorry I was short. I mean operator precedence was not
           | considered. In the first implementation, 'def =' has higher
           | precedence than 'if'. Which leads to surprising behaviour
           | with tailing ifs. The if is evaluated to determine whether
           | the method should be defined, instead of being part of the
           | expression.
           | 
           | The fact that they released this and are now considering a
           | change in precedence is a showcase of the haphazard
           | development of the lang. If the feature were useful, somebody
           | could have tried using it and noticed the issue before they
           | released it. But it's just a clever hack that adds another
           | way how methods can be written. Nobody sane will use it, but
           | the golfers cheer it into the release.
        
       | frou_dh wrote:
       | The following already worked in Ruby for single-line method
       | definitions:                   def foo(s) s.upcase * 3 end
       | foo "Hey" # => "HEYHEYHEY"
       | 
       | No ; and no =
        
         | lcnPylGDnU4H9OF wrote:
         | I'm not sure when the author added their update but they
         | responded to this point:
         | 
         | > What can I say! Once in a while, I forget how to Ruby :) This
         | does not make the argument invalid (this way of writing methods
         | is still frowned upon and never used; and still, reads like
         | "several phrases"), but it probably should've been centered
         | around the community's view at `;`.
        
           | stouset wrote:
           | Introducing new, ambiguously-parsed syntax to avoid an "ugly"
           | semicolon or a bare `end` for code-golfing purposes is an
           | extremely poor trade.
        
             | lcnPylGDnU4H9OF wrote:
             | I don't disagree, _per se_ , but I think the point is
             | stated too strongly.
             | 
             | Linting rules can help to enforce careful use of this
             | syntax even on team projects. The ambiguity can be avoided
             | or explicitly removed as demonstrated with the parentheses
             | around the expressions. It's just another syntax option for
             | defining a method of appropriate simplicity. Ruby already
             | allows for some heinous things:                 class Foo
             | private         # Not actually private         def self.bar
             | 'bat'         end       end            define_method(:".38
             | Special, the band?") { name == '.38 Special' }
             | 
             | Be smart about how you use it. A knife manufacturer ships
             | sharp blades despite the possibility that a chef might cut
             | themself with it.
        
               | trealira wrote:
               | > A knife manufacturer ships sharp blades despite the
               | possibility that a chef might cut themselves with it.
               | 
               | This is usually said about necessary but dangerous or
               | easily abusable features, like the use of raw pointers,
               | C++ templates, or global mutable state.
               | 
               | I would say that adding potentially confusing syntactic
               | sugar to a programming language is more like adding a
               | feature to a Swiss army knife that is redundant and makes
               | it unnecessarily more complex to use.
        
       | lgkk wrote:
       | I have always struggled with languages like ruby. def something
       | do end.
       | 
       | Swift JS Go Java etc just feel a lot easier for me to read if
       | that makes sense.
       | 
       | Not sure why. I've worked with ruby in the past, but never liked
       | the syntax. Same with python.
        
         | owlstuffing wrote:
         | Agreed. It's not like readability is purely subjective either.
         | Ruby and to a bit lesser extent Kotlin sacrifice readability
         | for conciseness, which in my view is a terrible trade-off.
         | 
         | We read code much more often than we write it. Conciseness is
         | great if it's reducing boilerplate. But when it hides type
         | information and linkage, it tends to obfuscate more than
         | clarify.
        
           | vidarh wrote:
           | That's funny, because to me what I love about Ruby is above
           | all that it is the easiest to read language I have worked
           | with, of several dozen.
        
       | oglop wrote:
       | I like Ruby for my own projects because of how descriptive it is
       | for when i come back to code later and need to understand what i
       | did, and it requires possibly the fewest keystrokes of any modern
       | language, which is a plus for my RSI hands.
       | 
       | But, for large projects I've learned I don't care for it. Not
       | unless the project, and team working on the project, follow
       | guidelines and conventions. Otherwise it will devolve into a
       | mess, guaranteed. And too many rubyist are hesitant to simply use
       | rubocop to enforce this at a PR or something similar. It's like
       | most of them forgot about `rake` and just having a formatting or
       | linting task.
       | 
       | But i digress. I like all these changes, but it's just me (i
       | wouldn't use them on an actual open source project yet). So Matz
       | is right to know the `end` may end ruby (seriously, my students
       | just _hate_ it) but i'm more curious to see if the community will
       | agree and follow along.
       | 
       | Also, endwise is a great extension to abstract away most those
       | `end`s being typed anyway.
        
       | t0mek wrote:
       | This is similar to Kotlin single-expression functions, which are
       | actually pretty useful:                   fun double(x: Int) = x
       | * 2
       | 
       | https://kotlinlang.org/docs/functions.html#single-expression...
       | 
       | However, in Kotlin there's no single-line constraint, so it's
       | possible to define an expression function e.g. with a long chain
       | of collection methods: `filter().map().findFirst()...`
        
         | dragonwriter wrote:
         | There's no single line constraint in Ruby, either, _as the
         | article mentions_ ; its a single expression, as in Kotlin, not
         | a single line.
        
         | adamgordonbell wrote:
         | Yeah, I'm a fan of this.
         | 
         | Was a bit of fun in scala to see if you could rewrite something
         | to be a single statement.
         | 
         | Sometimes the single statement version wasn't clearer, but
         | sometimes it really was.
         | 
         | Point free programming takes this to code golf type territory
         | and can be fun as a puzzle if not for real world code.
         | 
         | Maybe ruby just doesn't do it well?
        
         | MrBuddyCasino wrote:
         | One of my least favourite Kotlin features. Breaks the
         | uniformity of method declarations for minuscule gains, making
         | it harder to visually parse a class body quickly.
        
         | marcellus23 wrote:
         | I don't really understand what makes that better than something
         | like (from e.g. Swift):                   func double(x: Int)
         | -> Int { x * 2 }
         | 
         | It just seems less readable because it's introducing not only
         | an additional syntax for declaring functions, but also a
         | secondary meaning for the "=" operator.
         | 
         | edit: in my original version of this comment I forgot to add
         | the return type syntax, "-> Int", which Swift needs. I suppose
         | eliding the return type is a bit more terse. You could also
         | assign a closure in Swift to avoid that, like:
         | let double = { (x: Int) in x * 2 }
         | 
         | which is about as short as the Kotlin version, and has the
         | advantage of not being "special" in any sense from any other
         | Swift syntax (i.e., it's just assigning a standard closure to a
         | variable).
        
       | mvdtnz wrote:
       | Hideous language, just hideous.
        
         | trealira wrote:
         | Hideous? Why? Are the "do ... end" blocks really that bad?
         | 
         | It's surprising for me to hear this, because IMO, Ruby is among
         | the most aesthetically pleasing programming languages.
        
           | mvdtnz wrote:
           | In this case, hideous because of the overloading of =. But in
           | general it's just an ugly inconsistent mess of a language. It
           | can't decide if it's supposed to be expressive and read like
           | English (begin, end, rescue, unless, etc) or needlessly terse
           | by omitting random vowels (elsif, strftime, uniq). And then
           | there's the utter mess that large codebases get into with
           | metaprogramming.
        
             | trealira wrote:
             | Those are fair critiques. Thanks for the response.
        
           | trevor-e wrote:
           | For my brain it's an incredible amount of noise to parse
           | through compared to {}. Especially in addition to the amount
           | of things you have to keep in your head since there is no
           | typing to rely on.
        
       | whstl wrote:
       | My problem with Ruby is not so much the syntax. I kinda like this
       | one-line-function feature, and I enjoyed it when using in C#.
       | 
       | My problem is the fact Ruby practitioners have a tendency ABUSE
       | the usage of one-line-methods with LOTS of side-effects.
       | 
       | So it's not like Haskell one-line-methods.
       | 
       | Things that could be a function with 5 or 6 lines can become a
       | class with as many methods. And instead of local variables, you
       | now have to use instance variables (class fields).
       | 
       | For example, this is common in lots of codebases:
       | class UserCreator           def initialize(email)
       | @email = email           end                      def create_user
       | create_user_object             assign_admin_role
       | assign_invite_permission             call_invite_template
       | end                      private                      attr_reader
       | :email, :user                      def create_user_object
       | @user = User.create(email)           end                      def
       | assign_admin_role             user.roles << find_admin_role
       | end                      def assign_invite_permission
       | user.permissions << find_invite_permission           end
       | def call_invite_template
       | find_invite_template.call(user: user)           end
       | def find_admin_role             Roles.find_by(name: 'admin')
       | end                      def find_invite_permission
       | Permissions.find_by(name: 'invite')           end
       | def find_invite_template             Templates.find_by(name:
       | 'invite')           end         end
       | 
       | Sorry but this is not readable nor reasonable.
       | 
       | I encountered this in about 4 companies so far. Only one Ruby
       | company I worked didn't do it. That was the company that actually
       | had a very maintanable backend.
        
         | ht85 wrote:
         | I call it "assembly ruby", where registers start with "@" and
         | opcodes are defined with "def".
         | 
         | So dreadful.
        
         | cratermoon wrote:
         | https://thomascothran.tech/2023/11/readability/
        
         | JasserInicide wrote:
         | What is wrong with this example exactly? This is what you want
         | in a service object.
        
           | vidarh wrote:
           | It's reducing readability by introducing extra methods you
           | have to chase down where the method content is just as clear
           | as the method names themselves. If there are multiple clients
           | that might use those individual methods then _maybe_ some of
           | it is excusable. Personally I 'd refuse to accept a commit
           | like that if reviewing that code.
        
             | ljm wrote:
             | I notice this pattern often appears as a result of taking
             | rubocop's default settings as gospel. It pretty much forces
             | you into pointless abstraction by saying a method should be
             | less than 5 lines, or a class less than 100, or whatever,
             | even though such abstraction would offer no actual value.
             | 
             | Oftentimes when I suggest to change a rubocop default it
             | leads to epic arguments as if you're desecrating the bible.
             | In fact, it's just silly to not apply critical thinking to
             | the quality of your toolchain, and that includes
             | questioning the decisions of your linter's maintainers.
        
               | vidarh wrote:
               | In this case, though, it's not even producing shorter
               | code. It's producing longer, less readable code.
        
           | lstamour wrote:
           | The methods that start with "call_" and "find_" are
           | particularly redundant, because they aren't any simpler than
           | the code they contain. Technically true of all the methods
           | given but the rest could be excused as explaining less
           | readable syntax or for future expansion if parameters change.
           | Modules should be deep, not shallow, is one design approach.
           | This half fulfills that because the methods are private, but
           | it's also true that the method names are effectively comments
           | yet they obscure the natural reading order for the code. See
           | John Ousterhout's A Philosophy of Software Design for more on
           | this idea of deep vs shallow modules.
           | 
           | (Edit to add: another way of justifying the above service
           | design pattern is to suggest that the code is DRY because
           | you've extracted common sections to methods. This is taking
           | DRY to an illogical extreme. You _should_ repeat yourself if
           | your method calls are simple enough, and if you actually do
           | need to repeat some logic in multiple places requiring a
           | private method, you might find a better place to put it - for
           | example, a filter method.)
        
             | codr7 wrote:
             | It's not only about DRY, it's also about encapsulating
             | implementation details that you might want to change.
        
               | vidarh wrote:
               | Then encapsulate them _when you do_. In this case these
               | methods are private. They 're almost certainly not used
               | anywhere else.
               | 
               | The amount of code I see where people preemptively
               | extract methods and even add methods they don't use to
               | prepare for a hypothetical future situation that never
               | comes is staggering.
        
               | lstamour wrote:
               | The "YAGNI" principle is something I've had to learn the
               | hard way - and continue to learn it, just earlier this
               | week, in fact. YAGNI and DRY are sometimes in vicious
               | opposition. A pro tip I picked up from Gary Bernhardt's
               | Destroy All Software screencasts is to leave some DRYing
               | (or other refactoring for readability/maintenance
               | purposes) until after you write tests and are in a red-
               | green-refactor cycle. You can always DRY your code later,
               | especially now that IDEs make it so easy to extract
               | methods for you with automatic population of required
               | variables/parameters.
        
               | vidarh wrote:
               | Personally I think DRY only makes sense as long as
               | _either_ the code is shorter (here it 's longer...), _or_
               | you encapsulate logic, especially when that logic will be
               | called from multiple places in the code base. Here it won
               | 't...
               | 
               | I think _some_ of these methods might be ok in a public
               | helper class ( "go _here_ if you want to know the correct
               | way of finding these kinds of objects so we have a single
               | extension point "), but not as private methods...
               | 
               | The irony here is they try to let you read the main
               | codepath top down, but because they unnecessarily obscure
               | details they force you to jump back and forth between a
               | ton of methods instead, completely ruining any benefit.
               | 
               | I love code that lets you actually read it sequentially,
               | but then it needs to _actually_ let you do that.
        
               | mike_hock wrote:
               | How about the JUCS principle - Just Use Common Sense.
               | What do you want to see when you come back in half a year
               | to make a change? Write that.
               | 
               | What does YAGNI even mean if it can stand in opposition
               | of DRY? If it just means "you don't need to do that," and
               | "that" can mean whatever you want, then it's not a useful
               | guiding principle.
               | 
               | Example of a YAGNI violation: You're writing a piece of
               | code whose purpose is to download some kind of asset for
               | whatever thingamajigga you're working on. It just needs
               | to HTTP GET the resource from your official server. To
               | future-proof it, you also add support for using any of
               | the HTTP verbs as well as adding custom request headers.
               | Not only have you written useless code that will likely
               | never be needed, you've leaked implementation details
               | (all these HTTP details don't make sense if a future
               | version wants to use a different protocol).
               | 
               | Example of what is not a YAGNI violation: Your asset
               | downloader also needs to parse some sidechannel
               | information that the server returns as custom response
               | headers. You just assume that header names are
               | capitalized the way your server sends them and don't
               | bother with case-insensitivity. That isn't following
               | "YAGNI," that's leaving a ticking time bomb behind.
               | 
               | YAGNI is about functionality, DRY is about structure.
        
               | lstamour wrote:
               | > What does YAGNI even mean if it can stand in opposition
               | of DRY? If it just means "you don't need to do that," and
               | "that" can mean whatever you want, then it's not a useful
               | guiding principle.
               | 
               | > YAGNI is about functionality, DRY is about structure.
               | 
               | I absolutely see where you're coming from and I agree,
               | the reason DRY caught on and YAGNI hasn't is indeed the
               | vagueness of YAGNI. But I would equally argue that the
               | simplicity of DRY is what gets you in trouble if you
               | over-optimize for DRY regardless of context.
               | 
               | DRY can be about functionality: if you've already
               | implemented a feature, even if it's a library maintained
               | by another team, as long as the tests encourage your use
               | case, you should re-use existing functionality over
               | creating yet another abandoned half-baked attempt at
               | something.
               | 
               | YAGNI, by opposition, might suggest that the effort spent
               | learning the other team's library and maintaining a
               | connection to what might be brittle code is itself
               | overhead. A similar debate can be found in using an npm
               | library. DRY would argue if you already use the library,
               | keep using it. YAGNI would argue if the library is only
               | used in one place, rip it out for simplicity and replace
               | it, especially if the code is old.
               | 
               | Taken to extremes, DRY is how we end up with a left-pad
               | module on npm while YAGNI is using padStart built-in to
               | modern JS. Likewise taken to extremes, YAGNI may result
               | in missing functionality and unmaintainable spaghetti
               | code, while DRY is one possible approach to taming
               | complexity in codebases. Both are essential principles,
               | neither actually more important than the other.
               | 
               | I agree with JUCS, but it's even less defined than YAGNI
               | ;-)
        
               | mike_hock wrote:
               | This over-generalization of vague concepts isn't helpful.
               | 
               | An example of DRY would be: You're using libraries "foo"
               | and "baz," and whenever foo::get_bar() returns 1, 2, or
               | 3, you need to call (for your use-case) baz::qux(9),
               | baz::qux(31), or baz::qux(-5), respectively. So you
               | create one function that either handles both calls
               | completely, or at least maps the return value from
               | get_bar to the argument required by qux. The violation of
               | DRY would have switch statements littered throughout the
               | code that all do the same mapping manually.
               | 
               | A build-or-"buy" decision isn't about being DRY or not.
        
               | whstl wrote:
               | The encapsulation here is exactly the same as you would
               | have in a 6-line function.
               | 
               | And here's a bonus: the testability is exactly the same
               | in both cases too (unless you're breaking encapsulation
               | in your tests -- another thing that's awfully common in
               | Rails).
               | 
               | The only difference is the readability. With a 6-line
               | function you have perfectly readable code without jumping
               | around.
        
               | michaelteter wrote:
               | And as most commentors totally miss, this implementation
               | "hiding" (encapsulation) reduces coupling and makes
               | writing tests MUCH easier.
               | 
               | If you want to test all paths on a multi-line function,
               | you have to do a lot of duplication and sometimes a lot
               | more setup for each variation you're trying to test.
               | 
               | But another source of complexity is OOP itself. Mixing
               | code and data is a horrible affliction we have accepted.
               | All we need is some data structure (which can be a rigid
               | class/struct, and then a module of appropriate operations
               | which work on that data structure (or even elements of
               | that structure).
               | 
               | We can have mostly pure functions that are 100% covered
               | by tests... and if we choose appropriate names for these
               | functions it allows us to think in terms of higher level
               | "steps" (each of which we really don't care about how
               | they are implemented).
               | 
               | All that said, we're debating over relatively small
               | differences of approach in Ruby. For a really good time
               | (not), go look at Python codebases. Then some of the
               | people here who are arguing for a single function with 10
               | lines that just does the full job will find themselves
               | staring at 100 line Python functions and thinking, "we
               | should break this into individual functions" :).
        
           | whstl wrote:
           | The operations themselves are fine but the code style gets in
           | the way of readability.
           | 
           | A simple 4-line function instead of the 6-7 methods allows
           | for the same functionality, the same level of encapsulation
           | and for the same testability, without the "jumping around".
           | 
           | Here's the original text I inputted into ChatGPT that
           | generated the code above:                     def
           | create_user(email)             user = User.create(email)
           | user.roles << Roles.find_by(name: 'admin')
           | user.permissions << Permissions.find_by(name: 'invite')
           | Templates.find_by(name: 'invite').call(user: user)
           | end
           | 
           | Pretty much anyone writing this kind of class would be able
           | to understand the same code if it were written in a 4-line
           | function in one glance. This here, however severely obscures
           | the intent, the code flow and the operations.
        
         | vidarh wrote:
         | This is much more of a Rails thing than a Ruby thing, and as
         | someone who loves Ruby, I agree - when I come across code like
         | that it is cringe-inducing.
        
         | silasb wrote:
         | I don't mind this style, it's encouraged by dry-monads with the
         | do-notation. The big concern that I have with this code is it's
         | missing a service level transaction.
        
           | whstl wrote:
           | I don't have a problem with small functions in funcional
           | code, including code built around dry-monad, but this code is
           | definitely not in this category :/
        
         | pkkm wrote:
         | This is just the Clean Code style. If you read the book (or an
         | article with examples [0]), you'll see that it advocates
         | exactly this kind of thing: refactoring functions that are
         | "long" and "unreadable" because they have a dozen or two dozen
         | lines into a "clean" and "maintainable" web of tiny functions
         | calling one another. Personally, I find this style very
         | mentally straining because I have to constantly jump around and
         | keep track of a huge function call graph in my head; it's
         | almost as bad as beginner code with a thousand lines per
         | function. The popularity of fads like this in the Ruby
         | community is one of the reasons I'm glad I switched to Python.
         | 
         | [0] https://qntm.org/clean
        
           | whstl wrote:
           | "Mentally straining" is a good way of putting it. It also
           | makes debugging much harder, as you have to keep track of
           | instance variables in your head.
        
           | ht85 wrote:
           | If only functions could take arguments...
           | 
           | Doesn't that already look 100x better?
           | class UserCreator           def create_user(email)
           | user = create_user_object(email)
           | assign_admin_role(user)
           | assign_invite_permission(user)
           | call_invite_template(user)           end
           | private                      def create_user_object(email)
           | User.create(email)           end                      def
           | assign_admin_role(user)             user.roles <<
           | find_admin_role           end                      def
           | assign_invite_permission(user)             user.permissions
           | << find_invite_permission           end
           | def call_invite_template(user)
           | find_invite_template.call(user: user)           end
           | def find_admin_role             Roles.find_by(name: 'admin')
           | end                      def find_invite_permission
           | Permissions.find_by(name: 'invite')           end
           | def find_invite_template             Templates.find_by(name:
           | 'invite')           end         end
        
             | whstl wrote:
             | Slightly less worse, but the original function that I
             | inputted into ChatGPT to generate the code from my message
             | was this:                     def create_user(email)
             | user = User.create(email: email)             user.roles <<
             | Roles.find_by(name: 'admin')             user.permissions
             | << Permissions.find_by(name: 'invite')
             | Templates.find_by(name: 'invite').call(user: user)
             | end
             | 
             | IMO this 4 line function is significantly better in terms
             | of clarity, readability, and it avoids unnecessary state.
             | Testability and encapsulation are the same.
             | 
             | (I would argue that the encapsulation is better with a
             | function, since encapsulation is way too easy to break in
             | Ruby, but hey, that's me)
        
             | vidarh wrote:
             | No?
             | 
             | It provides some very marginal improvement, but doesn't
             | address the actual problem of the code, which is the
             | horrific verbosity. The use of attributes is the least of
             | my problem with it. This is how I'd want it to look:
             | class UserCreator                 # Arguably, I'd prefer
             | *call* because that allows it to be interchangeable with a
             | lambda            # But frankly this thing could *be* a
             | lambda. E.g. you could replace the above class declaration
             | # with "UserCreator = ->(email) do" and ditch the "def"
             | # The exception, where I'd allow for an initialize and
             | attributes would be in cases where you'd otherwise
             | # be passing a *lot* of *the same* state around between
             | multiple methods            #            def
             | self.create_user(email)              User.create(email).tap
             | do |user|                user.roles <<  Roles.find_by(name:
             | 'admin')                user.permissions <<
             | Permissions.find_by(name: 'invite')
             | Templates.find_by(name: 'invite').call(user: user)
             | end           end         end
             | 
             | The whole service object pattern is heavily abused by
             | people who don't seem to understand which (limited)
             | situations it actually provides benefits. This isn't one of
             | them.
        
         | michaelteter wrote:
         | While I personally prefer more functional (non-oop) approaches,
         | the example you show is positive to me.
         | 
         | If the functions are well named, you can read up to the
         | "private" line and stop reading. You now know what this thing
         | does, and you've not had to complicate your thought about HOW
         | it does it. If you need to know how one of the primary actions
         | is performed, you can jump to that source with a simple
         | keypress. With some settings, you can simply hover to see the
         | function expanded.
         | 
         | What's more, writing tests - 100% coverage - for this example
         | is trivial and clean. If you want to put the actual lines all
         | in the main function, your tests will be a lot uglier and will
         | involve more lines of test code.
        
           | whstl wrote:
           | The problem is that I need to know HOW this class does
           | something, especially when debugging.
           | 
           | The unnecessary branching makes me need to keep an eye on the
           | stack, and a lot of extra information in my head.
           | 
           | The additional state (in this case @email and @user) needs to
           | be tracked from a distance. Local variables are 100x better.
           | If there's any mutation, I must watch out during debugging
           | two or three levels deep. Often there are more variables than
           | that. If I had the "role" and the "permission" as a
           | parameter, I would need two extra instance variables. With a
           | regular function this is just two arguments, a couple lines
           | away from the usage site.
           | 
           | The original code I wrote into ChatGPT to generate the code
           | above shows me the what and the how with much more clarity:
           | def create_user(email)             user = User.create(email)
           | user.roles << Roles.find_by(name: 'admin')
           | user.permissions << Permissions.find_by(name: 'invite')
           | Templates.find_by(name: 'invite').call(user: user)
           | end
        
             | fknorangesite wrote:
             | Yes, but that's because the example we were given in this
             | thread is just a toy strawman in that each of the methods
             | are just simple one-liners anyway. So yeah, given this
             | literal code sample, I agree the private methods are
             | extraneous.
             | 
             | The point of this pattern is to extract each operation into
             | its own described-by-name method because they're each
             | longer and more complicated than just a `<<` operator or
             | whatever.
        
       | eurekin wrote:
       | Haven't worked in ruby in years, but reading it just feels nice:
       | 
       | > return [] if denied?
       | 
       | So strikingly simple and expressive, I miss that :)
        
       | lemper wrote:
       | in the ruby's ticket page that is linked in the article, one guy
       | (who seems to be a respectable gentleman, I must add), this
       | feature adds more cognitive load, requires all ides to support
       | this syntax, and encouraging less readable code. coming from
       | other programming language, I don't think that this feature is
       | adding more cognitive load. especially when I believe any Ruby
       | off Rails developer is also write js code. nor I do believe that
       | it encourages less readable code, even though I don't deep dive
       | into ruby's traditions, I believe stupid long one liners are the
       | enemy of "happy developers".
        
       | Lammy wrote:
       | I don't think "one line" should get associated with endless
       | methods. I really love endless methods for any situation where
       | the first statement in my method is also intended to be the
       | return value.
       | 
       | It lets me avoid my most hated pattern, where you instantiate a
       | thing with a temporary variable, do some stuff to it, and then
       | return the instantiated object with a hanging call to the
       | temporary variable. I've always found it very ugly and also
       | fragile if the order of operations gets mixed up by later edits.
       | This pattern:                 def do_the_thing(with_me)
       | thing = MyThing.new
       | thing.do_something_that_does_not_return_self(with_me)
       | thing       end
       | 
       | I also love to use it to avoid "double ends" for methods
       | consisting entirely of control-flow statements:
       | def do_the_thing(with_me)         case with_me         when rofl
       | then ...         when lol  then ...         when lmao then ...
       | else ...         end       end
       | 
       | With endless methods I would write these two examples as:
       | def do_the_thing(with_me) = MyThing.new.tap {
       | _1.do_something_that_doesnt_return_self(with_me)       }
       | def do_the_thing(with_me) = case with_me         when rofl then
       | ...         when lol  then ...         when lmao then ...
       | else ...       end
        
       | hoosieree wrote:
       | "how much of it fits in one page" matters. This doesn't mean that
       | cramming everything into tight subsequent paragraphs, like a
       | serious book, is a good idea: code isn't supposed to be primarily
       | read paragraph-by-paragraph. On the other hand, a two-words-per-
       | line, twenty-words-per-page nursery rhyme-like layout means that
       | one might need to scroll through dozens of pages to get "what's
       | this all about."
       | 
       | I disagree that code is "supposed to" be any particular way, but
       | in general I agree with this point. And I think it's gradually
       | being forgotten. Automatic code formatters and mandatory style
       | guides are becoming more common, and while they do raise the
       | floor (by preventing some interpretation of "bad" style) they
       | also lower the ceiling. Uniformity isn't bad, but sometimes you
       | really need certain things
       | 
       | to
       | 
       | stand
       | 
       | OUT!
        
         | mhink wrote:
         | This is my biggest gripe with the widespread use of Prettier
         | over in JS/TS land, especially when using React. It always
         | seems to introduce *more* formatting inconsistency because it's
         | only looking at line length to decide when to format. So in the
         | same block of code I'll have things like:
         | const foo = useMemo(() => {           // code         }, [
         | alfa,           bravo,           charlie,         ]);
         | const bar = useMemo(() => {           // code
         | }, [delta, echo]);              const quux = useCallback(
         | (arg0: string, arg1: number): SomeType => {             // code
         | },           [foxtrot, golf, hotel]         );
         | 
         | Which is infurating because it's not visually consistent so the
         | code starts to look like syntactic soup. It's the same thing
         | with components, really.
        
           | kevincox wrote:
           | This is also my main gripe with automatic formatting. Since
           | they don't really understand the code they almost all work
           | based on number of characters in a line. However I really
           | don't care about characters in a line (except for at the
           | extremes). What is much more important is how many ideas are
           | in the line. If I have an array with 3 very interesting
           | elements I don't want them on the same line even if they
           | happen to be quite short.
        
             | JohnFen wrote:
             | Good formatters _do_ understand the code, though. They
             | parse it much like a compiler does.
        
               | kevincox wrote:
               | They understand the syntax of the code. They may even
               | understand the types and relationships. They don't
               | understand meaning and can only have very rough estimates
               | at logical complexity.
        
               | vidarh wrote:
               | I've yet to experience a good formatter.
        
           | larschdk wrote:
           | I tried your code on prettier.io and got a result that was
           | more visually consistent than what you pasted here.
        
             | oprypin wrote:
             | I think the commenter meant that it _can_ produce such an
             | inconsistent result if the identifier names add up to
             | approximately the line length, but the example didn 't
             | actually make it so; the identifiers are much shorter than
             | that.
        
           | vidarh wrote:
           | Prettier always makes me want to rage-quit. Thankfully I'm at
           | a career level where I usually have the power to dictate it
           | be removed. A formatter that does not understand or allow for
           | careful alignment to make concepts clear is a formatter I
           | don't want near my code.
        
         | humanrebar wrote:
         | Counterpoint: if you want tools to help you edit your code,
         | like linters that supply fixes or smarter autocomplete tools,
         | you need algorithmic code formatting.
         | 
         | You can't code taste into every tool that might want to help
         | you edit your source code.
         | 
         | That being said, almost all formatters have ways to tell them
         | to go away in especially important stretches of code, with the
         | tradeoff being ugly generated code fixups for that need hand
         | massaging every time.
        
         | munificent wrote:
         | As someone who maintains an automated formatter (Dart format),
         | I agree that automated formatting raises the floor and lowers
         | the ceiling. But my experience is that it raises the floor by a
         | large amount and lowers the ceiling only a little. Most
         | importantly, it reduces _the engineering cost to reach the
         | floor_ to zero.
        
           | a1369209993 wrote:
           | > But my experience is that it raises the floor by a large
           | amount and lowers the ceiling only a little.
           | 
           | I mean, I agree that that's true at least in relative terms,
           | but in my experience that's because it raises the floor from
           | "bottomless pit" (eg IOCCC entry quality) to "much worse than
           | we usually do", whereas lowering the ceiling a proverbial
           | meter or two still means hitting my head on it (there's a
           | upper bound on how readable code can be when you need to
           | actually _read_ it).
        
           | vidarh wrote:
           | The problem for me is that the effect is that you almost
           | never make it above the floor. Maybe I'd have a different
           | view on it if I had to work with a bunch of beginners, but I
           | luckily don't.
        
       | michaelteter wrote:
       | Many people still argue that loops are more readable than
       | collection operations ("comprehensions" in Python). And many of
       | us here, myself included, would argue that there are plenty of
       | perfect cases for 1-line collection operations (such as each/map
       | etc.)
       | 
       | Any langauge feature can be used well or poorly, so singling out
       | some of these newer language features - just to show bad use
       | cases - is kind of misleading. It would be better to stop using
       | intentionally inflammatory words like "useless" and instead show
       | "when to use".
       | 
       | The one-line "endless" functions are perfect for actual functions
       | that fit on one line without additional punctuation.
       | 
       | For example, custom string manipulation utility functions read
       | like documentation.
       | 
       | def string_of_tags(tags) = tags.join(', ')
       | 
       | def fullname(person) = [person.first_name,
       | person.last_name].join(' ')
        
       ___________________________________________________________________
       (page generated 2023-12-01 23:01 UTC)