1. 47

  2. 5

    In my own code, if I ever have methods return self, it is because I am trying to implement some sort of chainable API. This pattern is popular when constructing objects in Rust, and is often called the “Builder” pattern. That is, instead of implementing a constructor that takes 5 different parameters like this:

    let mut my_obj = MyObj::new("some string", 1, SOME_ENUM, true, false);

    You use the “Builder” pattern to make it much more clear (also more verbose, but hey, “tradeoffs”):

    let my_obj = MyObj::new("some string")

    The nice about this in Rust is, you can keep the mutability confined to object construction. After the object gets assigned to my_obj, it is considered immutable (you would have to write let mut my_obj to change this behavior).

    1. 19

      I like builders and have written APIs that provide builder patterns, but I really prefer option maps where the language makes it possible. For instance:

      let my_obj = MyObj::New("some string",
                              {:priority 1
                               :mode     SOME_ENUM
                               :foo?     true
                               :bar?     false})
      1. Option maps are usually shorter in languages with map literals.
      2. Option maps are data structures, not code. They’re easier to store and read from files. You can put them in databases or exchange them across the network. Over and over again I see boilerplate code that sucks in JSON and calls a builder fun for each key. This is silly.
      3. Builders in most languages (perhaps not Rust!) require an explicit freeze/build operation because they’re, well, mutable. Or you let people clobber them whenever, I guess. :-/
      4. Option maps compose better. You can write functions that transform the map, or add default values, etc, and call a downstream function. Composing builders requires yielding the builder back to the caller via a continuation, block, fun, etc.
      5. Option maps are obviously order-independent; builder APIs are explicitly mutating the builder, which means the order of options can matter. This makes composition in builders less reliable.

      Why not use option maps everywhere? I suspect it has to do with type systems. Most languages only have unityped maps where any key is allowed, but options usually have fixed names and specific but heterogenous types. The option map above has booleans, integers, and enums, for example.

      In languages like Java, it’s impossible to specify type constraints like “This map has a :foo? key which must be a boolean, and has a :mode key that can only be one of these three values”. Using a builder with explicit type signatures for each function lets you statically verify that the caller is using the correct keys and providing values of the appropriate type.^

      Of course, all this goes out the window when folks start reading config files at runtime, because you can’t statically verify the config file, so type errors will appear at runtime anyway, but you can certainly get some static benefit wherever the configuration is directly embedded in the code.

      ^Know what a heterogenous map is in Java? It’s an Object! From this perspective, builders are just really verbose option maps with static types.

      1. 1

        I agree that it’s a shame to sacrifice the composability of option maps. I would prefer a builder API which is sugar on top of merging option maps, with an easy way of getting to the option maps when I want that composability.

        You can also have builder APIs which are pure and return new objects instead of self. In Rubyland, ActiveRecord’s Relation API is like this, which is great because intermediary results can be shared:

        posts = Post.where(user_id: 1).order("published_at DESC")
        latest_posts = posts.limit(10)
        favourites = posts.where(favourite: true)

        This provides one part of the composability you get from option maps, but not all of it. Unfortunately I don’t think the ActiveRecord::Relation API is built in a way that lets you build up relations with option maps when you want.

      2. 6

        I’d argue that named parameters solve the opaque-list-of-arguments problem with much less complexity.

        1. 6

          As with all things, it depends on what kind of complexity. It increases complexity in the language for a decrease in complexity in your code. This may or may not be worth it, depending on how much you increase complexity in the language.

          1. 4

            less complexity

            Questionable; have you seen Python’s positional/named parameter assignment rules? Granted, they’d be much simplified by killing the *args and **kwargs constructs, but at a language level, named parameters are definitely more complicated. On the other hand, they do make life somewhat simpler for language users. It’s a tradeoff.

            Regardless, I think either is a perfectly acceptable solution to the problem.

        2. 2

          I think you missed the point there a little…..

          Yes, “return self” says “Mutable Object” and immutable objects are indeed nicer.

          However, it you have a mutable object, leaking it’s guts breaks encapsulation and compromises the invariant you are trying to protect.

          The rather sound idea behind “east oriented” programming is since the object is obstinately refusing to leak it’s guts, clients are forced to “Tell, don’t ask”.

          ie. Obey the Law of Demeter.

          ie. The clients are forced to hand the appropriate information to the mutable object for it to do the right thing, rather than break encapsulation, and the client to perform some dubious operation, and hand the result back to the mutable object.

          In Ruby my first “Rule of Thumb” is to “freeze” the object at the end of the constructor….. and only remove that freeze if I have proven to myself that I need a mutable object here.

          If I do, then East Oriented programming looks like a Sound idea to me.

          1. 2