1. 1

    I think about this a lot. The worst part is that we’re not even getting a lot of additional functionality in the process. I find that my usage patterns with my phone haven’t changed much in the past decade. The functionality of the apps I use is about the same, yet the hardware needed to run the device has gotten incredibly more powerful.

    I feel like part of the problem here lies with capitalism creating wrong incentives. Companies have a drive to continuously sell new hardware, so it’s actually convenient for them that software continues to get ever more bloated necessitating new hardware with each cycle.

    1. 6

      The core problem here is that having a single browser engine will effectively kill web standards. The standard at that point will be whatever Chrome is doing, and we already went through that nightmare with IE. While WebKit is open source, the reality is that Google ultimately gets to decide on how it evolves. If WebKit becomes the only game in town, then it gives Google an unreasonable amount of power over how the internet works. The internet is simply too important for an ad company to become the sole gatekeeper for it.

      1. 3

        Webkit and blink (chrome / edge) are diverging. Firefox and blink seem to have better standards support than webkit.

      1. 1

        Babashka is a similar idea for Clojure where you can do stuff like:

        ls | bb -i '(take 2 *input*)' | bb '(first *input*)'
        bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]'
        
        1. 2

          Thats nice, thanks. Checking it out.

        1. 3

          I’ve been using Cursive for years, and can highly recommend it for any serious projects. I find it just works, and has a lot of really nice functionality for refactoring and navigation. I also use Calva for small projects. It’s not nearly as featured as Cursive, but if you want something light weight it’s worth checking out as well.

          1. 3

            Would be helpful to clarify that this is about building a static site, which is really different from what I expected upon reading the title.

            1. 3

              Good call, updated the title 👍

            1. 3

              I really like this, and can see myself using this in some form.

              One feature that’s missing here that would push it over the line to “killer app” for me, would be the ability to share data as well as applications. That way, you could sync your tasks from a todo app from your laptop to your phone, for example.

              Anyway, thanks for sharing!

              1. 3

                You could probably get jlongster’s CRDT implementation running in here.

                1. 4

                  schism would probably be a better options since it’s also in ClojureScript :)

                  1. 2

                    Oh very cool I’ll check these out. Thanks!

              1. 6

                Just came across this and am going through your previous posts. A coupe of comments:

                • nitpicky comment but the underlying set for the field doesn’t necessarily have to be a set of numbers

                • The example you gave for the set {1, 0, -1} is not closed under addition like you mentioned: 1 + 1 = 2 and 2 is not in the set

                • It’s actually the case that the additive and multiplicative groups of the field are abelian, and this is quite an important property that you’ve left out.

                • for your definition of multiplicative inverses, you’re missing a - in the exponent at the beginning of the line

                • for multiplicative inverses, it is also required that a is not the multiplicative identity

                • it is not true that finite fields always have prime order.

                • You reference fields with the naming convention F_p and I assume you are implying fields defined under modular arithmetic but you don’t mention it until later so I think it might be a bit confusing to some people

                • I’m not sure if the section on division is helping or hurting. “division” in a finite field is just multiplication by an element that is an inverse – i.e. it’s just multiplication by another element in the field that happens to have the property that it is an inverse of some other element. It’s not a separate operation, and I actually think that the normal intuition behind division is harmful in the case of fields modulo N (we’re not “dividing” anything into smaller parts, among other reasons).

                I’ll keep reading through. Definitely an interesting topic!

                1. 1

                  Just as a note, I’m not the author of the series. Just thought it was pretty interesting. That said, those are interesting notes!

                  1. 1

                    edit

                    for multiplicative inverses, it is also required that a is not the multiplicative identity

                    I should have proofread myself. It is required that a is not the ADDITIVE identity.

                  1. 1

                    One huge benefit my team found after moving to ClojureScript was the stability of the language and the ecosystem around it. We’ve been using re-frame for about five years now, and its APIs and semantics have remained largely unchanged. There have been a few new features added, but practically no breaking changes over the years. We just update the version to get speed improvements, features, and fixes.

                    Meanwhile, React ecosystem has had a ton of churn in that time. There have been lots of patterns introduced like redux, flux, hooks, and so on. If you started building an app using best practices a few years ago it’s likely to be considered legacy today.

                    There’s a lot to be said about having a stable ecosystem and development practices that don’t keep changing for the sake of change. Chasing Js trends is simply not sustainable in my opinion, and it’s a huge drain on team efficiency. On the other hand, if you don’t keep up then you end up with legacy code where libraries you depend on are no longer maintained, and it’s hard to find developers who are still familiar with them. A lot of shops got burned by this investing in Angular when everything changed with Angular 2 coming out.

                    1. 6

                      I mostly agree but I would say that OO is evolving toward FP and they aren’t that far apart (at least in certain circles, maybe not in 20 year old code).

                      Good OO is also principled about state and I/O, just like FP.

                      Related comment I wrote on “How to Avoid the Assignment Statement”

                      https://news.ycombinator.com/item?id=22835750

                      Older comment on “Disadvantages of Purely Functional Programming”

                      https://news.ycombinator.com/item?id=11841893

                      So while I agree with the general gist, I would add the caveat that it’s easier to do FP in OO languages than OO in functional language. And the former is pretty natural.


                      Another way I think about it is “dependency inversion of state and I/O”, which I wrote about here:

                      http://www.oilshell.org/blog/2020/04/release-0.8.pre4.html#dependency-inversion-leads-to-pure-interpreters

                      And I also think in many programs it’s useful to “cheat” a little to get it working, and then refactor to a more principled architecture.

                      1. 9

                        There’s a fundamental difference between OO and FP. With FP the core idea is to keep data and code separate. Programs are written as pipelines of functions that you pass data through, and get another piece of data at the other end. In modern FP, data is also immutable, so the functions are referentially transparent. The big advantage here is that data is inert by nature because it doesn’t have any behaviors associated with it, and it’s transparent. What you see is what you get because it’s just data.

                        And you operate on this data by combining a common set of functions from the standard library. Once you learn how these functions work, it becomes trivial to transform any kind of data using them. It’s akin to having a bunch of Lego pieces that you can put together in many different ways to accomplish different tasks.

                        On the other hand, objects are state machines that have some internal state with the methods as the API for interacting with that state. An application written in OO style ends up being a graph of opaque interdependent state machines, and this tends to be quite difficult to reason about. This is why debugging is such a big part of OO development. It’s impossible to reason about a large OO application because there’s just too many things that you have to keep in your head. So, the only thing you can do is put a break point, get the application in the desired state, and try to figure out how you got there.

                        Meanwhile, classes are really ad hoc DSLs, each class defines its own custom API and behaviors in form of its methods, and knowing how one class works tells you absolutely nothing about the next class. The more classes you have in your program, the more stuff you have to keep in your head to work with it effectively.

                        This also makes it much more difficult to learn APIs for libraries. When you call a method, and you get an object graph back, now you have to learn about each of these objects, and how they behave. When your API is data driven, this problem doesn’t exist. You call a function, get some data back, and that’s the end of the story.

                        Rich Hickey describes this problem here very well, and that matches my experience very closely.

                        1. 6

                          No, that is sort of an old way of thinking about OO. There are a lot of people who don’t write it that way, including me. You can do something much closer to FP in a OO language.

                          That is the point of the linked comments. Exceprt:

                          I use “functions and data” (a la Rich Hickey).

                          Except my functions and data are both CLASSES. Data objects are basically structs, except they can do things like print themselves and answer simple queries based on their values, which helps readability (e.g. 1 liners, like word.AsFuncName() ).

                          Function objects are simply classes with configuration passed to constructors. That usually have a single method, but multiple methods are also often useful. Calling this method is basically equivalent to calling a curried function. But this is supremely useful for both compilers and servers, because often you have config/params that is constant once you reach main(), and then you have params that vary per request or per file processed. Many functions depend on both, and it’s cleaner to separate the two kinds of params.

                          So both “functions and data” are effectively and usefully implemented as classes.

                          The Oil interpreter started out maybe 60% in this style, and is approaching 90% of that style. It’s tens of thousands of lines of code, so it’s not a small change.

                          There are a few classes that are state machines, but they are explicitly limited to about 10% of the interpreter, just as you would do in a functional language. Most of it is parsing, which has “local” mutable state inside and an interface that’s basically a function.

                          Again, from the comments, the thing I found funny is that for lexing and parsing, languages like OCaml just borrow the exact same mutable algorithms from C for lexing and parsing (LALR parsing and DFAs for regexes). The mutable escape hatch of of OCaml is essential.

                          Lexing and parsing are inherently stateful. As long as it’s localized, it’s no problem. FP and OO both agree on that.

                          1. 1

                            Thing is that in practice you rarely pass functions around in Clojure. Vast majority of the time you’re passing around plain data, you pass that data through different functions to transform it, and get new kind of data back. There is very little reason to pass functions around in my experience. So, yes you can do similar stuff to OO with functions and closures, but that’s not generally how you end up structuring your applications.

                            And yes, you can write FP style code in OO languages, but then you’re really not making the most out of the paradigm the language was designed for. You’re much better off doing FP in an actual FP language.

                          2. 3

                            This also makes it much more difficult to learn APIs for libraries. When you call a method, and you get an object graph back, now you have to learn about each of these objects, and how they behave. When your API is data driven, this problem doesn’t exist. You call a function, get some data back, and that’s the end of the story.

                            It creates another problem though: exposing implementation details. Your clients may start assuming things about the data structure that need to be changed. This article tells the story of an API that exposed a list [1]. It turned out the list was too slow, and they wanted to change it. Unfortunately the API’s clients assumed it was a list. The solution given in the article? Hide the data behind a constructor and selectors. That’s basically a class definition.

                            1: https://itnext.io/information-hiding-for-the-functional-programmer-b56937bdb789

                            1. 2

                              Not really because I choose what data I return from the API functions in a library. Meanwhile, your anecdote could’ve happened just as easily using OO API. In fact, I would argue that it’s a far more common problem in OO since you return a graph of objects, and if you ever need to change any of them down the line you’ll be breaking the API for all your users.

                              Having been working with Clojure for around a decade now, I can tell you that this problem has yet to come up for me in practice.

                              1. 1

                                In fact, I would argue that it’s a far more common problem in OO since you return a graph of objects, and if you ever need to change any of them down the line you’ll be breaking the API for all your users.

                                One of the core tenets of OOP is to program to the interface, not the implementation. If you change the implementation but keep the interface unchanged, you are guaranteed to not break the downstream consumers.

                                1. 1

                                  Interfaces only address the problem partially, because if the interface ever changes that will still create a breaking change. My experience is that changes to interfaces in OO happen far more regularly than changes to the shape of the data in functional APIs.

                                  1. 1

                                    As per the Java Language Spec,[1]

                                    …here is a list of some important binary compatible changes that the Java programming language supports: … Adding new fields, methods, or constructors to an existing class or interface.

                                    (Among others)

                                    This is similar to making an additive change to the shape of data, e.g. adding a new field to a map which is consumed by a function that doesn’t use the field.

                                    [1] https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html

                                    1. 1

                                      Having worked with Java for around a decade, I’m well aware of how interfaces work. Yet, my experience is that breaking changes in a language like Java happen far more frequently than they do in a language like Clojure. So, while there are mitigating factors in theory, I don’t find they translate to having stable APIs in practice. That’s just my experience though.

                                2. 1

                                  How does you choosing what data you return from the API prevent you from exposing implementation details? Or are you saying you just don’t care because you can change the API interface whenever you feel like it?

                                  1. 1

                                    I don’t really follow your question. When I write a function, I explicitly what data it returns, and the shape of that data. The shape of the data is explicitly part of the API contract.

                                    1. 1

                                      The shape of the data is explicitly part of the API contract.

                                      Because the approach forces you to do that even when the shape of the data is an implementation detail that you don’t want to expose.

                                      1. 1

                                        That statement doesn’t make sense. The shape of the data is explicitly part of the contract provided by the API. It is not an implementation detail.

                                        1. 1

                                          Every implementation choice that is not relevant to the API’s core functionality is an implementation detail. For example, it’s not relevant for an iterator to know the data structure of a collection. All the iterator cares about is that it can iterate over the elements. The concrete shape of the collection (list, set, map, queue, stack, whatever) is an implementation detail from the point of view of the iterator.

                                          Just because you decide to make an implementation detail a part of the API’s contract, that doesn’t mean it’s not an implementation detail anymore. That’s essentially the problem in the article that I linked.

                                          1. 1

                                            In Clojure, you have a seq interface, so any function can iterate over any collection. The same way as it works with Java interfaces. So, no you’re not leaking any more implementation details than you wold with OO API. You’re just arguing a straw man here.

                                            1. 1

                                              Just because iteration in Clojure does not depend on the implementation details of the data structure, that doesn’t mean the API’s clients can’t write code that does depend on such details. So, that does not contradict my point at all.

                                              I don’t know Clojure that well, so I can’t give you an example in Clojure. However, I’m pretty confident that even Clojure does not magically solve the problem of leaking implementation details (otherwise, why would it have interfaces in the first place).

                                              1. 1

                                                Again, there’s no difference between FP and OO here. Both Clojure and Java have collection interfaces. If you have an OO API that says it returns a collection, that collection is backed by a concrete implementation, such as a list, internally. That’s an implementation detail. OO doesn’t magically do away with collections. And this is why I find your whole line of argument completely absurd.

                                                Also, having actually used Clojure for a around a decade now professionally, I can tell you that what you’re describing has never ever come up in practice. So, forgive me if I’m not convinced by your arm chair analysis here.

                                                1. 1

                                                  Again, there’s no difference between FP and OO here. If you have an OO API that says it returns a collection, and that collection is backed by a list internally, that’s an implementation detail. OO doesn’t magically do away with collections.

                                                  I don’t follow this argument at all. Yeah, in the OO case there is an implementation detail. However, as you pointed out with the word internally: that implementation detail is not exposed to the client. Meaning, the API owner can change that list to a map or a tree or any other data structure at any time without having to worry about the client’s code at all.

                                                  It’s also a little bit of a straw man because this discussion was not about OO vs FP. It was about your statement that APIs should produce/consume raw data rather than objects.

                                                  Having actually used Clojure for a around a decade now professionally, I can tell you that what you’re describing has never ever come up in practice. So, forgive me if I’m not convinced by your arm chair analysis here.

                                                  So, you’re telling me that, in a whole decade, you’ve never had to make changes to an API because a data structure turned out to be wrong? Given that I could pretty easily find an article that showed people having exactly that problem, forgive me if I conclude that either you’re not remembering correctly, or you’re an extraordinary programmer.

                                                  1. 1

                                                    The implementation detail is exposed to the client in exactly the same way. In both cases you have an interface that abstracts the type of collection used from the client. The API owner can change the implementation in EXACTLY the same way. If my API returned a list and then I change it to return a vector, the client does not need to make any changes because both of them support the seq interface.

                                                    The discussion is about OO vs FP because my whole point is that in OO you end up interacting with object graphs in your API, while in FP you end up interacting with plain data. I’m simply telling you that your argument is incorrect in the context of Clojure because collections conform to interfaces. I’m starting to get the feeling that you don’t understand how interfaces work.

                                                    So, you’re telling me that, in a whole decade, you’ve never had to make changes to an API because a data structure turned out to be wrong?

                                                    Correct, I’ve never had to make a change to an API because I changed the implementation of the backing data structure that conformed to the same interface.

                                                    And please do link me an article of this happening in Clojure since you claim you claim that can easily find that. Perhaps what you failed to consider is that the article you found deals with the problems in a specific language as opposed to a general FP problem that you extrapolated from it.

                                                    It frankly amazes me that somebody who openly admits to knowing nothing about the subject can have such strong opinions on it based on an article they find.

                                                    1. 1

                                                      The implementation detail is exposed to the client in exactly the same way. In both cases you have an interface that abstracts the type of collection used from the client. The API owner can change the implementation in EXACTLY the same way. If my API returned a list and then I change it to return a vector, the client does not need to make any changes because both of them support the seq interface.

                                                      As I said, only because Clojure happens to have a seq interface. This reply tells me that you didn’t really get the point I made, and I don’t know how to explain it to you at this point. It seems you just don’t want to see it.

                                                      Correct, I’ve never had to make a change to an API because I changed the implementation of the backing data structure that conformed to the same interface.

                                                      Then your API is not producing/consuming raw data, it’s producing/consuming interfaces. It seems you’re contradicting your own position.

                                                      And please do link me an article of this happening in Clojure since you claim you claim that can easily find that. Perhaps what you failed to consider is that the article you found deals with the problems in a specific language as opposed to a general FP problem that you extrapolated from it.

                                                      So, I searched “Clojure information hiding”, and the first hit literally repeats my whole point. Maybe you understand this explanation then:

                                                      One of the goals of information-hiding is to hide implementation details, so that programmers cannot write programs that depend on those details. (If they do so and those implementation details change, then those programs must also be changed, driving up software maintenance costs.) Clojure thus does not give us any good way to hide implementation details.

                                                      https://cs.calvin.edu/courses/cs/214/adams/labs/08/clojure/

                                                      1. 1

                                                        Clojure having a seq interface clearly disproves your point. I’m not sure what else there is to say here.

                                                        Then your API is not producing/consuming raw data, it’s producing/consuming interfaces. It seems you’re contradicting your own position.

                                                        If you think that then you didn’t understand my position. My original point was that data is immutable, transparent, and it doesn’t have behaviors associated with it. Having interfaces doesn’t invalidate any of that. Your whole argument is just a straw man.

                                                        So, I searched “Clojure information hiding”, and the first hit literally repeats my whole point. Maybe you understand this explanation then:

                                                        There is no information hiding in Clojure because data is part of the API. That’s my whole point of the difference between OO and FP. The claim that this is at odds with hiding implementation details is incorrect however, and I’ve already explained repeatedly why it’s incorrect.

                                                        Seeing how this discussion just keeps going in circles I’m going to leave it here. You can feel free to continue believing what it is that you want to believe, I’ve explained all I can.

                                                        Have a good day.

                                                        1. 1

                                                          My original point was that data is immutable, transparent, and it doesn’t have behaviors associated with it. Having interfaces doesn’t invalidate any of that.

                                                          Okay, that changes things…

                                                          I guess I didn’t understand that at all from reading your comments. There is a paragraph in your initial post where you talk about “classes being ad hoc DSLs” and a bunch of other stuff that applies equally to interfaces. You know, in C++ you make interfaces as pure virtual classes. Then, in the next paragraph you make a contrast between that situation and just data. So, I thought you were talking about concrete data structures not hidden behind any interface.

                                                          Also, in your first counterargument, you’re countering with this graph of objects that’s hard to change. However, if that was a graph of interfaces, that problem would still be there (as you pointed out in another comment). So, this reinforced the understanding in me (and also others, it seems) that you were talking about concrete “interface-less” data structures.

                                                          Anyway, if what you’re arguing for includes the ability to return only interfaces from APIs, then I agree the problem that I brought up doesn’t really apply.

                                                          1. 1

                                                            The problem with graphs of objects is primarily with objects being opaque and having behaviors. This is completely tangential to the discussion around interfaces.

                                                            When you have a data driven API backed by immutable data, then what you see is what you get. You can see the the data returned in the API, and it doesn’t have any behaviors associated with it. All the interfaces achieve here is abstracting over the concrete implementation, so you don’t have to worry about the specific backing data structure affecting the API semantics.

                                                            1. 1

                                                              Alright, that’s great! I actually share the view that state can turn into a nasty thing. It’s just that you seemed to be arguing against the idea of data abstraction. This kind of triggered me because my experience tells me the code I work with professionally would never survive without it.

                                                              I guess it’s words like transparent and opaque that cause some confusion for me. They are very generic words so they can be misinterpreted easily. For example, an interface is also opaque in the sense that you can’t see the implementation details.

                                                              1. 2

                                                                Glad we’re on the same page. Unfortunately, this kind of thing happens quite often. We all use the same words, but we attach subtly different meanings to them in our heads based on our existing knowledge and experience. So, it’s really easy to end up talking past each other and not even realize it. :)

                              2. 2

                                I basically agree with everything you said about what makes a program good or bad, but I disagree with your conclusion: that functional programming leads to good programs and oop leads to bad programs (in some general sense, let’s not nit-pick or talk about absolutes).

                                But I disagree. In almost all languages, you are perfectly allowed to mutate inputs into functions. This includes basically all (quasi-popular) functional languages that are not named Haskell or Clojure. You are also allowed to cause side effects in your functions in all functional languages that are not named Haskell. This means that I can write input-mutating, side-effecting functional code in OCaml, LISP, Scheme, Rust, etc, etc. Some languages discourage it more than others.

                                My point is that I agree that making 100 opaque state machines to interact with is a bad idea for a program. But that ad-hoc, crappy, DSL is also perfectly easy to write in a functional way.

                                I have little doubt that a very strict OOP language that does not allow input arg mutation and side-effects in methods is possible to create and would probably work just as well as good functional languages. The only difference would be a coupling of the data to the functions. That is the only actual difference between FP and OOP in any strict sense, IMO.

                                1. 5

                                  Both major ML dialects (Standard ML and OCaml) keep a typed distinction between immutable and mutable data. I find this to be good enough to tell which operations can mutate data that I care about at any given moment. Moreover, modules allow you to easily implement ostensibly immutable abstract data types that internally use mutation. (The operating word here is “easily”. It is possible in Haskell too, but it is a lot more painful.)

                                  I would not call Rust a “functional language”, but, for similar reasons, its ability to track what data can be safely mutated at any given moment is good enough to get most of the advantages of functional programming. And then, some of the advantages of non-functional programming.

                              3. 3

                                Hopefully on-topic: My experience in functional programming has led to heavy use of composition.

                                One thing that’s always frustrated me about Python is that instance methods do not return “self” by default, but instead return “None”. I once hacked up a metaclass to make that happen, and suddenly Python felt much more functional! SmallTalk and some of the heavy OO languages do return “self” or “this” by default, I find that fits the Haskell part of my brain better.

                                What’s the zen koan? Objects are a poor man’s closures, and closures are a poor man’s objects? In Haskell I use partial application to give me roughly what object instances give me. It’s neat, try it out!

                                1. 4

                                  One thing that’s always frustrated me about Python is that instance methods do not return “self” by default, but instead return “None”.

                                  Yes! It makes it much harder to do simple (list|dict|generator) comprehensions when mutations return None.

                                  In Haskell I use partial application to give me roughly what object instances give me. It’s neat, try it out!

                                  I (ab)use functools.partial for this in python. Very helpful when you have a defined interface and you want to stick extra parameters in as well.

                                2. 3

                                  Even principled object oriented code hides race conditions. If you connect two systems in an OO language, it may produce an incorrect result while separately systems would run just fine.

                                  Another problem is that partial evaluation for an OO language is not obvious. If you intend to write abstract code like people can do with FP it introduces structure that may need to be contracted away.

                                  1. 2

                                    You don’t get a guarantee from the language, but you can absolutely structure your OO code so composition is thread-safe, and unsafe combinations are obvious.

                                    When I say dependency inversion of I/O and state, I mean that they are all instantiated in main(). And all other code “modules” are also instantiated in main (as classes), and receive state and I/O as parameters.

                                    If you pass the same state to two different modules, then you have to be careful that they are not run concurrently.

                                    If they don’t accept the same state as parameters, then they are safe to run concurrently.

                                    There are zero mutable globals. That is how the Oil interpreter is written.

                                    It helps as I mentioned to have some classes that are like functions, and some classes that are like data. Data and functions are both usefully and easily expressed as classes. (In Oil I use ASDL to make data-like classes, for example)


                                    tl;dr You can make state and I/O parameters in an OO language, and then you get a lot of the reasoning benefits of functional programs, along with some other flexibility (like using a mutable style inside referentially transparent functions, mentioned in my comments and in the article)

                                    1. 1

                                      Could you expand on your first point? What kind of systems and connection between them do you have in mind?

                                      1. 3

                                        An example, a simple one:

                                        class A {
                                          int x;
                                          mutate_x (int v) { x = v };
                                          sendto (B receiver) {
                                            int y = x + 10;
                                            while (x < y) { receiver.receive(x); x += 1 }
                                          }
                                        }
                                        

                                        Depending on whether a receiver here has a separate access to A and gets to mutate_x when sendto is going on, this code is either fine, or then it’s not.

                                        1. 1

                                          That makes sense. Thanks for elaborating!

                                    2. -1

                                      Can you please paste you replies here, so I don’t have to make another click?

                                    1. 12

                                      I’ve come to realize that open source is the only type of software worth investing into. No matter how great a commercial piece of software might be, sooner or later it’s going to either disappear or change in a way that doesn’t suit you. Commercial software has to constantly chase profit for the company to stick around. This necessarily means that the product has to continue evolving to chase what’s currently in vogue. And if a company fails to do that, then it will die and the software will stopped being developed.

                                      This is a bad situation to be in as a user since you have little control over the evolution of a product that you rely on. Instead of the product being adapted to your needs, it’s you who has to adapt to the way the product evolves, or spend the time investing in a different product.

                                      On the other hand, open source has a very different dynamic. Projects can survive with little or no commercial incentive because they’re often developed by the users who themselves benefit from these projects. Projects can also be easily forked and taken in different directions by different groups of users. Even when projects become abandoned, they can be picked up again by new teams.

                                      Evolution of GNOME is a great example of this. There are now many flavors of GNOME all catering to different workflows, and users don’t have to compromise their preferred way of doing things to chase how GNOME is evolving. Meanwhile, users of Windows or MacOS have very little choice but to continue adjusting to the ways Apple and Microsoft choose to evolve the desktop. Microsoft even uses DMCA to prevent users from doing customization.

                                      1. 3

                                        Speaking as someone who abandoned the Microsoft stack for a much better development experience with free software, it’s worth remembering that free software has its own related failure mode:

                                        https://www.jwz.org/doc/cadt.html

                                        1. 3

                                          I mean, that failure mode is alive and well at the last place we both worked - hardly a problem restricted to OSS.

                                          1. 3

                                            Ah that’s subtler - CADPM (Cascade of Attention-Deficit Product Managers) ;-P

                                            Seriously though yes, it’s certainly not unique to free software.

                                        2. 2

                                          I switched to Linux as my main computer at the almost same time as you did (if I recall it correctly) and never looked back.

                                          I am using musl-linux + wayland on some of my computers which is little unstable combination with proprietary SW (firefox, chromium) and still, it is ten times better than anything I have ever used. Mostly for the reasons Nikita says in this article.

                                        1. 2

                                          I always thought the whole idea of running high level languages on these boards a bit pointless. Esp32/esp8266 sell out there as nodemcu with support for lua. There’s also versions with firmwares with a web server so one can run micropython or JavaScript as this post talks about.

                                          I really never bothered these and think the advantages of high level languages don’t really hold when working with micro controllers with tiny resources. They also rarely cover the whole functionality of the hardware and I.ve heard that your programs are much less stable than if you just flash your own firmware written in C.

                                          Can anyone give an example of where support for python, lua, JavaScript, etc saved the day?

                                          1. 3

                                            In case of ClojureScript the advantage comes from having a REPL that allows you to modify any part of the application at runtime. You can start up your app and continue tweaking it live as it’s running. I find this to be a far more productive workflow than the compile/run cycle especially when you have to push your code to the chip to try out the changes.

                                            1. 2

                                              I wrote some home automation code in micropython, it’s mostly mqtt to gpio or pwm. With micropython I don’t really need to flash or re-program for incremental code changes, and I could test some ideas on the fly by dropping to the repl shell. I didn’t notice any missing hw functional by using micropython, unless you are hacking with uncommon io interfaces. For stability, I don’t get why it’s less stable, it’s as stable as the CPU can process instructions, and CPU won’t make itself less stable when you run instructions from Python.

                                            1. 1

                                              We have no way to question our program, if we want to see a specific value at a point in time we have to insert multiple log statements and restart it each time until we get the information we need.

                                              This is wrong. Debuggers (and in particular, time traveling debuggers such as RR) let you ask such questions to your programs and in a much better way than logs let you. With time traveling, you can break at some point in your program, look at the program state, add watchpoints to variables that are “wrong”, rewind your program to understand what changed the variable… RR turns the art of debugging into a science.

                                              Don’t get me wrong, I still think that repls are nice. But I find that they don’t scale well and quickly become impossible to use on very large (tens of millions of LOC) programs. Meanwhile, debuggers work like a charm.

                                              1. 3

                                                Mind if you elaborate on repls not scaling well? From what I’ve gathered the whole point of this ‘conversational’ approach is to scale (hot-patching, compared to having to recompile and rerun every incremental change)

                                                1. 3

                                                  I’m retracting my claim as I just realized that debuggers are actually just REPLs for binaries.

                                                2. 2

                                                  Pretty much all Clojure development is done interactively via the REPL by connecting the editor to a running instance of the application, and I use the REPL all the time to develop large scale apps. I think the language plays a role here. Since Clojure defaults to immutability, and most of the code is written using pure functions, you rarely need the kind of debugging you’d use in an imperative language. Vast majority of the time you don’t need to trace state through the application, but you’re looking at a particular function you want to change in some way.

                                                  The idea that you’re just using printing to the console is also incorrect. Tools like this allow you to capture local state that you want to debug, and actually work with that state in a separate REPL. It’s a strictly more effective approach than debugging because you can get a program in a particular state, fix the problem within the scope of that state interactively, and keep going. All the debuggers I’ve used either don’t allow you to modify state during debugging, or have a fairly limited scope of what can be changed. So, you can use the debugger to trace, but then you have to restart and rebuild the state when you make any significant code changes completely breaking your workflow.

                                                  As a side note, I don’t really see why any application should be structured as a monolith with millions of LOC all coupled together. Any large project can, and should be, broken down into smaller isolated components that can be reasoned about independently. This also has the benefit of making code modular and reusable.

                                                  1. 1

                                                    I think the language plays a role here.

                                                    I agree.

                                                    All the debuggers I’ve used either don’t allow you to modify state during debugging, or have a fairly limited scope of what can be changed.

                                                    That’s interesting, what are these debuggers? I never tried to use anything else than GDB on large programs and the set and call commands let you modify state.

                                                    So, you can use the debugger to trace, but then you have to restart and rebuild the state when you make any significant code changes completely breaking your workflow.

                                                    I think this has never been a problem for me, I’ve always been able to create minimal reproducers for the bugs I’ve worked on. But I can imagine this is true for bugs in GUI programs that require lots of interactions to happen.

                                                    As a side note, I don’t really see why any application should be structured as a monolith with millions of LOC all coupled together.

                                                    Me neither and yet these applications exist and need to be debugged :).

                                                    1. 2

                                                      Java debuggers will typically allow limited amount of modification and reloading within the scope of a method. I’m not just talking about modifying state, but changing the code at runtime. If I spot a problem and I want to change the code, I want to be able to reload it and keep going without having to restart the process.

                                                      And I’m glad people are putting the work to debug these existing applications, it’s just not my cup of tea. :)

                                                1. 3

                                                  I recommend taking a look at ActivityPub federated blogging platforms such as Plume and Write.as.

                                                  1. 1

                                                    What’s the benefit of AP-supporting platforms vs a regular old site? Is it mainly so that people can follow your posts from e.g. mastodon?

                                                    1. 1

                                                      Yeah, it basically allows people using any platform that supports it, and to communicate with each other across different platforms. So, you get better discoverability overall. I tend to use Mastodon as my main feed akin to RSS nowadays.

                                                      AP also makes it easier for new service to bootstrap. For example, Pixelfed didn’t have the ability to follow people from different instances initially, but it was possible to follow different Pixelfed instanced via Mastodon. So, existing functionality in one part of the network helped boostrap another.

                                                  1. 13

                                                    The article does a great job highlighting how capitalism negatively impacts software development steering it primarily towards producing trade value for the capital owners resulting in creation of incredibly harmful software platforms like Facebook.

                                                    I am very encouraged by open source and federated platforms starting to become viable alternatives. Mastodon, PeerTube, and Pixelfed are all great examples of non-commercially driven platforms that bring us back to the way the internet was meant to operate in the first place. These platforms are developed by the users for the users, there are no ulterior motives such as monetizing the users by mining their data and activities or creating ad revenue by putting garbage on the page that’s directly at odds with providing good user experience.

                                                    One huge advantage ActivityPub federation has is that there is no incentive to create walled gardens and jealously guard the users from others. The federation flips the user economy model on its head from the way commercial social media operates. Ability to interoperate between the services benefits all the services in this model, and new services leverage the existing user base of the entire federation instead of having to create the user base from scratch.

                                                    Censorship is another area where federation fares much better than centralized platforms. Since there is no central databse or a single company that governs the entire network it’s much harder to censor content on the platform. It’s also possible to create private networks by spinning up your own server and limiting access.

                                                    I really think that the future lies with open source and federated services that communicate using open protocols like ActivityPub with nodes operated by many users in different countries around the world.

                                                    1. 18

                                                      It is not open source though. It’s license prohibits you from selling modified engine versions, which is not compliant with OSD point 1. Additionally, their definition for “Game Engine Product” is way too broad so it probably breaks OSD’s point 6. Some people have pointed this out to them, we are yet to see any reconsideration.

                                                      Edit: looks like they are changing their marketing to exclude the phrase open source.

                                                      1. 8

                                                        The language even looks misleading. They keep saying free and open on the first page. It’s how lots of sites say “FOSS.” Then, the next page says this:

                                                        “Defold is a free and open game engine with a permissive license derived from the popular Apache 2.0 License. The license grants you the freedom to develop games without worrying about paying licensing fees or royalties on your earnings.”

                                                        The references to permissive and Apache make the reader think it’s going to be like that. Then, clicking the license shows us it’s not like that at all. They need to fix all this so it doesn’t undermine the great thing they’re doing in sharing their code.

                                                        1. 3

                                                          thanks , updated the title

                                                          1. 4

                                                            Referring to it as a permissive license is a bit confusing, as permissive is often used to refer to BSD, MIT and similar licenses which have fewer restrictions than some of the copyleft licenses. The article and linked pages never describe the license as permissive, so why not just put the article title, “King shares the Defold game engine source code and invites collaborators and partners” as the submission title?

                                                            1. 2

                                                              Yeah, I see your point, can’t edit the title anymore unfortunately. I got the permissive part from the tweet linked in the above comment https://twitter.com/defold/status/1262744466311360517

                                                          2. 1

                                                            His Majesty giveth, but His Majesty also taketh away.

                                                          1. 8

                                                            One huge testament to the quality of re-frame design is that APIs and semantics have remained largely unchanged over the years. There have been a few new features added, but practically no breaking changes over the years. My team has some apps that have been in production close to half a decade now, and we just update the version to get speed improvements, features, and fixes.

                                                            Meanwhile, React ecosystem has had a ton of churn in that time. There have been lots of patterns introduced like redux, flux, hooks, and so on. If you started building an app using best practices a few years ago it’s likely to be considered legacy today. Modern React best practices also closely resemble re-frame further confirming in my mind that it got things fundamentally right from the start.

                                                            Re-frame completely insulates ClojureScript developers from the constant flux in the underlying React ecosystem. React can really be seen as just an implementation detail.

                                                            1. 1

                                                              I would call those apps “expat” apps instead of “native”.

                                                              1. 1

                                                                They’re hybrid apps where you use a native GUI layer and keep your logic in a Js runtime. Not sure what you mean by expat there to be honest.

                                                                1. 1

                                                                  It just lives there without interacting with its surroundings.

                                                                  How do you create and configure a NSTableView or build an NSAttributedString in ClojureScript?

                                                                  1. 1

                                                                    React Native allows wrapping any native UI components, here’s how that’s done. MS fork of RN works with macOS components in addition to iOS.

                                                                    1. 1

                                                                      So in order to actually do native programming, you must actually use a native programming language?

                                                                      That’s a whole lot of boilerplate to accomplish very little.

                                                                      1. 1

                                                                        You’d only be doing this if you needed a widget that didn’t already have a standard wrapper for it, which most do. You’re also only doing that once per custom widget type. I’m not sure what kind of programs you write, but I find most interesting things are happening in the business logic layer. Having to implement a custom widget once in a while isn’t my primary concern when it comes to writing and maintaining large projects. Perhaps that’s not the case for you though.

                                                                        1. 1

                                                                          It’s not about convenience or what’s interesting, it’s about whether your program is a native or an alien.

                                                                          Many other languages have been able to make direct use of Cocoa’s very flexible API, such as Ruby or Applescript, so I don’t see why ClojureScript needs to wrap everything.

                                                                          But that is beside the point. If you must wrap, you’re not native.

                                                              1. 12

                                                                A lot of the pro-lisp answers are because “people are biased against lisp”. I think this is unlikely. My realistic answer is that “Lisp is not actually that much better” or “language choice is a proxy for some other confounding factor like programming experience”. My crazypants answer is that powerful languages are actually bad for collaboration.

                                                                1. 8

                                                                  Having worked with Java for around a decade and then Clojure after, I can definitely say that Lisp is indeed much better in a wide range of scenarios. For example, here’s a presentation on the application my team develops, and the benefits we saw from using Clojure. I simply can’t imagine developing something like that with a small team using a language like Python.

                                                                  Clojure community is also a clear counterexample to the idea that Lisps are bad for collaboration. My personal experience is that I never contributed to a single Java project during my time with it. Since I moved to Clojure I’ve contributed to lots of libraries I use, and people often contribute to the ones I maintain. There’s a vibrant community around the language with lots of collaboration happening all the time. There are also lots of companies using Clojure commercially, and some have grown to have teams with dozens and even hundreds of developers. Here’s a talk from one company that grew to large scale using Clojure.

                                                                  1. 4

                                                                    Do you believe there are meaningful improvements you can make to lisps, or the lisp communities, that would make it more popular, or do you believe the issue is entirely with non-lisp users?

                                                                    I ask this because this seems similar to a common problem in formal methods communities: the assumption that it’s other people’s fault for not using FM, as opposed to a meaningful issue with the tool or community that drives people away. Often insiders have a very different view of the issues that the outsiders they want to reach do.

                                                                    1. 3

                                                                      I see Clojure as one such improvement. I find that the additional syntax it introduces with data structure literals results in significantly improved readability. I also think that since Clojure was aimed at production use from the start that helped shaped the tooling around it. Clojure has had a great story for managing libraries out of the gate because it piggy backs on the Java repository ecosystem. This facilitated collaboration and library use from the early days.

                                                                      Clojure also manages to side step the niche ecosystem problem by embracing being a hosted language. This gives it access to existing ecosystems around .NET, JVM, Python and Js. So, you don’t have to worry about finding mature drivers for databases, crypto libraries, and so on.

                                                                      I think that the growth of commercial Clojure use over the years speaks for itself. Nowadays, it’s used in many different domains by companies big and small, including Apple, Atlassian, Boeing, Walmart. In case of Walmart, tech isn’t even their primary business. If there was no practical advantage to using the language then we simply wouldn’t see this happening.

                                                                      1. 11

                                                                        And yet it’s still not nowhere near as popular as languages like Go, Kotlin, Rust, and Swift, languages that came out after Clojure. Go and Rust didn’t even have existing ecosystems! So why is Clojure not mainstream? What’s holding it back?

                                                                        I propose that you, as @yogthos, are not the right person to answer this question. That’s because you’re an expert in Clojure, and the price of expertise is expert blindness. Once you master something it is incredibly hard to understand what it’s like to not know it inside and out. You’ve rejected most of the problems people have provided in this thread because they are not problems once you’re an expert. But they’re still what people perceive about the language, and often they’re what people initially experience with the language.

                                                                        This has been on my mind a lot because I’m doing similar work for formal methods, interviewing people and researching what’s holding it back. The problem is that almost everybody in FM is already a master, so their efforts have for years been shaped by what masters think outsiders need, not what outsiders actually needed. This is f.ex why people have been putting a huge amount of time into switching Alloy analysis from SAT to SMT but nobody’s bothered to add a CLI yet.

                                                                        1. 6

                                                                          And yet it’s still not nowhere near as popular as languages like Go, Kotlin, Rust, and Swift, languages that came out after Clojure.

                                                                          We might need to differentiate in these discussions between languages that are popular for whatever set of reasons and languages that got popular because highly-influential companies pushed them. That’s 4 out of 4 on your list. Add .NET and Java to that. I mean, a company as rich as Apple announcing their next, big language was a systems Lisp instead of Swift probably would’ve led a Lisp to having massive uptake and investment overnight. Also hints at how to get a language to succeed against all odds: get a FAANG or whatever to make it a major language in their ecosystem, esp with money or users tied to it.

                                                                          Those not backed by highly-influential companies seem to be successful or not for other reasons. These Lisps would be judged by the other reasons. Looks like a lot of reasons versus the one, major driver for people adopting each of the above languages.

                                                                          1. 3

                                                                            There are counter points, however. Dart, Reason and Flow don’t seem to have nearly the buzz online that you see from Go/Rust/Kotlin/Swift, and those all had pushes from the same large companies.

                                                                            1. 2

                                                                              Those are good counterpoints. I’ll modify my claim to say I guess big company was primary advantage on top of other influences I’m less sure about.

                                                                              1. 2

                                                                                I’m not saying that a push from a big company isn’t a strong advantage, just that it’s not sufficient to drive adoption of a language. If it was, we’d all be writing VBScript in our web browsers, rather than JavaScript.

                                                                                There are some situations, like Apple + ObjC and Swift, where a company has the ability to drive adoption of a language pretty heavily.

                                                                                1. 1

                                                                                  It’s not just another good point. You just made me remember that I did write VBScript at one point. Had to keep reusing my BASIC skills. That’s one I won’t be relearning, though.

                                                                          2. 2

                                                                            I’m not sure why you find the fact that languages from the same popular family would be be more popular surprising. It’s much easier for somebody to go from one mainstream language to another because a lot of the concepts and patterns transfer directly. Learning a language from a different family takes a lot more up front effort. This is precisely why it’s so notable that Clojure is being used in the industry right now, and it’s adoption is growing.

                                                                            While I’m an expert at Clojure, I also have extensive experience teaching Clojure to beginners. My team has been using Clojure for around a decade now, and we regularly hire coop students who have little programing experience, and haven’t even heard of FP or Lisps. I think that gives me a pretty good basis to say that learning Clojure is not inherently more challenging than any mainstream language. I’ve rejected most of the problems people provided because I’ve worked with a lots of beginners who were able to overcome these problems easily. If a second year computer science student with little programming experience is able to pick up Clojure within a couple of weeks and write useful code, it seems silly to argue that the language is somehow inherently challenging. In fact, I can get somebody with no programming experience productive with Clojure much easier than I could with a language like Java because there are less concepts involved.

                                                                            1. 1

                                                                              and we regularly hire coop students who have little programing experience

                                                                              I’m kind of curious about your hiring process with that claim. Do you hire really smart people inclined to pick up any tech or average people willing to put in effort who learn because it’s truly easy? Have you accounted for that in assessing how easy it was to train them?

                                                                              I like testing tech usability on folks that are less technically-inclined or at least challenged when moving to new things. The easier it is on them, the easier it might be in general.

                                                                              1. 4

                                                                                Our experience is that it’s actually much easier to hire for Clojure than it was for Java just because it acts as a filter. We used to get hundreds of applications for a Java position, and we found that resumes were only loosely correlated to the actual skill of the candidate. With Clojure, we might get a dozen applicants, but they tend to be much better quality overall. People who apply for Clojure jobs tend to be open to learning and trying new things. We only hired one dev who had prior experience with Clojure, but the rest applied because they were interested in trying something different.

                                                                                During the interview we tend to focus on CS basics to see whether the candidate understands the use of data structures and algorithmic complexity. We don’t ask any puzzles in our interviews, and mostly treat it as a pair programming session. The goal is to see that the candidate has a solid understanding of the core programming principles, and that we can communicate easily.

                                                                                My experience teaching is that people can pick up Clojure very quickly with a bit of mentoring. We start by giving the hires a bit of an overview, get them to read through some online resources, and play with it in the REPL on their own. Then we pair up a bit to see if they’re on the right track. Next, we start giving them some small tasks to do, like simple JIRA tickets, we then do code reviews together to show them how to accomplish things in a more idiomatic way, and it goes from there.

                                                                    2. 3

                                                                      My crazypants answer is that powerful languages are actually bad for collaboration.

                                                                      There is a presumption here that collaboration is to be optimized for. Obviously, collaboration is a desirable quality in a language. But I’m not sold that we must sacrifice metaprogramming on the altar of collaboration. (I realize you’re not saying this.)

                                                                      I’m biased here; I’ve sunk a lot of time into malleable grammars, so there’s bound to be some sunk cost fallacy at work in me. But I worry that tech culture (whatever that is) seems to subconsciously adopt more corporate views of programming as time goes on, and this is another one. Namely, that programming is so error-prone and tricky, we must put as many guard rails in our tools as possible.

                                                                      Thus you get things like Elm mandating a certain architecture, Rails requiring files to be named a certain way, and avoidance of macros because “readability,” despite things like JSX doing exactly that. In short, we seem to be okay trusting that $MEGACORP engineers should wield maximum computational power when making these tools, but we never trust regular developers with that power.

                                                                      That seems a little too authoritarian for my taste.

                                                                      1. 4

                                                                        It makes sense to me that collaboration and metaprogramming are in opposition. In order for two people to collaborate, they need to communicate, which means they need a shared vocabulary. Meanwhile, metaprogramming is all about giving individuals the power to customise their vocabulary, but “unique, personal vocabulary” is the opposite of “shared vocabulary”.

                                                                        That’s not to say you must choose one or the other, since people can always learn the new vocabulary, and then they can collaborate. But that’s not a solution so much as another impractical extreme: you can avoid collaboration, you can avoid metaprogramming, you can do both and accept that you’ll spend a lot of time on education, or you can pick some point between all three with a little bit of collaboration, a little bit of metaprogramming, and a little bit of education to cover the difference. But whatever you choose, you can’t crank up both collaboration and metaprogramming at zero cost.

                                                                        As for authoritarianism in tech, as megacorps employ a huge number of technical people, and since they often talk about the problems they face and their impressively-engineered solutions, it’s only natural that “tech culture” reflects those ideas. To try to fight against those trends directly would be fighting against human nature, and that’s not a fight you can win.

                                                                        Instead, make cool stuff and talk about it. Build something neat with metaprogramming cranked up to the max, and show people what the benefits are. Talk about projects you’ve worked on that emphasised configurability and what the experience was like. Help broaden people’s horizons, and trust that they can figure out their own comfort zone among the alternatives they’ve seen.

                                                                        1. 3

                                                                          I wax poetic about AHK and love fringetech, you don’t need to convince me about the importance of expressiveness ;) I just think that this is a possible explanation for why the languages don’t become mainstream.

                                                                          (I wrote a followup defending this here.)

                                                                          1. 2

                                                                            But I’m not sold that we must sacrifice metaprogramming on the altar of collaboration.

                                                                            You said it buddy. I’m missing it so much with Python that I ended up dabbling with Hy Language. The trick that experienced Lispers told me was that you can stay within the regular language in a maintainable style as much as possible. Then, use macros where they solve more problems than they create.

                                                                          2. 1

                                                                            I don’t think this correct, mainly because people who have tried both, seem to prefer clojure to python (maybe selection bias). I think the answer is somewhere between people use what they tried first, and people have pretty closed minds, its better but not THAAAT much better when you are an average programmer.

                                                                          1. 3

                                                                            I find it telling that almost every comment that points out a missing part in the Lisp metaverse is given a retort of “But Clojure…”

                                                                            Clojure is not Lisp. It’s an implementation of it. That’s the problem - there’s not a reference implementation that can be pointed to as the “reference” to follow. Because of that, excessive fragmentation is a guarantee. There are so many of them out there, each with large enough standard library differences to make it difficult for a fair fraction of developers to think twice. All of the Python interpreters out there that I know of provide a fairly consistent standard library such that it’s not surprising. They pretty much behave the same way with the same semantics and grammar. C and C++ have large hidebound standardization committees and a large audience to demand a lot of primitives and behaviors are present in most general situations.

                                                                            The fragmented nature of the Lisp family means that it’s clojure vs racket vs scheme… A whole bunch of versus and infighting about what really is the definitive Lisp. And because of that, each one fights on its own, succeeding on its own, et al. An advocate for Clojure is less of a lisp advocate and more of the Clojure advocate. A Python advocate doesn’t have that burden, so their advocacy suffers less from message incoherence.

                                                                            As a side note, yes, Python 2/3 is a perfect example of a reference splitting the ecosystem and there’s been much effort into choosing one over the other to resolve that split.

                                                                            1. 3

                                                                              I don’t really follow your argument here. Lisp is a family of languages, and it’s exactly the same situation with Algol style languages. You have C, C++, Ruby, Python, Go Java, Rust, etc. Python is just one language in a huge family that’s bigger and less consistent than Lisps. Fragmented nature of Algol family means that it’s Ruby vs Python vs Go. A Python advocate is certainly not a Ruby advocate any more than Clojure advocate being a CL advocate.

                                                                              1. 2

                                                                                The question asks why Lisp (language) is not as popular as Python (language). Aside from a few pedants, most people don’t compare or even talk about Algol vs Lisp. They talk about Lisp (usually picking their favorite implementation as the language) vs another language.

                                                                                You can’t have it both ways Y. The discussion is around “why isn’t {{language}} popular”. Not “why isn’t {{language family}}popular?”

                                                                                No one seriously would conflate C and Python despite being under the same nebulous Algol umbrella. Languages are the tools that are used. Language families are multiple sections of the tool store.

                                                                                1. 4

                                                                                  The write up is clearly talking about Lisp family of languages as it uses examples from different Lisps. It seems to me that you’re the one trying to have it both ways here by lumping all Lisps together and comparing them to Python as if similar languages like Ruby and Go don’t exist.

                                                                                  I’m simply pointing out that your argument regarding fragmentation doesn’t really make sense because the exact same argument holds for languages like Ruby, Python, and Go.

                                                                                  1. 2

                                                                                    Actually I’m pointing out the fundamental comparison is flawed. For example, you don’t compare “Declarative languages” to C - you compare C to a declarative language or set of them.

                                                                                    Also, you’re flat out wrong. There’s multiple write ups of different opinions, many of them anti-lisp in general. To say the writeup covers the family is disingenuous.

                                                                                    1. 3

                                                                                      When you phrase it that way, I completely agree. It would be much better to pick a concrete Lisp like CL or Clojure and compare it against Python.

                                                                            1. 3

                                                                              I’ve written mostly Java for the last 15 years. I write Java code every day. I kept up with the latest and tried to take part in the community up until about Java 8. I love Java. But there are just some things that I miss. I’d love to have tuples, like in Python or Rust. I’d love the pattern matching feature of Rust. There are some generics type information that I simply cannot get during run-time (I.e. using lambdas for generic static functions) And last, but not least, I always run into problems working with char[] and strings.

                                                                              In some ways I think being so adamant on always being backwards compatible has held Java back a bit. I’d love. I’d be totally fine with the major LTS releases not being backwards compatible.

                                                                              1. 4

                                                                                I recommend taking a look at Kotlin. You get access to the whole Java ecosystem with a very concise and expressive language without making a class for every little thing.

                                                                                1. 1

                                                                                  Kotlin is a nicer language than Java but Java has much better static analysis tools.
                                                                                  I would stick with Java if static analysis is important to you.
                                                                                  Here are some examples of IntelliJ Java inspections that aren’t yet available in Kotlin:

                                                                                  • Class metrics
                                                                                  • Concurrency annotation issues
                                                                                  • Control flow issues
                                                                                  • Data flow
                                                                                  • Dependency issues
                                                                                  • Error handling
                                                                                  • Finalization
                                                                                  • Internationalization
                                                                                  • JUnit
                                                                                  • Logging
                                                                                  • Method metrics
                                                                                  • Performance
                                                                                  • Portability
                                                                                  • Serialization Issues
                                                                                  • Threading Issues
                                                                                  • TestNG
                                                                                    The Kotlin language design fixes some issues but many of these inspections would apply to Kotlin.
                                                                                  1. 1

                                                                                    No static analysis tool in the world would make me stick with Java. I read the same code more often than said tools do, and Java is more code doing less.

                                                                                2. 2

                                                                                  I recommend taking a look at Scala. You get access to the whole Java ecosystem with a very concise and expressive language with a REPL, allowing you to mix functional and imperative styles however you see fit.

                                                                                  1. 2

                                                                                    Disclaimer: My experiences are about five years dated. Updates on the state of Scala are appreciated.

                                                                                    I have learned the basics of Scala, but I found the complete experience underwhelming.

                                                                                    • The tooling was slow and a bit unwieldy.
                                                                                    • I found the language and libraries to use excessive amount of cryptic operators. To me it was a hieroglyph language, akin to APL. Same inline operators with English words would have been friendlier.
                                                                                    • I appreciate the idea of making illegal states unrepresentable, but I saw some advanced generic code in Scala, and it seemed just as hard to understand as with complex OOP inheritance hierarchies, and it did not seem to simplify understandability and maintainability of the code.

                                                                                    My impression was that me maybe too much OOP is left in it, and it combines the complexities of OOP anti-patterns with the complexities of a very strict type system.

                                                                                    Right now I’m experimenting with F# (mixed paradigm similarly to Scala) and I find the ML based syntax friendlier, and my impression is that its type system is simpler (more limited maybe), but it doesn’t encourage to do so overly complicated types. I’ll see, when get to dive into medium size projects which I saw with Scala, what will my impressions be.

                                                                                    Also I feel it has lost traction. Kotlin is the thing now, backed by the most important players. It is not such a radical step forward(?)[^1] like Scala.

                                                                                    [^1]: or sideways, to different paradigm

                                                                                  2. 2

                                                                                    I recommend taking a look at Clojure. You get access to the whole Java ecosystem with a very concise and expressive language with a REPL driven workflow that’s not available in any mainstream language.

                                                                                    1. 1

                                                                                      The concrete problems would really interest me, especially about the runtime genetic type informations.

                                                                                      C#, which I have been working mostly in the last few years, generally provides most things you mention. You should really go with Kotlin instead, as that lets you take advantage of your knowledge of the JVM ecosystem. I’m just curious if apart from that C# would address your pain points. Most are explicitly available features in new versions (C# 8.0 being the latest I think), but the generics are a bit different, due to the lack of type erasure, which is sometimes a bliss, sometimes a curse.

                                                                                      It has its own flaws, around async/await being incomplete, and having messed up some patterns imho, but that is a different case.

                                                                                      C# is very similar to Java, but has been less obsessed about backward compatibility, but I can see that it has now always worked out fine. (Most novelties are nice and useful alltogether)

                                                                                      1. 1

                                                                                        Here is an example I ran into the other day. I have a generic helper function for memoization:

                                                                                        Private static final Map<Object, Map<Supplier<?>, Object>> cache = new ConcurrentHashMap<>()
                                                                                        
                                                                                        public static <T> T memo(Supplier<T> o) {
                                                                                                return (T) cache
                                                                                                        .computeIfAbsent(o.getClass(), k -> new ConcurrentHashMap<>())
                                                                                                        .computeIfAbsent(o, k -> o.get());
                                                                                            }
                                                                                        

                                                                                        I’d like to use something else than o.getClass() as key, as it will return something different each time if I call it with a lambda like so:

                                                                                        Util.memoize(() -> longCalculation(1));

                                                                                        Instead I’d like to get some kind of signature. For example the return type, the actual runtime type T. As far as I know, this cannot be done.

                                                                                      2. 1

                                                                                        To be honest, the main reason I used Python is the huge documentation on getting started with it. How to write new modules, the zen of python, libraries very well explained when you start with the language…

                                                                                        I’ve tried Java, but it always felt very “magic” to me each time I’ve tried to use recommended frameworks like Spring or Akka… Do you have resources to learn about the JVM, and the Java ecosystem in general?