This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
- # aleph (50)
- # announcements (20)
- # babashka (68)
- # beginners (70)
- # calva (25)
- # cider (1)
- # clj-kondo (10)
- # cljs-dev (3)
- # clojars (1)
- # clojure (112)
- # clojure-australia (7)
- # clojure-berlin (4)
- # clojure-europe (47)
- # clojure-italy (14)
- # clojure-nl (2)
- # clojure-norway (1)
- # clojure-serbia (5)
- # clojure-spec (11)
- # clojure-uk (8)
- # clojurescript (16)
- # community-development (2)
- # conjure (2)
- # crux (26)
- # cursive (15)
- # data-oriented-programming (35)
- # datahike (12)
- # datascript (5)
- # datomic (13)
- # duct (7)
- # fulcro (21)
- # graalvm (94)
- # graphql (1)
- # helix (4)
- # honeysql (19)
- # jackdaw (8)
- # jobs (2)
- # jobs-rus (1)
- # leiningen (1)
- # malli (32)
- # missionary (1)
- # mount (1)
- # off-topic (40)
- # perun (2)
- # portal (7)
- # reitit (10)
- # rewrite-clj (26)
- # shadow-cljs (90)
- # spacemacs (29)
- # sql (17)
- # tools-deps (49)
- # wasm (1)
I’d like to come back to the topic of loose coupling as promoted by Rich Hickey.
I think I finally grasped what Rich meant by loose coupling.
Let’s take the example of a 3rd party library that gives access to Google calendar API. Let’s assume this lib provides a function
createEvent to create an event.
Now let’s compare how we pass the information about the event we’d like to create:
1. In a statically typed approach (FP or OOP), we need to pass an
Event record with the information about the record
2. In a data-oriented approach, we pass a map with the appropriate field name
There is a huge difference between the two approaches:
1. In the statically typed approach, the information is passed in a way that is dependent of the implementation of
createEvent (Rich call it a data concretion). The only way for my code to generate the information that
createEvent expects is to use the
Event record. There is a tight coupling between my code and the library code.
2. In the data-oriented approach, I am free to generate the information as I want. The only constraints that I need to respect are the field names in the map. There is a loose coupling between my code and the library code.
@me1740, @cgrand what do you think?
it has been a long time since I listened to RH talks. that’s dependency coupling: two modules that share data shouldn’t have to share code, only agree on a spec (general sense).
I’m not seeing the difference between the two to be honest (coupling-wise). In the Clojure case, your code still needs to pass a map with the right keywords. The main advantage of using maps over classes like Event is that you can reuse all the functions to work on maps to manipulate your event data record. Instead of creating a new abstraction for each type of record you manipulate, you reuse the same abstraction (Map). This is related to Perlis’ quote: “It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures.” So I agree with you that to represent static information, reusing the same abstraction integrated in your language is much better than creating a new class every time like you do in Java. But I wouldn’t “sell this” as a reduction of coupling.
@me1740 Don’t you see a difference in terms of coupling between being forced to pass an instance of class vs. passing a map?
There are older systems like CORBA and COM that I have not used, but my understanding is they much more tightly coupled the language used in the hosts with the messages being passed over the network. JSON / XML / EDN / etc. are already a way to help avoid that coupling -- you are passing values between hosts over the network. Any semantics of them being RPC calls or not is up to the application to define (or not).
JSON / XML / EDN don't by themselves impose any restrictions on what map keys are required, or optional, and what kinds of values they are expected to have. CORBA/COM/etc. style things do.
(is my very basic understanding of things like CORBA/COM/etc. from 10,000 foot view and never having used them myself -- corrections welcome)
Rich Hickey mentions and at least briefly discusses CORBA / COM in his talks "Value of Values" and "Language of the System", found quickly by grep'ing through his talk transcripts here: https://github.com/matthiasn/talk-transcripts
I don't have an example handy off the top of my head, but I'm nearly certain there are straightforward examples of using JSON for over-the-network communication between hosts, and still having other forms of tight coupling, different than the ones CORBA/COM get you into.
For me "coupling" means the tendency for 2 pieces of code to change together. When a function calls another function, there is some coupling because if you change the spec of the function you might have to change the caller. But this coupling is visible and is fine. Ideally you still want to minimize the coupling as much as possible because you want to be able to modify the callee without impacting the caller. The type of coupling that is bad though, is hidden coupling. It is when the same design decision is reflected in multiple parts of the code. If you change one part of the code, you will also have to change the other one. But because the coupling is not visible (as the function calling example), you might forget to update the other code and bug happens.
Would you say that a frontend and a backend that communicates over HTTP via JSON are tightly coupled or loosely coupled?
From a logical point of view, the coupling is the same as passing a map or a Customer instance. Both sides have to agree on a representation for the message, whether it is a Clojure map, Java instance, or JSON document. But components interact in other ways. By separating the system into a client and server we got rid of a lot of other interactions: propagation of certain kinds of error, they don't rely on shared state, the client can timeout if the server fails to respond... In this sense we reduced the coupling between the two components because we prevented certain kind of change or error to propagate between the two.
My takeaway from the Hickey talk (the same one, I think) was that JSON/HTTP services are already loosely coupled, and that pure functions with immutable data seek to replicate that looseness.
I don't know if this is a good example or not -- interested to hear from others. Suppose your HTTP server checked the incoming JSON messages from the clients to ensure that they had keys x, y, and z, but gave an error if any others were present. Some would prefer to do this as a form of error checking / validation, but it does mean that as you want to extend the data model over time (assume you do), you need to change that error check at the boundary of the server, in addition to internal implementation changes related to the new data.
If the "only has keys in this set" check is in some HTTP handling code far from the core of your application that uses those keys, then there is at least some level of coupling between the error checking part of the code, and the other parts that actually use it. If you have 10 software components between the network interface and the part of the HTTP server that uses the data, and they all do closed world assumption checks that reject keys outside of a valid set, then all of those must be changed whenever that set grows.
For me that's an example of hidden coupling. The design decision (what is the set of required keys) is replicated in multiple places in the code. Every time you change one part, you will have to change the other parts as well. We should definitely not do that 🙂
I don’t believe there’s such a thing as loose coupling. Coupling is a continuity and describes how much a local change is going to ripple through dependencies. It also affects deployments as strong coupling is at odds with forward/backward compatibility. When I got started in Clojure, it struck me that most defaults were the opposite of what you get in Java (immutability, typing, collection-based modeling, pervasive namespacing on fields). In my experience it’s easier to get a less coupled system in Clojure than in Java because of that. In Clojure you go with the grain of the language, in Java you go against.
I don’t think that coupling is only about how changes ripple through dependencies. According to https://en.wikipedia.org/wiki/Loose_coupling: > In https://en.wikipedia.org/wiki/Computing and https://en.wikipedia.org/wiki/Systems_design a loosely coupled system is one in which each of its https://en.wikipedia.org/wiki/Software_component#Component_Definition has, or makes use of, little or no knowledge of the definitions of other separate components. It’s also about the amount of knowledge need to know about each other. A fronted and a backend that communicate via JSON are loosely coupled as the only knowledge that they have about each other is the data format and the semantics of the fields. However, two components inside a single program that communicate by calling function that receive an instance of data class are more tightly coupled). I agree that coupling is a continuum. In the context of function calls, I see three levels from loosely coupled to tightly coupled: 1. argument is generic data (e.g. map) 2. argument is a data interface 3. argument is a concrete data class Does it make sense to you guys @me1740 @cgrand? Once we agree about the definition of loose coupling, we can start discussing about the benefits of loose coupling inside a program.
I did a research about the term concretion and it comes from the dependency inversion principle (the D of SOLID) from OOP: https://en.wikipedia.org/wiki/Dependency_inversion_principle: Entities must depend on abstractions, not on concretions. It means that when one class depends in an instance of another class, it should not depend on concrete instances of the other class. Rather, it should depend on an abstract interface implemented by that class. We could take it one step further and claim that a more abstract way is to send a message to an object. The only required knowledge is the name of the message. And it leads us to Java OOP vs. SmallTalk OOP (message passing style).
We have a ladder of abstraction in code and data: 1. Code: concrete class, Data: concrete record 2. Code: abstract class, Data: abstract class with getters 3. Code: message passing. Data: generic maps
In a nutshell, one could say that: > “just use maps” is to data what “message passing” is to code