Hacker News new | past | comments | ask | show | jobs | submit login

Well it's a bit preposterous of you to say that without actually having tried Clojure in good faith.

But, since we all make opinions from others, let me provide you a balance of opinions by giving you mine.

I'm a senior engineer and I currently use Clojure professionally. I find it to be a lovely, fun and productive language, my favorite one to date actually. I have prior professional experience with C++, C#, ActionScript 3, JavaScript, Scala, Kotlin and Java. And of all of those, Clojure is my favourite to work with.

The "context' pattern you may have heard of, I believe I know what it refers too, and it is not an imperative pattern at all, let me explain.

There's often a case where a piece of functionality will be implemented by multiple functions composed together. For example, an API handling a request will delegate to sub-functions which could in turn call down to more sub-functions.

In that scenario, it can happen that the sub-functions need data about the context of the call to the parent function, or they need data generated by the prior sub-functions that were called. In my example, lets say the request object to the API dictates various options and has the request details, and each options is relevant to different sub-functions, maybe one needs the username, where the other needs the cart-id both of which were passed on the request, but maybe the other function also needs the user-permissions which was obtained from the prior sub-function.

Ok, so say:

    someAPI({username: "foo", cart-id: 123}) {
      permissions = get-permissions(username);
      retrieve-cart-items(username, permissions);
}

Now as you have more and more of these forming a coherent whole, in Clojure there is a pattern where people will say, all this initial data and the data generated by the intermediate steps becomes the "context" data for the operation as a whole.

    (defn some-api [context]
      (-> get-permissions
          retrieve-cart-items
          :cart-items))
Where context is at first a map of the request data:

    {:username "foo",
     :cart-id 123}
And after the call to "get-permissions" it is a map of the initial request data and the intermediate data added by get-permissions:

    {:username "foo",
     :cart-id 123,
     :permissions [:can-view-cart]}
And after the call to retrieve-cart-items it now also contains the cart-items:

    {:username "foo",
     :cart-id 123,
     :permissions [:can-view-cart]
     :cart-items [:pen, :paper]}
Thus all implementing functions for the some-api operations are designed so they take an initial context map and return a context map with added context.

And generally in Clojure you'd use the destructuring syntax to indicate what in the context map you depend on:

    (defn get-permissions
      [{:keys [username]:as context}]
      ...)
So you see that get-permissions expect the :username key to be present on the context.

But, this is still fully functional, because none of the functions share a mutable reference to a shared context, they take an immutable context as input and return a new immutable context as output which will just happen to also include all the key/values of the context they received.

Sometimes people also pass in dependencies using this context pattern, which is a form of dependency injection through parametrization, but you combine all dependencies into one parameter object.




Very good explanation.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: