[HN Gopher] Chaining, or why you should stop returning void (2012)
___________________________________________________________________
Chaining, or why you should stop returning void (2012)
Author : debdut
Score : 41 points
Date : 2021-11-05 12:10 UTC (2 days ago)
(HTM) web link (jamie-wong.com)
(TXT) w3m dump (jamie-wong.com)
| AdrianB1 wrote:
| I love this kind of blog posts because not only they present
| different ways of doing things and a fairly sane discussion about
| it, but also showing the author learning from experience and
| telling about it.
|
| Young people are very creative (mostly between ages of 15 and 25)
| and that creates new ideas. Experienced people test the new ideas
| and draw conclusions. We need both to progress, the first should
| be as creative as possible and the later as analytical thinking
| as possible. It is the innovation pipeline, the first throw the
| ideas, the others refine, filter and select.
| rchaves wrote:
| A better reason to not return void is that it will make you think
| more about immutability and side-effects. Not returning void
| allow you to reduce the hidden state changes or at least to
| manage them better
| thriftwy wrote:
| These tricks ate necessary due to lack of property assignment
| syntax in languages such as Java. C# does not have this problem
| and they may start pointing fingers.
| resoluteteeth wrote:
| There are a bunch of different approaches that other languages
| that take that make this unnecessary as well.
|
| Some functional languages simply don't tend to use mutable
| objects with method/property syntax so you can just chain
| function calls to achieve the same result. (Also some languages
| have syntactic sugar to do the same thing for method calls)
|
| VB.net (unpopular as it is) has "With" statements that allow
| you to simply avoid repeating the name of the object you are
| mutating with method calls/property assignments (this looks
| very similar to a "fluent api" but it's just a serious of
| normal property assignments with the object name omitted using
| a with statement): With theCustomer
| .Name = "Coho Vineyard" .URL =
| "http://www.cohovineyard.com/" .City = "Redmond"
| End With
| thriftwy wrote:
| Functional programming won't save you from the fact that you
| don't want to have constructor with dozen unnamed parameters.
| That's also fragile in the face of code change.
| Smaug123 wrote:
| Well, the situation is a bit better than you make out. Your
| parameters may be unnamed, but they should at least be
| _typed_. Cases like `Directory.Move : IDirectoryInfo - >
| IDirectoryInfo -> unit` are a bit of a pain to give
| unambiguous types, but a twelve-parameter method in which
| every parameter has a different type (`CustomerId`,
| `BankId`, whatever - tiny types) is orders of magnitude
| less fragile than one in which everything is a string.
| thriftwy wrote:
| It's still not good enough. Java also gives you typing
| but builders and chained setters are still used.
| thrower123 wrote:
| Fluent builder patterns are wildly popular in C# however.
|
| At this point it seems like a stylistic choice.
| jedimastert wrote:
| First, needs a (2012).
|
| Second, this is the very top:
|
| > Edit from 2018: The tone of this post is a little cringe-worthy
| to me now in retrospect, and I think that many APIs are probably
| clearer when you return void. Leaving this here for posterity.
| amsully wrote:
| It's amazing how easily my brain skips a subtitle like this.
| Years of subconsciously parsing advertisements has won over my
| elementary school teachers encouragement to "read the whole
| passage."
| spicybright wrote:
| Same. Bites me more than I'd like to admit.
| Shorn wrote:
| Y'know, I've been wondering lately what's been going on with
| my reading comprehension. I just wrote off all these errors
| over the last few years as part of getting old.
|
| I've been so busy patting myself on the back for succeeding
| in not giving any attention to ads - I never stopped to think
| there might be a cost.
| avl999 wrote:
| Chaining works well in builder objects and perhaps in some GUI
| programming or some other very specialized cases but most of the
| classes I write don't have sufficient mutable state to make this
| kind of thing worth it.
|
| My advice would be to almost always start with a void (unless
| builder objects) and then add chaining if the evolution of your
| API dictates that chaining together calls would be a common
| enough usecase.
| alerighi wrote:
| This can be inefficient depending from the language and the
| compiler. The compiler many not always know that the this that
| you return is the same of the object you are calling the method
| to, and that can prevent further optimizations.
|
| I don't see either any readability advantages of doing so, doing
| that just for not typing the name of the variable is nonsense,
| also the code is less clear to the reader (you have to know that
| the method that you call returns the same objects, it's not
| obvious, it can as well return another object, or a copy of that
| object). It's more explicit doing it the normal way.
| kevinmgranger wrote:
| There's competing incentives for API ergonimics and making
| mutations / ownership explicit. Kotlin's scope functions really
| get you the best of both worlds with this.
|
| Lambdas can have a "receiver", which acts as the (implicit or
| explicit) `this` for the lambda.
|
| `apply` even returns the object itself, so the first example
| could be: frame.add(FormPanel().apply {
| setSize(300, 150) add(usernameLabel)
| add(usernameField) add(passwordLabel)
| add(passwordField) })
| ris wrote:
| You should categorically not be implementing chaining semantics
| unless your operations are non-mutating.
|
| If they are mutating and you return the same object, it's
| possible for people to go _years_ without realizing that 's
| what's happening (no, most people don't read the manual),
| assigning the result to something else, reusing the original for
| another thing and introducing subtle bugs all over the place.
|
| See the design of python's list `.sort()` returning None for this
| exact reason.
|
| (and of course there are sometimes performance penalties to
| implementing non-mutating methods, so there are lots of valid
| cases for not implementing chaining semantics)
| hcrisp wrote:
| Or at least adopt a semantic preference that informs the user
| as to what is happening, e.g. Python has .sorted() for for non-
| mutating method vs .sort() for mutating method.
| mgraczyk wrote:
| `sorted()` is a builtin function, not a method. It can still
| be confusing, but it's more clear IMO that `sorted(array)`
| will not mutate vs a hypothetical `array.sorted()`.
| eyelidlessness wrote:
| This is why I hate JS Array#sort. Its return type being an
| array gives the impression it's not sorting the original array,
| leading to a lot of hard to track down bugs.
|
| It's also one of the biggest reasons I discourage use of
| lodash. Sure, _most_ of its methods are pure and return new
| values. But some do mutate the input, and they're
| interchainable (sorry, couldn't resist). The only way to know
| which you're using is to memorize or constantly consult their
| _enormous_ API.
| theteapot wrote:
| > You should categorically not be implementing chaining
| semantics unless your operations are non-mutating.
|
| What if it's a method of the data object?
| Brian_K_White wrote:
| data should not have methods, but that's a whole other
| religeous war :)
| AtlasBarfed wrote:
| Builder pattern for complex initialization like db connections
| timando wrote:
| Return a new builder that has the changes applied.
| gpderetta wrote:
| In c++ you can make your mutating chaining operations rvalue
| qualified, have them return by value and move _this_ into the
| result. So you have purely functional operations that still don
| 't create copies in practice.
| emerongi wrote:
| I like function chaining more. E.g. Elixir's `Map.set(map, field,
| value)` function combined with the `|>` operator, which feeds the
| value from the previous operation in to the function as the first
| argument: person_map |> Map.set(:name,
| "Peter") |> Map.set(:age, 37) |>
| Map.set(:married, true)
|
| It's so much fun to look at a chain of generic functions working
| on a generic data structure, while keeping the code clear. Makes
| me feel all warm inside. Immutability and functional programming,
| ain't it nice, huh?
| aaaaaaaaaaab wrote:
| >It's so much fun to look at a chain of generic functions
| working on a generic data structure, while keeping the code
| clear. Makes me feel all warm inside.
|
| Yeah, that's usually a bad sign. Programmers who think some
| language features are "fun" usually end up overapplying them
| and making life harder for everyone who just want to do their
| job and ship stuff.
|
| Programming languages should be simple, boring and self-
| evident.
|
| Keep "fun" in your hobby projects.
| pasquinelli wrote:
| i'm confused, are you responding to their example or to their
| vibe?
| emerongi wrote:
| You do you. Software development is a large enough field, our
| paths don't have to cross.
| macintux wrote:
| Elixir (really, Erlang) has tightly-constrained semantics
| around immutability and exception handling that make this
| feature a natural application of the core concepts.
| a_lost_needle wrote:
| My functional API's never return void because they're functions
| and immutability is the benefit there. Returning void means
| you're either mutating something or interacting with something
| thing that does. Also, chaining can be dangerous, as if it's not
| implemented correctly, can hide errors, or not have adequate
| failure tolerance, and cleanup can be a mess if a stream or
| connection disconnects in the middle of your chain.
|
| But in an OO project, void is a really useful. If you're doing
| game programming, mutability is required for all but the most
| trivial games. And throwing in more functions and more variables
| on the stack isn't going to benefit anything. You click a button,
| the gun fires. I don't need to check the return, it add no value,
| adds complexity and isn't idiomatic to the language.
|
| Use the right tool for the job.
| laurent123456 wrote:
| Chaining is fine when building up an object, but definitely not
| something that should be systematically used. That's making the
| interface more complicated for little gain.
| G3rn0ti wrote:
| Why? Whenever you return ,,void" you return ,,this". That does
| not make anything more complicated. It is a pattern that suits
| itself well in front end code. When it comes to async. code,
| however, it's a different story since you return promises in
| this case that are also chainable.
| laurent123456 wrote:
| > When it comes to async. code, however, it's a different
| story since you return promises in this case that are also
| chainable.
|
| So that's one issue. That's what I mean by the interface
| being more complicated - void is void, but if you return
| something there might be some edge cases, some of which are
| difficult to predict. Personally I think it's not worth the
| trouble.
| aaaaaaaaaaab wrote:
| Yeah, how about no?
|
| Mutating methods should return void, period.
| mjgoeke wrote:
| I work in C# but have come to a similar conclusion to his note at
| the top of his article.
|
| C# supports extension methods, so if you want you can augment the
| original 3rd party class with your own (chaining in this case)
| methods. Or author your own "fluent interface" on your own
| classes, as he's done here.
|
| I'm the end, most of the time I found I was wanting a function
| 'forward composition' operator (like F#'s |> ). And the only
| place the chaining methods really made sense was factory
| classes/methods for complex declarative configuration.
|
| The only ones that stuck for us were ORM mapping declarations,
| and factories for setting up complex domain specific data setup
| in tests.
| bob1029 wrote:
| For C#, the only method chaining we really do today would be
| for LINQ (very heavy usage) and AspNetCore startup logic.
|
| If you are super addicted to certain language sugar like object
| initializers, then wrapping some of your business logic (e.g.
| mappers) with extension methods is not a terrible path. E.g.:
| public Thing MyFavoriteThing => new Thing { Id =
| Guid.NewGuid(), TheThing =
| _someLocalProperty.SomeTransform().Trim(),
| MyOtherInitalizedFact = true };
|
| The alternative would look something like:
| public Thing MyFavoriteThing() { var result = new
| Thing { Id = Guid.NewGuid(),
| TheThing = SomeTransform(_someLocalProperty),
| MyOtherInitializedFact = true };
| result.TheThing = Trim(theThing); return result;
| }
| MichaelBurge wrote:
| You could also make a single-letter alias:
| auto& r = rectangle; r.width(100); r.height(100);
|
| I like to use very short variable names(single or 3 letters at
| most) but fully write out the type: Rectangle
| r;
|
| Which is similar in spirit to aliasing the variable.
|
| "Rectangle" is probably not a good example, because rectangles
| are pure data used for many different purposes. So "rectangle" is
| often a poor variable name. But many programs use 1 type for 1
| purpose, and then the type alone is enough.
| BoardsOfCanada wrote:
| I like dart's cascade operator (..) a lot, that way you can chain
| calls on an object without the functions returning this. I'm sure
| other languages have something similar, kotlin has apply which
| feels a bit more heavy handed though.
| [deleted]
| mpweiher wrote:
| Pretty sure this was taken from Smalltalk, which uses the
| semicolon as the cascade.
|
| So a b c d.
|
| sends c to the result of sending b to a, and d to the the
| previous result etc. a b; c; d.
|
| Sends b, c, and d to a. This is often indicated visually:
| a b; c; d.
| Someone wrote:
| Pascal (https://www.freepascal.org/docs-html/ref/refsu62.html)
| and Visual Basic (https://docs.microsoft.com/en-
| us/dotnet/visual-basic/languag...) have _with_.
|
| That serves a similar purpose, (in VB only for accessing
| multiple fields in an object, I think)
|
| Visual Basic's syntax is a bit safer. Requiring that seemingly
| superfluous dot means there can't be confusion between
| referencing fields and referencing local or global variables.
|
| In Pascal adding a field to a record whose name shadows that of
| a local can change the meaning of the statements in a _with_
| statement, and removing it may silently 'fix' a broken
| reference to a no longer existing field.
|
| _with A, B, C, D do_ is asking for trouble even more.
| donatj wrote:
| Oooh I didn't know this was a thing. I had that as an idea
| recently but it's really interesting to hear it already exists!
|
| I have been hearing a bunch of interesting things about Dart
| lately and feel like I should check it out.
| ptx wrote:
| What's heavy-handed about Kotlin's apply? It's just an
| extension function in the standard library (so not a separate
| language feature, if that's what you mean by "heavy"?) and
| solves the problem cleanly IMHO.
|
| You can easily implement a similar extension function in C#,
| although the syntax is slightly uglier and you don't get the
| implicit receiver in the lambda.
| BoardsOfCanada wrote:
| I just mean it looks a bit more obtrusive to me than just ..
|
| I agree that it speaks volumes about the power of the
| language itself.
| [deleted]
| 74B5 wrote:
| It's the fluent interface pattern.
|
| https://en.wikipedia.org/wiki/Fluent_interface
| baktubi wrote:
| Yeah some of the issues with error handling are pretty serious.
| I would say in C++ it could be useful only for simple "nothrow"
| routines for basic object configuration; this would apply for
| cases when the configurations should happen after construction.
|
| Aside from that I wouldn't use it for complex algorithms.
|
| It's almost like a weird intersection between functional and
| object oriented paradigms.
|
| But there's not much benefit from these two approaches:
|
| rect.setHeight(125).setWidth(250);
|
| rect.setHeight(125); rect.setWidth(250);
|
| That being said, for algorithms similar in spirit to the
| chaining I think the C# Linq language is a good example of
| something that works well in a "chained" fashion.
| salawat wrote:
| God, I hate that pattern.
|
| Give me an API that takes a key/value pair where key is the
| name of the field to be set, and value is the value to be set.
|
| Easy to read. Great way to get people to pay attention to tge
| datamodel, Low overall maintenance burden.
|
| As opposed to: lets add another bloody withMethod() to the API,
|
| so people can write
|
| MethodsLikeThis .withThis(Thing) .andThat(Stuff)
| .atTheEndofWhich(aThingwithAnEnd) .someone(you) .hopes(new
| Hope("There is a point to it all)).but().alas("There is not.");
|
| So now my stack traces look daft when things break in a
| contrived attempt to make things prettier to read when
| otherwise it could have been:
|
| Unable to set value for key:"Kye" value:"if you spell it right,
| it will set it. Unless it isn't even the right datatype".
| Unknown field:"Kye"
|
| Build your state packet. Prime your logical machine. Push the
| button. Verify output. Do not try to turn my test suite into a
| work of effing Shakespeare.
|
| Maybe that's just me being crotchety though.
| Smaug123 wrote:
| > Give me an API that takes a key/value pair where key is the
| name of the field to be set, and value is the value to be
| set.
|
| Not in a statically typed language, please! It _should_ be a
| compile-time error to try and set the key `Kye`.
| flohofwoe wrote:
| The "Galaxy Brain" version of this advice is using plain old
| designated initialization of 'option bag' data structures. Here's
| a non-trivial C99 example from the sokol_gfx.h API to create a
| pipeline-state-object for 3D rendering:
| state.pip = sg_make_pipeline(&(sg_pipeline_desc){
| .layout = { .attrs = {
| [ATTR_vs_position].format = SG_VERTEXFORMAT_FLOAT3,
| [ATTR_vs_color0].format = SG_VERTEXFORMAT_FLOAT4
| } }, .shader = shd,
| .index_type = SG_INDEXTYPE_UINT16, .cull_mode =
| SG_CULLMODE_BACK, .depth = {
| .write_enabled = true, .compare =
| SG_COMPAREFUNC_LESS_EQUAL, }, .label =
| "cube-pipeline" });
|
| Chaining might be useful for data processing pipelines, but not
| for simple data initialization tasks IMHO.
| jimbob45 wrote:
| I know C is an extremely deep language but that doesn't look
| like C99 to me. Since when can you set struct member variables
| piecewise in a...function definition?
|
| Is this maybe a custom compiler? Or is this actually C++?
| nwellnhof wrote:
| What you're seeing is a compound literal with designated
| initializers. Both features were introduced in C99.
| rackjack wrote:
| It is C99, which surprised me too.
|
| https://stackoverflow.com/a/7487949/11736447
| chisquared wrote:
| I encountered code like this in a C99 project recently. I
| still don't understand it, but I believe it's supposed to be
| possible.
| kevin_thibedeau wrote:
| It's a C99 compound literal with designated initializer. C++
| doesn't support it.
| MayeulC wrote:
| As other commenters have pointed out, those are designated
| initializers[1], one of my favorite C99 feature.
|
| I's also been picked up recently for inclusion into C++20[2].
|
| However, it seems that C++ adds a specific limitation:
|
| > _all designators used in the expression must appear in the
| same order as the data members of T_
|
| > _out-of-order designated initialization, nested designated
| initialization, mixing of designated initializers and regular
| initializers, and designated initialization of arrays are all
| supported in the C programming language, but are not allowed
| in C++_
|
| Which limits the usefulness, as I've used this syntax to let
| myself reorder struct fields (and add some) without breaking
| the API (while obviously breaking the ABI). It's a great tool
| to use while your API is still in flux.
|
| At least an error will be produced, which is much better than
| just providing values and hoping that the API/ABI remains
| stable.
|
| [1]: https://en.cppreference.com/w/c/language/struct_initiali
| zati...
|
| [2]: https://en.cppreference.com/w/cpp/language/aggregate_ini
| tial...
| matheusmoreira wrote:
| Designated initializers is the most amazing feature of the C
| language. I don't understand why no other language ever copied
| it...
| jmercouris wrote:
| It exists in Lisp, even before C.
| AtlasBarfed wrote:
| Groovy has this ability I think if I can parse that code. My
| C code experience predates C99
___________________________________________________________________
(page generated 2021-11-07 23:02 UTC)