• ☆ Yσɠƚԋσʂ ☆@lemmy.mlOP
    link
    fedilink
    arrow-up
    1
    ·
    3 hours ago

    Yeah you can definitely have this kind of stuff in other languages.

    It’s not even remotely comparable. Outside Lisps, I have not seen any environment where you can start up your app, connect the editor to it, and then develop new code in the context of a running application. I also find that language design very much impacts conventions and architecture. Clojure’s focus on immutability naturally leads to code that’s largely referentially transparent and where you can reason about parts of the application in isolation without having to consider side effects and global state. Meanwhile, focus on plain data avoids a lot of the complexity you see in OOP languages. Each object is basically a state machine with an ad hoc API on top of it. You end up having to deal with graph of these opaque stateful entities, which is incredibly difficult to reason about. On the other hand, data is inert and transparent. When you pass data around, you can always simply look at the input/output data and know what the function is doing. Transforming data also becomes trivial since you just use the same functions regardless of what data structure you’re operating on, this avoids many patterns like wrappers and adapters that you see in OO style. My experience with Clojure is that its semantics naturally lead to lean systems that are expressed in terms of data transformation pipelines.

    Again, this is my personal experience. Obviously, plenty of people are working with mainstream languages and they’re fine with that. Personally, I just couldn’t go back to that now.

    • Simon 𐕣he 🪨 Johnson@lemmy.ml
      link
      fedilink
      arrow-up
      1
      ·
      edit-2
      2 hours ago

      Outside Lisps, I have not seen any environment where you can start up your app, connect the editor to it, and then develop new code in the context of a running application.

      This is absolutely true, however I don’t particularly value this feature because most engineers typically already cannot separate concerns very well in industry so IMO if I had this I would not want people to use it. Very much a “it works ship it” trap.

      . I also find that language design very much impacts conventions and architecture. Clojure’s focus on immutability naturally leads to code that’s largely referentially transparent and where you can reason about parts of the application in isolation without having to consider side effects and global state.

      I’m with you here. I basically force almost every code base I end up working on into functional forms as much as possible.

      When you pass data around, you can always simply look at the input/output data and know what the function is doing. Transforming data also becomes trivial since you just use the same functions regardless of what data structure you’re operating on, this avoids many patterns like wrappers and adapters that you see in OO style

      Yep agreed, almost every role I step into I push the org to enforce this style of work.

      Transforming data also becomes trivial since you just use the same functions regardless of what data structure you’re operating on, this avoids many patterns like wrappers and adapters that you see in OO style.

      This is where you lose me, you still have wrappers and adapters, they’re just not classes. They’re functions. I still use those words regardless if I’m in Haskell or Typescript. Semantic meaning shouldn’t be lost in functional style because it’s part of architecture. Functional programming simply gives your basic building blocks better names and better division of responsibility, e.g. functor, applicative, monad, etc.

      • ☆ Yσɠƚԋσʂ ☆@lemmy.mlOP
        link
        fedilink
        arrow-up
        1
        ·
        2 hours ago

        This is absolutely true, however I don’t particularly value this feature because most engineers typically already cannot separate concerns very well in industry so IMO if I had this I would not want people to use it. Very much a “it works ship it” trap.

        That’s been the opposite of my experience using Clojure professionally. You’re actually far more likely to refactor and clean things up when you have a fast feedback loop. Once you’ve figured out a solution, it’s very easy to break things up, and refactor, then just run the code again and make sure it still works. The more barriers you have there the more likely you are to just leave the code as is once you get it working.

        This is where you lose me, you still have wrappers and adapters, they’re just not classes.

        A good explanation of the problem here https://www.youtube.com/watch?v=aSEQfqNYNAc

        When you’re dealing with types or classes they exist within the context they’re defined in. Whenever you go from one context to another, you have to effectively copy the data to a new container to use it. With Clojure, you have a single set of common data structures that are used throughout the language. Any data you get from a library or a component in an application can be used directly without any additional ceremony.

        • Simon 𐕣he 🪨 Johnson@lemmy.ml
          link
          fedilink
          arrow-up
          1
          ·
          edit-2
          1 hour ago

          That’s been the opposite of my experience using Clojure professionally. You’re actually far more likely to refactor and clean things up when you have a fast feedback loop. Once you’ve figured out a solution, it’s very easy to break things up, and refactor, then just run the code again and make sure it still works. The more barriers you have there the more likely you are to just leave the code as is once you get it working.

          This is more of a how the saussage is made issue in my experience than a tooling selection issue. Clojure may make it easier to do the right thing but the actual forcing function is the company culture. Self-selection of Clojure as the company’s tooling may create a correlation.

          Most companies have the ability to implement fail fast workflows for their developers they simply choose not to because it’s “hard”. My preferred one is Behavior Driven Development because it forces you to constrain problems into smaller domains/behaviors.

          When you’re dealing with types or classes they exist within the context they’re defined in. Whenever you go from one context to another, you have to effectively copy the data to a new container to use it. With Clojure, you have a single set of common data structures that are used throughout the language. Any data you get from a library or a component in an application can be used directly without any additional ceremony.

          An Adapter is typically a piece of code that transforms data between formats at various boundaries. Typeclasses remove the need for Adapters for functionality at library boundaries e.g. most thing have map where in javascript I can’t do {}.map with the EMCA standard. However typeclasses do not solve the problem of the literal data format and functionality differences between different implementations.

          For example I call some API using a Client and it returns bad gross data based on how that API is written, I would use an Adapter to transform that data into clean organized data my system works with. This is extremely helpful when your system and the external system have references to each other, but your data taxonomy differs.

          A real example is that Google Maps used to have a distance matrix API where it would literally calculate matrices for you based on the locations you submit. Circa 2018 Google changed it’s billing driving up the prices, which lead a lot of people to use alternative services like Here.com. Here.com does not have a distance matrix API. So in order to build a distance matrix I needed to write an adapter that made N calls instead of Google’s 1 call and then stuff the Here.com responses into a matrix response compatible with Google’s API which we unfortunately were using directly without an internal representation.

          These concepts are still used/useful within functional contexts because they are not technical concepts they are semantic concepts. In functional languages an Adapter may just be a function that your responses are mapped over, in OOP style it might be a class that calls specific network client and mangles the data in some other way. Regardless of the technical code format, it still is the same concept and procedural form, thus it’s still a useful convention.

          • ☆ Yσɠƚԋσʂ ☆@lemmy.mlOP
            link
            fedilink
            arrow-up
            1
            ·
            1 hour ago

            I agree, the language alone isn’t a silver bullet. I’m not suggesting that it is. You still have to implement good workflow, do testing, code reviews, architecture design, and so on. All these things are language agnostic. What the language can do is reduce friction in your workflow, and nudge design in the right direction by making it easier to do the right thing. I largely see it as a quality of life improvement.

            Also, I’m not saying that patterns like adapters don’t have their uses or that you might not use a similar approach in a functional language. My point was that these types of patterns tend to be more pervasive in mainstream languages.

            Static typing itself is a trade off as well. It introduces mental overhead because you are restricted to a set of statements that can be expressed using a particular type system, and this can lead to code that’s written for the benefit of the type checker rather than a human reading it. Everything is a trade off in practice.

            Finally, the choice of language ultimately depends on a particular team. Different people think in different ways, and have different experience. The best language is the one that majority of the team is comfortable using. Hence, I’m speaking here from my personal perspective of the way I enjoy doing development. This will necessarily vary from person to person.

            • Static typing itself is a trade off as well. It introduces mental overhead because you are restricted to a set of statements that can be expressed using a particular type system, and this can lead to code that’s written for the benefit of the type checker rather than a human reading it. Everything is a trade off in practice.

              You mean code that’s written to the benefit of a low efficiency enterprise workflow, which is my love hate relationship with Typescript. Best out choice out of a pile of shit.

              • ☆ Yσɠƚԋσʂ ☆@lemmy.mlOP
                link
                fedilink
                arrow-up
                1
                ·
                46 seconds ago

                That’s not what I’m saying. I think static typing introduces a certain set of trade offs that some people prefer. You restrict the set of statements that are possible to express to ones that can be checked by the type system, and as a result you get additional compile time guarantees. For example, Lemmy devs prefer this trade off and it has nothing to do with enterprise workflows.