Chaining, or why you should stop returning void (2012)
jamie-wong.com
jamie-wong.com
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)
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.
Rust can a) enforce that a method is non-mutating on its object, and b) use move-semantics to return a "new" value without doing an actual clone, while preventing the "old" value from being reused elsewhere. So the immutable "builder" pattern works very smoothly and safely
What if it's a method of the data object?
Then it should either mutate the object in-place without chaining, or return a new data object with the updated value and leave the original unmodified.
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.
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.
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.Is this maybe a custom compiler? Or is this actually C++?
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_initializati...
[2]: https://en.cppreference.com/w/cpp/language/aggregate_initial...
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?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.
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.I'm a bit neutral on returning self (either explicitly or implicitly), although it enables left-to-right pseudo-expressions like 1 + 2 * 3.
So:
a foo:2; bar:4.
This sends foo: and bar: to a. But you can't do that without the cascade: a foo:2 bar:4.
That's not bar: sent to the result of a foo:, that's foo:bar: sent to a.For normal messages you have to use brackets:
(a foo:2) bar:4.
Which is ugly and asymmetrical with respect to cascades. So in Objective-S, I introduced the pipe for message chaining: a foo:2 | bar: 4.
IMHO, this works both visually and semantically, because you are "piping" the result of "a foo:2" into the message expression " bar: 4", as the receiver.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.
I have been hearing a bunch of interesting things about Dart lately and feel like I should check it out.
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.
I agree that it speaks volumes about the power of the language itself.
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.
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.
Not in a statically typed language, please! It should be a compile-time error to try and set the key `Kye`.
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.
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.
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;
}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.
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)
})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.
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.
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.
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 WithAt this point it seems like a stylistic choice.
Mutating methods should return void, period.