[HN Gopher] Why I still Lisp
       ___________________________________________________________________
        
       Why I still Lisp
        
       Author : lycopodiopsida
       Score  : 215 points
       Date   : 2021-01-31 11:59 UTC (11 hours ago)
        
 (HTM) web link (mendhekar.medium.com)
 (TXT) w3m dump (mendhekar.medium.com)
        
       | vector_spaces wrote:
       | Wow. I thought I recognized that name - the author of this post
       | interviewed me for a job several years ago when I was first
       | learning how to code. It didn't work out as I was waaaaaaay too
       | junior at the time but I'll always remember that interview
       | fondly.
       | 
       | My background was more in math and my interests were in PL
       | theory, so he quizzed me on the lambda calculus, and turned me
       | onto Essentials of Programming Languages by Friedman & Wand (and
       | off the bad compiler books I had been reading previously), and
       | pointed me towards material for further study in programming
       | languages and lambda calculus.
       | 
       | The interview was still fairly practically oriented despite those
       | detours, and I learned a great deal that helped me to quickly
       | find a solid technical role not long after. But more importantly,
       | he and his colleagues who interviewed me were incredibly kind and
       | respectful, and instilled this sense that both computer science
       | and software engineering could be a lot of fun. :)
        
         | Donckele wrote:
         | You should apply again, the company the author works for is
         | looking for people "Proficient in Java and C++ with strong
         | object-oriented design skills".
        
       | tromp wrote:
       | I find it odd that the author qualifies Haskell as ugly and its
       | ancestor Miranda as very beautiful as the two seem pretty similar
       | in syntax to me. I find beauty in both.
       | 
       | I would only qualify Template Haskell as ugly.
        
       | mikewarot wrote:
       | I've installed portacle, and ended up in a nest of errors I
       | didn't understand.
       | 
       | Can anyone here recommend a non-editor based version of lisp?
        
         | smabie wrote:
         | what is a editor based version of Lisp?
         | 
         | fwiw, I really like Racket. It's definitely the most
         | clean/elegant Lisp around.
        
           | mikewarot wrote:
           | Thank you!
           | 
           | Racket gives me error messages, which I can learn to
           | understand, and doesn't take me into Emacs like portacle
           | does.
           | 
           | The tutorial helps too... more thanks!
        
       | jgwil2 wrote:
       | > I have never had a static type checker (regardless of how
       | sophisticated it is) help me prevent anything more than an
       | obvious error (which should be caught in testing anyway).
       | 
       | This is the static vs dynamic type debate in a nutshell.
       | Personally I think Typescript hits a sweet spot of static typing
       | by default with an escape hatch (via the `any` type) when you
       | need it. I think more self-documenting code makes the tradeoff
       | well worth it (with the added guarantee that the documentation is
       | correct). And most importantly, I like getting feedback from the
       | editor _as I 'm typing_ rather than having to write code then run
       | tests, then write some more and perhaps modify the tests, etc.
        
         | IshKebab wrote:
         | It's like saying you don't need a spell checker becuase you
         | never make mistakes. Pure hubris.
        
           | kazinator wrote:
           | No, it's like saying "I can't remember the last time I needed
           | to turn my document into a string literal embedded into the
           | word processor's source code, so that a spelling error could
           | be caught when the word processor is recompiled. I test my
           | document using an easy-to-use run-time spell checking
           | feature."
        
             | jgwil2 wrote:
             | This is not an apt analogy at all. You don't recompile your
             | editor to get type errors. The question is type checking vs
             | unit tests. If your text editor can type check as you type,
             | the feedback loop is much tighter than recompiling _and_
             | rerunning _and_ possibly modifying handwritten unit tests.
        
         | frou_dh wrote:
         | In that debate, we quite often hear something along the lines
         | of "I can't remember the last time a static typechecker
         | actually helped me" and that makes me imagine a fish saying
         | they can't remember the last time that water actually helped
         | them. Maybe the ambient help is so constant that it no longer
         | even registers in long-term memory.
        
         | tazjin wrote:
         | On the other hand, I've worked with Erlang systems where almost
         | all issues we saw in production would have been prevented by a
         | type-checker.
         | 
         | There are, however, very few ergonomic type systems. At the
         | moment I think Rust is, sadly, the only one.
        
           | coldtea wrote:
           | What makes it ergonomic in your opinion?
        
         | coldtea wrote:
         | > _Personally I think Typescript hits a sweet spot of static
         | typing by default with an escape hatch (via the `any` type)
         | when you need it._
         | 
         | That has been available in Obj-C, C# and others...
        
         | mssundaram wrote:
         | Even better, use `unknown` when at all possible instead of
         | `any` - it's like `any` but you need to assert/inspect it to
         | unwrap anything from it.
        
       | saladbarstalker wrote:
       | It's a shame that so many Americans lack access to a speech
       | pathologist.
        
       | TedShiller wrote:
       | Why I'm a bad programmer
        
       | hota_mazi wrote:
       | > As a programmer, I carry around invariants (which is a fancy
       | name for properties about things in my program) in my head all
       | the time.
       | 
       | The author has probably not worked on large code bases or even in
       | teams.
       | 
       | I often forget even my own code's purpose six months after having
       | written it, let alone code written by teams of hundreds of people
       | and millions of lines of code.
       | 
       | Dynamically types will kill you in such (common) situations.
       | 
       | > In other words, static typing is pointless.
       | 
       | Yes, author is definitely very young.
       | 
       | There are two types of programmers: programmers who know that
       | static typing is the only sane way to write robust, scalable,
       | maintainable code, and programmers who haven't been in this
       | profession for long enough.
        
         | didibus wrote:
         | > I started serious programming in my teens in BASIC on a ZX
         | Spectrum+, although I had previously dabbled in (hand-) writing
         | Fortran programs
         | 
         | > I ended up studying Programming Languages at Indiana
         | University with Dan Friedman (of The Little Lisper / The Little
         | Schemer fame). It was my introduction to Scheme (and the world
         | of Lisp.) I finally knew that I had found the perfect medium to
         | express my programs in. And it has not changed in those last 25
         | years
         | 
         | Author seems to have over 25 years of experience.
        
       | kazinator wrote:
       | Hi all,
       | 
       | I'm preparing release 250 of the TXR Language, the bulk of which
       | is a Lisp dialect called TXR Lisp.
       | 
       | Release 250 adds compiler optimizations, like jump threading,
       | some dead code elimination, and a few peephole reductions.
       | 
       | This work is finally possible because I had put it on hold due to
       | not wanting to write such code without a pattern matcher, which
       | we now have.
       | 
       | I'm fixing two bugs that were reported by a user, which will be
       | nice in such a landmark release (250 releases, wow!).
       | 
       | The new structural pattern matching sub-language in TXR Lisp (new
       | since 247 or 248) will be improved. Bugs are fixed. The way the
       | @(or ...) pattern operator works has been rewritten, so certain
       | corner test cases now pass. It generates better code.
       | 
       | There are will be some new features in the matcher too. The @[fun
       | ...] flavor of the predicate operator now lets you capture not
       | only the variable, as usual, but using an extra argument, the
       | value of the predicate (which could be an extended Boolean with
       | an interesting value).
       | 
       | For instance, is "abc" in the hash table htab? If so capture it
       | as x, and also capture the value as y:                 This is
       | the TXR Lisp interactive listener of TXR 249.       Quit with
       | :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
       | Upgrade to TXR Pro for a one-time fee of learning Lisp!       1>
       | (let ((htab #H(() ("abc" "foo") ("xyz" "bar"))))
       | (when-match @[htab x y] "abc" (list x y)))       ("abc" "foo")
       | 
       | Here, htab is just being used as a function; any function will
       | work that can take the argument "abc"; if it returns non-nil,
       | then the predicate has rung true, the pattern has matched, and x
       | takes "abc", and y the returned value.
       | 
       | By the way, to see quips like "Upgrade to TXR Pro for a one-time
       | fee of learning Lisp!" you must add this to your ~/.txr_profile:
       | (put-line (quip))
        
       | Blikkentrekker wrote:
       | > _But it has now taken a new interpretation in the last couple
       | of decades: Static typing is a form of compile-time error
       | checking, so it will help you produce better quality code. It is
       | as if static typing is a magical theorem prover that will verify
       | some deep properties of your program. This is where I call_
       | bullsh*t _._ [ _sic_ ] _I have never had a static type checker
       | (regardless of how sophisticated it is) help me prevent anything
       | more than an obvious error (which should be caught in testing
       | anyway)._
       | 
       | That's an anecdote; facts are that serious critical bugs that
       | lead to privilege escalation and loss of data have existed in
       | codebases that would surely have been caught by various static
       | type systems.
       | 
       | > _In other words, static typing is pointless. It has, maybe,
       | some documentary value, but it does not substitute documentation
       | on other invariants. For example, your invariant might be
       | something like I'm expecting here a monotonically increasing
       | array of numbers with a mean value of such and such and a
       | standard deviation of such and such. The best any static type
       | checking will let you do is "array[float]". The rest of your
       | invariant must be expressed in words as a documentation of that
       | function. So why subject yourself to the misery of
       | "array[float]"?_
       | 
       | With dependent typing, it's actually quite possible to both
       | define a type for "sorted list" and prove to the type checker
       | that a sorting algorithm does sort it.
        
         | didibus wrote:
         | > That's an anecdote; facts are that serious critical bugs that
         | lead to privilege escalation and loss of data have existed in
         | codebases that would surely have been caught by various static
         | type systems.
         | 
         | An anecdote at this point is at least better than a baseless
         | claim as you're making.
         | 
         | Also keep in mind the article is in the context of Lisp, so it
         | would be static types on top of a garbage collected memory
         | managed runtime with runtime type checks.
        
         | kazinator wrote:
         | > That's an anecdote; facts are that serious critical bugs that
         | lead to privilege escalation and loss of data have existed in
         | codebases that would surely have been caught by various static
         | type systems.
         | 
         | Do you have any non-strawman examples of this (e.g. C security
         | holes are strawman).
        
           | AnimalMuppet wrote:
           | Given who you are, I'm going to give you the benefit of the
           | doubt, but... _What?_ C security holes are strawman? What is
           | your basis for saying so?
        
             | kazinator wrote:
             | Firstly, C is statically typed, so if you're arguing for
             | static typing as the cure for security holes, citing C
             | examples of security hoes won't speak well for your
             | argument Secondly, C has an incomplete static system, which
             | has no run-time safety net to compensate for it.
             | 
             | Many properties of C programs go unchecked. What is checked
             | is easily defeated with type casts. Even the numeric
             | conversions lack safety; e.g. floating point to integer
             | conversion is silently implicit, with undefined behavior if
             | the value is out of range.
             | 
             | All of that is a straw man example if we are arguing
             | against incomplete static type systems, because the iron
             | man is incomplete static with strong dynamic checks.
             | 
             | C also doesn't provide access to the language at compile
             | time; we cannot execute test cases for the code in the same
             | breath as compiling it. If code is cross-compiled, it needs
             | unit tests compiled into individual executable programs,
             | and an emulator like QEMU to run them.
             | 
             | Because C doesn't have run-time safety, unit tests do not
             | reliably flush out type errors; programs with type errors
             | can pass unit tests by fluke due to undefined behavior.
             | E.g. a string that is not properly null terminated can have
             | a zero byte in the right place anyway during the execution
             | of a test case, and so on.
             | 
             | C security holes are not a good example to invoke in a
             | static/dynamic debate as something that could be prevented
             | with static, so I'm asking that: if those are the examples,
             | please spare the debate.
        
       | jtth wrote:
       | Anyone know of something like Rails for a Lisp?
        
       | apricot wrote:
       | A great read for a lazy Sunday morning. The author's tastes in
       | programming languages agree with my own, and he's defending them
       | in an eloquent way so it's an article I read drinking coffee and
       | nodding my head.
       | 
       | Also describing Miranda as "Ugly Haskell's beautiful mom", and
       | invoking old Hindi movie censorship to belittle static type
       | checking is just mmph. <Italian chef's kiss>
        
       | carapace wrote:
       | Lisp is too complicated.
       | 
       | For years I used to say, "I'm keeping a bank of brain cells free
       | for learning Lisp one day." I knew it was important, I knew one
       | day I would sit down and learn Lisp. Eventually, that day came.
       | As it happened, I chose PG's book to start with, and (as is my
       | wont) I began with the Table of Contents. That's as far as I got.
       | Somehow, in the years leading up to that moment, I had acquired
       | the necessary background information to be able to "get" Lisp
       | just from reading that ToC. My mind was blown. Here was the
       | perfect, the complete, the only needful language. And then I got
       | mad. I got mad at you and me and all our peers. For wasting time.
       | For wasting attention. For fucking around with syntax and
       | bullshit for fwcking decades when we had Lisp. All the time and
       | energy and money, burned like so much trash, because not Lisp.
       | 
       | So did I start using Lisp? No, of course not. Python pays (paid)
       | the bills. Sick world.
       | 
       | - - - -
       | 
       | Anyway, in the meantime, I discovered a language called Joy. I'm
       | pretty sure it's the simplest useful language. It is a point-free
       | form that's good for Categorical Programming. It's not based on
       | Lambda Calculus so there are no variables, no binding
       | environments. You can do algebra on it and it's easier than
       | Squiggol (
       | https://en.wikipedia.org/wiki/Bird%E2%80%93Meertens_formalis...
       | ).
       | 
       | I was messing around with Make-a-Lisp the other day but I didn't
       | get very far because, as I say, Lisp is too complicated (not to
       | understand, to implement. It's a PITA.) Joy is simpler. The
       | syntax is trivial, the grammar is trivial, the interpreter is
       | simple, it's just a beautiful elegant formal system.
       | 
       | To be clear, I'm not complaining about Lisp, I think Lisp is the
       | best language (Except for Joy, which is even better, IMO.)
        
         | csb6 wrote:
         | > Somehow, in the years leading up to that moment, I had
         | acquired the necessary background information to be able to
         | "get" Lisp just from reading that ToC.
         | 
         | Damn, you are a genius! As for myself, I can only gain this
         | level of understanding by skimming the index (or maybe the
         | appendices). ;)
        
       | jedimastert wrote:
       | > next cool feature being dropped into Python or Scala, or
       | whatever their flavor of the month is
       | 
       | I might be off-base here, but I feel like Python is at least _a
       | little_ mature enough to not really justify calling it  "flavor
       | of the month"
        
       | omar_kha wrote:
       | if lisp is so good, where all the libraries to create cool stuff?
        
         | tjr wrote:
         | What libraries are missing?
        
       | kingdomcome50 wrote:
       | > I have never had a static type checker (regardless of how
       | sophisticated it is) help me prevent anything more than an
       | obvious error (which should be caught in testing anyway).
       | 
       | "I have never worked in a non-trivial system that changed over
       | time."
       | 
       | FTFY
        
       | mark_l_watson wrote:
       | Lisp development is niche, but has sufficient use so that
       | languages like Common Lisp, Clojure, and Racket will hopefully be
       | supported and enjoyed for a long time.
       | 
       | The author hits on the sore point of available libraries to hit
       | APIs and I would add access to deep learning infrastructure. I
       | use the Hy language (hylang, has a Clojure style syntax) that is
       | built on Python (you can get a copy of my Hy book for free, the
       | minimum price is free https://leanpub.com/hy-lisp-python), but I
       | still like Common Lisp as my main driver.
       | 
       | Anyway, I consider myself to be very fortunate to have about half
       | of my career since 1982 use Lisp languages.
        
       | zokier wrote:
       | > For example, your invariant might be something like I'm
       | expecting here a monotonically increasing array of numbers with a
       | mean value of such and such and a standard deviation of such and
       | such. The best any static type checking will let you do is
       | "array[float]".
       | 
       | Isn't this just plainly false? With dependent type system
       | wouldn't you be able to encode all such invariants into the
       | types? Curry-Howard isomorphism and so on?
        
         | taeric wrote:
         | I suspect the problem is the types begin to get fairly heavy
         | weight. It can also start requiring you to make many atomic
         | operations so that your invariant holds across larger chunks of
         | code. And, that often isn't easy or really necessary.
        
         | sn41 wrote:
         | I think so. Using existential types, we can encode predicates
         | in such a way that the type is inhabited (i.e. there is a value
         | of that type) only if the predicate is true.
         | 
         | I am learning Forth and Idris in my spare time these days. I
         | have tried writing merge of two sorted lists in Idris, with the
         | constraint that the merged list must have the length equal to
         | the sum of the lengths of the input lists etc. - frankly, I
         | much prefer Forth :D
         | 
         | https://www.idris-lang.org/
        
         | [deleted]
        
       | HerrMonnezza wrote:
       | In other words, static typing is pointless. It has, maybe,
       | some documentary value, but it does not substitute
       | documentation on other invariants. For example, your
       | invariant might be something like I'm expecting here a
       | monotonically increasing array of numbers with a mean
       | value of such and such and a standard deviation of such
       | and such. The best any static type checking will let you
       | do is "array[float]". The rest of your invariant must be
       | expressed in words as a documentation of that function.
       | 
       | I wonder if "design by contract" [1] tooling would help here? It
       | seems to me one could express the stated invariants of the array
       | as pre- or post-conditions on any function processing that array.
       | 
       | [1]: https://wiki.c2.com/?DesignByContract
        
         | clankyclanker wrote:
         | Thank you for mentioning that and reminding me about two of my
         | favorite projects, Eiffel and PyContract. (Is part of why DbC
         | is so useful is because it's an extremely concise way to write
         | assertions?)
         | 
         | I'd love static typing systems if they allowed for DbC-style
         | type declarations. There are times when the thing I need to use
         | isn't a generic int, or even a short int, but an integer three
         | or more. That's also type information, just not type
         | information based on the shape of the data in memory. I've
         | never seen a type system that can make those determinations at
         | compile time, even when working with literal numbers in source
         | code.
        
           | ywei3410 wrote:
           | Dependent type-systems like Agda can do that. Unfortunately
           | they are also a pain to write.
           | 
           | Fun-fact, you can use church-encoding to transform from your
           | generic int to a "shape" based encoding for the slowest
           | operations ever in quite a few languages (Python included).
        
         | haskellandchill wrote:
         | You can express those invariants using dependent types. Look at
         | something like Lean.
        
         | [deleted]
        
         | chromatin wrote:
         | Great point.in addition to the excellent sibling comment on
         | Ada, I would add that I have personally benefited (in the field
         | of scientific computing) from contract programming facilities
         | available in D
         | 
         | https://dlang.org/spec/contracts.html
        
         | johnisgood wrote:
         | That is funny, Ada/SPARK is not mentioned at all! How strange.
         | 
         | https://en.wikibooks.org/wiki/Ada_Programming/Contract_Based...
         | 
         | http://www.ada-auth.org/standards/12rat/html/Rat12-2-3.html
         | 
         | https://docs.adacore.com/spark2014-docs/html/ug/en/source/ho...
         | 
         | https://blog.adacore.com/contracts-of-functions-in-spark-201...
         | 
         | Plus, I do not believe that static typing is pointless. Even if
         | it were ONLY for documentation, that would already make it
         | pretty useful, in my opinion, like Erlang's type
         | specifications, although there is a static analysis tool called
         | dialyzer that identifies software discrepancies such as type
         | errors and such.
         | 
         | In any case, from AdaCore's website:
         | 
         | > In statically typed languages, a type is mainly (but not
         | only) a compile time construct. It is a construct to enforce
         | invariants about the behavior of a program. Invariants are
         | unchangeable properties that hold for all variables of a given
         | type. Enforcing them ensures, for example, that variables of a
         | data type never have invalid values.
         | 
         | > A type is used to reason about the objects a program
         | manipulates (an object is a variable or a constant). The aim is
         | to classify objects by what you can accomplish with them (i.e.,
         | the operations that are permitted), and this way you can reason
         | about the correctness of the objects' values.
         | 
         | Just for the curious:
         | 
         | > A nice feature of Ada is that you can define your own integer
         | types, based on the requirements of your program (i.e., the
         | range of values that makes sense). In fact, the definitional
         | mechanism that Ada provides forms the semantic basis for the
         | predefined integer types. There is no "magical" built-in type
         | in that regard, which is unlike most languages, and arguably
         | very elegant.
        
           | pfdietz wrote:
           | And then if you don't test that Ada code, you blow up your
           | Ariane 5.
        
             | johnisgood wrote:
             | https://www.researchgate.net/publication/220475937_Design_b
             | y...
             | 
             | For the record: that was ages ago, the language has
             | improved a lot since then. Ada 2012 includes features for
             | contracts, for example. Read the "Preface" of "Programming
             | in Ada 2012" for details. :)
        
       | bstpierre wrote:
       | > What improves (and guarantees) software quality is rigorous
       | testing. To deliver high quality software, there is no other
       | solution.
       | 
       | This is 100% false. You can't inject quality into a bad design
       | via testing. You can't guarantee quality or correctness through
       | testing. Testing is the second worst place to find errors.
       | 
       | You get a MUCH higher roi by producing thoughtful written designs
       | and getting them peer reviewed. And again with code reviews.
       | (Peer review is best but self review can also be very effective.)
       | I'm not saying don't test -- testing is valuable, especially
       | because it forces you to design for testability, and a suite of
       | automated tests is valuable when making changes later.
        
         | noisy_boy wrote:
         | Testing tends to validate design from the point of testability.
         | If it feels like the code is resisting testing, that usually
         | ends up being an indicator of bad design (though not always
         | vice versa).
        
           | karmakaze wrote:
           | This is where we are now. We know how to make testable
           | designs, since effort has been put towards it.
           | 
           | What we lost is being able to distinguish good vs bad designs
           | in other key aspects. One indicator I use is the number of
           | test cases that are needed for covering. A poorly separated
           | design will need to test many combinations having not
           | isolated the factors.
        
           | plafl wrote:
           | Exactly. Testability requires modularity and reproducibility.
        
         | koonsolo wrote:
         | I wonder how much experience you have in the real world. All of
         | us developers had this thought at some point.
         | 
         | I've seen terrible code with plenty of quick hacks on top of
         | that, which was running very stable, because it was battle
         | tested for years in production.
         | 
         | What do you think will happen when you refactor such code to a
         | better design? All juniors would think this is the best way to
         | get a stable codebase.
         | 
         | Reality is that you will introduce bugs by refactoring, not get
         | rid of it. Writing code means writing bugs, no matter how
         | pretty your code is.
         | 
         | This reminds me of some quote that "Junior developers write 1
         | bug per 10 lines of code, experienced coders know they write 1
         | bug per 10 lines of code"
        
           | ledauphin wrote:
           | this is pure argument from authority.
           | 
           | I value testing, but primarily because (in years of
           | experience, etc.) it makes code easier to refactor or reuse,
           | not easier to make _correct_. Actually, this also applies to
           | static types, which I also value.
           | 
           | But suitability-to-purpose (the best proxy for correctness
           | that I've yet found) is far more determined by peer-reviewed
           | design than by tests.
        
           | brabel wrote:
           | You might be one of those experienced developers who has gone
           | through their entire career without ever meeting a well
           | designed, beautifully architectured application, and
           | therefore concluded, sensibly, that all real-world
           | applications look like shit if you look inside, and you seem
           | to have come to believe that this is the only possible way.
           | 
           | I have a different experience. Code that is well designed
           | makes mistakes look obvious and the logic feel natural. It's
           | very hard to write code like that, but I've seen it and I've
           | been able to write it myself, so when I see a bunch of
           | complicated code that only tests can tell whether or not it
           | works, I know that the problem is really that the best design
           | simply has not been found yet. If I find code that looks like
           | shit and I realize there's a better design for it that will
           | make the code clear and natural, I won't think twice to
           | refactor it ruthlessly if such code has been a source of
           | problems and time wasted (and fixing difficult to read code
           | is huge costly to morale as well, which when taken into
           | consideration, increases the cost of technical debt by a
           | lot).
        
             | koonsolo wrote:
             | > a well designed, beautifully architectured application
             | 
             | I've seen everything during my career, bad code with lots
             | of bugs, bad code running stable. Nice code full of bugs,
             | and nice stable code.
             | 
             | The reason something was stable was never how the code
             | looked, but how battle tested the product was. You will
             | learn, don't worry.
             | 
             | Plus, at a certain point, it's about tradoffs and
             | compromises. I would love to see highly optimized code that
             | is easy to read.
             | 
             | > only tests can tell whether or not it works
             | 
             | Tests are nice, but the real test will be when users start
             | breaking your app.
             | 
             | I'm curious, what kind of product are you writing?
        
               | mpfundstein wrote:
               | you assume a little bit to much about your discussion
               | partners. calling them inexperienced etc...
               | 
               | this takes away the whole strength of your argument plus
               | lets you sound like a douche.
               | 
               | plz fix
        
         | tggreene wrote:
         | I agree that testing is not a panacea, but I also don't think
         | it is completely divorced from design.
         | 
         | I see testing and design as cooperative processes, I agree that
         | design is almost always the highest yielding process in any
         | non-trivial program, but I think of testing almost as a design
         | phase that tries to invert the design to look for weaknesses.
        
           | chestervonwinch wrote:
           | Right, spending all your effort on the one true design gets
           | you in architect astronaut territory. Similarly for existing
           | systems, conjuring up the grand rewrite in the sky (as Robert
           | Martin calls it, I think?) is equally poor. Instead,
           | recognize that design is important but will constantly
           | evolve. Tests give the ability to incrementally redesign
           | quickly and confidently.
        
           | Zelphyr wrote:
           | I suspect what he's referring to is the fact that so many
           | people think that, because they have 100% code coverage in
           | their tests, they have quality software. I've seen so many
           | times a team claim that with pride when it's brutally obvious
           | to anyone who looks that the product is broken. They just
           | don't seem to be able to reconcile that problem when asked
           | about it.
           | 
           | What you test matters every bit as much as how much you test.
           | Tests require thought and design as well. Too many developers
           | don't seem to understand that.
        
             | bstpierre wrote:
             | That's part of what I was talking about, but the original
             | statement in the article was that rigorous testing
             | guarantees software quality, and it also says "there is no
             | other solution", and that is demonstrably false.
             | 
             | One example: A telecom product that processed phone calls.
             | I stepped into a role on the team building the call
             | progression handling system, which was based on an ad hoc
             | state machine and some inter-task communication that
             | included a combination of mutexes and message queues. It
             | was a mess. Our testers succeeded in churning out a lot of
             | bug reports. Not a single one of those bug reports did
             | anything to improve the quality of the system -- even if
             | we're not being pedantic and saying that only the patches
             | in response to the bug reports were what might have
             | improved the quality. Because none of the patching we could
             | do really made any net increase in quality. The only thing
             | that improved system quality was stepping back and re-
             | examining the design. We changed the existing undocumented
             | design to get rid of deadlocks by just using one inter-task
             | mechanism (just queues, no mutexes) and by formalizing the
             | state machines so that none of them got stranded in an
             | unexpected state. Once the design was formalized this way
             | we found -- by inspection, not testing -- a number of
             | fundamental bugs in the states and transitions. After
             | fixing those, more system testing showed some more bugs,
             | but we were able to assess these in the context of the
             | design and fix them at the root cause rather than just
             | hacking on patches. The design changes also enabled
             | automated integration testing that wasn't possible with the
             | old ball of mud. This helped save time in allowing the
             | [mostly manual] system testers to focus on certain areas of
             | the system.
             | 
             | I've worked in other situations where lots of testing just
             | resulted in lots of bug reports, which ended up mostly
             | getting deferred and thus did nothing whatsoever to improve
             | product quality.
             | 
             | And I've worked on software teams that were in fact
             | producing rather high quality software, but we were
             | building the wrong thing. It's really hard to fix bad
             | requirements through testing.
             | 
             | I don't want to sound like I'm anti-testing, but I think it
             | should be understood that testing isn't the source of
             | quality, it's just the last phase of a process that
             | hopefully has (non-test) mechanisms to ensure a quality
             | product in the upstream process phases.
        
       | jgwil2 wrote:
       | > This feature, called tail call optimization (or TCO), has been
       | around for a few decades and it is a sad commentary on the state
       | of our programming languages that none of the modern languages
       | support it.
       | 
       | TCO is part of ECMAScript specification, although apparently only
       | Safari supports it.[0]
       | 
       | [0] https://2ality.com/2015/06/tail-call-optimization.html
        
         | bjoli wrote:
         | Which, considering JavaScript is a language many compile to,
         | sucks. I have said it many times: recursion is unbiased.
         | Compiling a languages looping facility to something recursive
         | is simple. Forcing one looping facility into the shape of
         | another looping facility is the path to suffering.
        
       | indymike wrote:
       | When I look at languages I use regularly (largely Emacs), Lisp is
       | the one I'd love to try to build something large with. It feels
       | quite powerful and the syntax is pretty easy to understand. It
       | could just be my personal lack of experience with Lisp, but there
       | seems to be little standard in naming conventions and code
       | formatting. This may be where a lot of developers look at Lisp
       | code and declare it an unmaintainable mess. Often I've seen
       | multiple styles of formatting in the same file when I look at
       | non-emacs production code.
        
       | dj_mc_merlin wrote:
       | > You're always thinking about how information is acted upon, how
       | it is transformed, and how it is produced. I have yet to find a
       | foundational framework that captures this inherent intentionality
       | (the 'how') that is better than the l-calculus.
       | 
       | Yes! Lambda calculus is my "native brain programming language"
       | and I can't find a language the expresses it better than the
       | Lisps. I never have to bend around the language, it feels as if I
       | can always do exactly the method I wanted to solve something.
       | 
       | Another big benefit is the simple syntax makes complex
       | metaprogramming achievable. You just move the s-expressions and
       | atoms around. Unnest and nest them. Like normal code. It makes
       | sense.
        
       | arendtio wrote:
       | > Call-by-value
       | 
       | > Mostly Functional
       | 
       | > Dynamically Typed
       | 
       | Sounds a bit like POSIX shell.
       | 
       | Yeah, I know that especially people doing functional programming
       | do not want to be compared to shell scripters, but since I know
       | how horrible shell scripting is, I wonder if there could be a
       | future, where a proper, mostly functional language could be the
       | successor of the very practical bash. I mean, if we aren't doing
       | anything soon, JavaScript might take that place...
        
         | pjmlp wrote:
         | POSIX shells are not as powerful as Lisp REPLs and Smalltalk
         | transcript windows.
        
       | coldtea wrote:
       | > _I have never had a static type checker (regardless of how
       | sophisticated it is) help me prevent anything more than an
       | obvious error (which should be caught in testing anyway)._
       | 
       | Obvious in retrospect is not the same as obvious. And such errors
       | happen all the time, the same way without syntax checks typos
       | happen all the time.
       | 
       | And "caught in testing" is 2 extra steps removed from caught
       | immediately by the syntax checker running as you type or save:
       | writing the tests and running the tests (and being thorough/lucky
       | enough that this part of the code is covered in a test).
       | 
       | > _But it is a stupid tool. It can only do so much. So, you now
       | end up with artificial rules about how to satisfy this tool. And
       | things that I know (and can justify or even formally prove for my
       | use cases) are perfectly fine to do are suddenly not._
       | 
       | What thing that violates a type check would be "perfectly fine to
       | do"?
       | 
       | Dynamic languages still have types (since you still need to pass
       | around the right object with the right methods or fields to the
       | call site, else it will raise an exception when you use a
       | method/access a field/run a function on the wrong type).
       | def square(value):          return value*value
       | 
       | If value is not a numeric type, square is not going to be happy.
       | And that's the case with most (all?) dynamic code.
       | 
       | What would be a counter-example that something not satisfying to
       | a type checker would be "perfectly fine to do" in a dynamic
       | language?
       | 
       | (I do accept that the static typed language is better to have
       | generic and/or algebraic types, no make lots of invariants easier
       | to express without ceremony).
        
         | AQXt wrote:
         | > What thing that violates a type check would be "perfectly
         | fine to do"?
         | 
         | A typical example, in dynamic programming languages, is to
         | treat numbers as strings and vice-versa; evaluate lists in
         | boolean context; and so on.
         | 
         | > If value is not a numeric type, square is not going to be
         | happy. And that's the case with most (all?) dynamic code.
         | 
         | Some programming languages will be glad to accept a string and
         | treat it as a number -- see Duck Typing
         | (https://en.wikipedia.org/wiki/Duck_typing).
        
           | adwn wrote:
           | > _A typical example, in dynamic programming languages, is to
           | treat numbers as strings and vice-versa_
           | 
           | That sounds great until it bites you in the ass really hard.
           | In general, you want to catch unintended behavior _as early
           | as possible_.
        
           | ArchieMaclean wrote:
           | Can you give an example of where doing this is useful?
        
           | smabie wrote:
           | that sounds really awful, not "fine."
        
         | seepel wrote:
         | > What thing that violates a type check would be "perfectly
         | fine to do"?
         | 
         | Here is an example limitation of typescript's type system that
         | I routinely run into while developing real code [1].
         | 
         | I look at it like this. If you consider all possible programs,
         | some are invalid, some are valid, and some are valid and
         | useful. A type system's job is to reject as many invalid
         | programs as possible while accepting as many valid programs as
         | possible and trying to optimize for useful valid programs. Due
         | to the halting problem this is impossible to do perfectly, so
         | any given type system will likely accept some invalid programs
         | and reject some useful valid programs. If the type system
         | happens to reject your useful valid program, you'll likely have
         | a bad day :)
         | 
         | [1]
         | https://www.typescriptlang.org/play?#code/PTAEHUFNQCwQwG7TgO...
        
           | temp24582934 wrote:
           | Sometimes the type system is forcing you to think in terms of
           | what it is that you are passing around. In this case,
           | getEmails() isn't expecting a group of students or a group of
           | faculty, but rather a group of people that can be emailed.
           | You can introduce an interface of that type and have it
           | inherited by both Student and Faculty and use that in the
           | method for clarity and type-safety without over-relying on
           | union types: https://www.typescriptlang.org/play?ssl=1&ssc=1&
           | pln=38&pc=30...
        
         | lisper wrote:
         | > If value is not a numeric type, square is not going to be
         | happy.
         | 
         | That is not necessarily true. In Common Lisp, with its generic-
         | function based object system, you can extend the operation of
         | any function at any time to cover new types. So, for example,
         | one could define a method for SQUARE that operates on matrices.
         | 
         | > What thing that violates a type check would be "perfectly
         | fine to do"?
         | 
         | (define (self-apply fn) (fn fn))
        
           | adwn wrote:
           | > _(define (self-apply fn) (fn fn))_
           | 
           | Out of curiousity, what would be a practical application of
           | this?
        
             | lisper wrote:
             | See:
             | 
             | http://www.flownet.com/ron/lambda-calculus.html
        
               | adwn wrote:
               | That didn't answer my question. Let me rephrase it: When
               | would you use                   (define (self-apply fn)
               | (fn fn))
               | 
               | in an actual, real-life situation (outside of teaching
               | lambda calculus)?
        
               | lisper wrote:
               | Personally, I consider teaching the lambda calculus to be
               | a real-life situation. Teaching the lambda calculus is no
               | less real to me than any other part of my life. But OK,
               | self-apply is not something you're likely to see in
               | production code, and neither are Church numerals, which
               | is where this problem shows up for real. But MAP, REDUCE,
               | and APPLY are, and they all have the same problem as
               | self-apply: their static types are infinite.
               | 
               | This is a general problem with all static type systems.
               | There are only two possibilities: either your type system
               | is Turing complete or it is not. If it is not, then there
               | are things that one might reasonably want to express that
               | cannot be expressed, and if it is, then it is
               | undecidable. Static typing is either constraining, or it
               | is a Turing Tarpit
               | (https://en.wikipedia.org/wiki/Turing_tarpit).
        
               | kazinator wrote:
               | Without additional arguments? Never. With additional
               | arguments, it provides the function with access to
               | itself.
               | 
               | This could be used "under the hood" in an implementation
               | to bootstrap the ability of named local functions to have
               | themselves in scope, which is useful for writing ordinary
               | recursion.
               | 
               | Compilers and interpreters can achieve such a thing
               | without any such jig like a Y combinator, because
               | interpreters and compilers work with environments that
               | are re-ified as objects. They are free to manipulate
               | environments and evaluate/compile any piece of code in
               | any environment they see fit.
        
           | rini17 wrote:
           | Most implementations of Common Lisp prevent shadowing builtin
           | symbols. You get compile/runtime error like "lock on COMMON-
           | LISP package violated". So, unless author went to extra
           | lengths to work around it, you can rely on the standard stuff
           | like * when you read the code.
           | 
           | Even for user-defined functions, it's not an usual practice
           | to extend operation of any function (although again, it's
           | kind of possible). There are CLOS generic functions for that.
        
             | lisper wrote:
             | > Most implementations of Common Lisp prevent shadowing
             | builtin symbols.
             | 
             | So? Make your own package.
        
         | ywei3410 wrote:
         | An example:
         | 
         | > y = [1] ++ [2] ;; Assume types List[Int], List[Int]
         | 
         | > y[0] ;; This should return Union[Int, None]
         | 
         | > y[0] + 3 ;; This should fail type-checking
         | 
         | You /know/ the list isn't empty. Sure you can argue that
         | /maybe/ we should be using NonEmptyList, but now imagine this
         | code.
         | 
         | > y = [1, 2] ;; NonEmptyList[Int]
         | 
         | > y = (filter (lambda x: x % 2 == 1) y) ;; Has to return
         | List[Int]
         | 
         | > y[0] ;; Union[Int, None]
         | 
         | Again, we hit into type issues.
        
           | iso8859-1 wrote:
           | The type system is a proof system. In your examples, the
           | types do not imply the invariants that you want.
           | 
           | Does this mean that the type system is faulty? No! It just
           | means that you need to e.g. get a more expressive type
           | system, document code properly, use smart constructors. There
           | are many tools.
           | 
           | Nobody said you can trivially prove everything using type
           | systems. So what are your examples showing?
           | 
           | I see your code, but I don't know what you're actually trying
           | to do. One interpretation could be, that you want to prove
           | that for the naturals (excluding 0), up to and including 2,
           | there is an odd number. Can one prove this using type
           | systems? Definitely. If you read Software Foundations, you
           | will know how.
        
             | ywei3410 wrote:
             | Sure - but the point is that there are /diminishing
             | returns/ on type-systems. The question is then, /where/
             | should one draw the line when trying to prove certain
             | properties of code; which properties are worthwhile
             | proving?
             | 
             | Are you /seriously/ suggesting that one should use Agda or
             | Coq for most code?
             | 
             | EDIT. The original comment was suggesting as though dynamic
             | and static type-systems are a dichotomy. I was pointing out
             | that it is a sliding scale from nominally-typed ala Go, all
             | the way to dependently-typed languages; and that's not even
             | including model-checkers!
        
         | tines wrote:
         | It depends on how sophisticated your type system is. For a pure
         | HM type system, anything requiring rank-n types won't type
         | check, but are theoretically sound in a type system that
         | supports them.
        
         | dack wrote:
         | > What thing that violates a type check would be "perfectly
         | fine to do"?
         | 
         | One good example is where you might treat records or "product
         | types" as maps
         | 
         | Let's say you want to write a function that can capitalize all
         | the string fields in the object passed in. In a dynamic
         | language, you could map over the values and apply
         | capitalization trivially. It would be a one-liner.
         | 
         | In a static language, you'd have a few options, but none are
         | great:
         | 
         | 1. You could use "reflection" or some dynamic feature to do
         | this, but lose type-safety
         | 
         | 2. You could make the function take in the union of all
         | possible data types in your system, and then write a mapping
         | function for each one. Then you're duplicating the logic N
         | times, or at the least writing a lot of conversion boilerplate.
         | 
         | 3. You could write two functions for each data type in your
         | system to convert to a Map<String, Object> or some such and
         | back, which lets you operate on the maps. Again, lots of
         | boilerplate. Maybe you use code-generation for this? That would
         | add complexity.
         | 
         | In reality, you probably wouldn't even try to write such a
         | function in a typed language because of how awkward it is.
         | Maybe you'd just write specific translation functions for the
         | records you know you happen to need. But I think that's what
         | people mean when they say you're restricted by the type-system
         | - because it can't verify those types of operations, you find
         | workarounds when maybe that would have been the easiest way to
         | implement something.
        
           | mssundaram wrote:
           | It's pretty easy to do in Typescript and loses no type safety
           | (without needing to assert or cast anything)
           | type T = { [k: string]: number };                  const t: T
           | = {           one: 1,           two: 2,           three: 3,
           | };                  const makeUpperCaseKeys = (v: T): T => {
           | const keys = Object.keys(v);           return keys.reduce((p,
           | c) => {             const key = c.charAt(0).toUpperCase() +
           | c.slice(1);             return { ...p, [key]: v[c] };
           | }, {});         };
           | console.log(makeUpperCaseKeys(t));         // {         //
           | One: 1,         //   Two: 2,         //   Three: 3         //
           | }
        
             | dack wrote:
             | That's great! Are you making the argument that I won't be
             | able to find an example where doing some transform in a
             | type-safe way is awkward in TypeScript, or just that this
             | one example can be done in TypeScript?
             | 
             | By the way, my example was meant to be more like User ->
             | User where there are some string values and some other
             | types of value. You want to do the transformation in a
             | generic way, but still have the type-safe object come out
             | the other end with all the expected keys.
        
               | [deleted]
        
               | sbergot wrote:
               | You can try this with typescript 4.1 :
               | 
               | interface User { name: string; age: number; }
               | 
               | type CapitalizeProperties<T> = { [Property in keyof T as
               | Capitalize<string & Property>]: T[Property]; };
               | 
               | type CUser = CapitalizeProperties<User>;
               | 
               | var c : CUser = { Age: 17, Name: "john" }
               | 
               | You can check that the autocompletion works on the c
               | variable.
               | 
               | https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=15&pc
               | =2#...
        
               | [deleted]
        
           | IshKebab wrote:
           | That's not a very compelling example - in most statically
           | typed languages the object field names are not actually
           | stored in member, so it makes no sense to want to do runtime
           | operations on them, because they don't exist by the time the
           | code is compiled.
           | 
           | That's why you find it hard to do in a compiled statically
           | typed language - because it's not something that really makes
           | any sense.
        
           | adwn wrote:
           | As a proponent of static typing (at least for larger
           | programs): thank you, that was a very enlightening example
           | for when dynamic typing can be superior to static typing.
        
           | coldtea wrote:
           | > _1. You could use "reflection" or some dynamic feature to
           | do this, but lose type-safety_
           | 
           | By using reflection you only lose type-safety for this
           | particular function. With a dynamic language, you lose it
           | everywhere.
           | 
           | And it's still an one-liner or close in a language without
           | much ceremony (e.g. not Java 1.5).
           | 
           | Example implementation (pseudo-code):                 fun
           | capitalize (o: object) -> [f.toUpper() for f in
           | reflection.fields(o) if reflection.type(f) == "string"]
        
           | iso8859-1 wrote:
           | I reject the claim that it is reasonable to capitalize all
           | the string fields of arbitrary objects.
           | 
           | But it can still be done, like you mention, by using
           | reflection. Now, you claim that it is unsafe. This is untrue,
           | it depends how the reflection works. Even if it were unsafe,
           | it would be no worse than in Python, where you get a
           | TypeError or in JavaScript where you get undefined, which
           | probably leads to a crash later.
           | 
           | For an example of reflection which is safer than just a
           | string-dictionary like JavaScript or Python, see
           | GHC.Generics. It works by compile-time generating a type safe
           | structure which is _not_ just a dictionary. The function you
           | want would have a signature involving this constraint,
           | "Generic x => x -> x", which means it works for any "object"
           | implementing the Generic interface.
           | 
           | Now you may say "but I asked for a function that handles all
           | objects", but I don't see the point. In statically typed
           | languages you get an _additional_ option of using generics
           | when you want. It doesn 't make sense to me to require all
           | types to implement reflection when you don't actually need it
           | everywhere.
        
           | madmax96 wrote:
           | There is no limit on the number of problems easier to solve
           | using dynamically typed languages. For an individual hacker,
           | dynamically typed languages make a lot of sense. I don't
           | forget what types my functions accept as I am writing a
           | program. All static typing can accomplish is slow me down
           | when I'm trying to bang out something. This is why Python
           | (for instance) is loved by data scientists, researchers, and
           | startups. And they're right to love it! Purely anecdotal, but
           | I can accomplish more faster (from a clean slate) with a
           | dynamic language like Python than I can with Haskell, Go, or
           | Rust. The difference is small, but non-negligible.
           | 
           | But static typing has a few key benefits:
           | 
           | * No searching through comments for type constraints.
           | 
           | * Better autocompletion.
           | 
           | * Better compilation.
           | 
           | * Better serialization/deserialization.
           | 
           | I do think static typing pays for itself in the long term.
           | Sometimes, we shouldn't care about that. Engineering means
           | managing tradeoffs: short-term velocity vs long-term
           | performance and maintainability?
        
             | coldtea wrote:
             | > _There is no limit on the number of problems easier to
             | solve using dynamically typed languages._
             | 
             | So, some examples? Also examples where "easier" is not just
             | "you don't need to write types". That goes without
             | saying...
        
             | moocowtruck wrote:
             | how about using something like clojure.spec where you need
             | it most to help make that long term payment?
        
             | ywei3410 wrote:
             | The auto-completion is purely Python's dynamic-dispatch
             | problem though. Lisp has symbol-based auto-completion which
             | doesn't suffer from the same issue (search is a different
             | story!).
             | 
             | This sounds terribly controversial, but I would love to
             | know /when/ the static-typing pays off compared to a looser
             | approach like sound-gradual typing (where you basically
             | export types at a boundary and make sure that you don't
             | violate those).
             | 
             | I do wonder whether a better approach is to prove your code
             | in some other language (say Z3, Agda, Coq) - then implement
             | it in something else, making sure you can prove
             | isomorphism. This has quite a few benefits; you're not tied
             | to a constructive proof on the type-level ala Haskell,
             | Rust, Go etc... and you can automate a large part of
             | trivial proofs.
        
         | kazinator wrote:
         | > _And such errors happen all the time, the same way without
         | syntax checks typos happen all the time._
         | 
         | No they don't, because syntax is richly varied and nested, even
         | in programs whose "type story" is bland.
         | 
         | For instance, in some numerical program, there are lots of
         | opportunities to make typos in the syntax, but the type of just
         | about everything may be either _float_ or else _array of float_
         | (possiby _string_ , if it has any error-handling code with
         | messages, and _bool_ if there are some logical operations).
         | 
         | If you pass an _array of float_ where a scalar _float_ is
         | expected, such that an error occurs, and if you don 't catch
         | this in your testing, it means you're not testing the code.
         | 
         | Untested code is of a dubious status, even if it compiles with
         | a static type system; testing is not negotiable.
         | 
         | Note that functions like sin and cos have exactly the same type
         | signature, yet it is disastrous if you mix them up. Static type
         | checking doesn't help. Static type checking also doesn't help
         | with mixed up variables: calling f(x, y) which should have been
         | f(y, x) or something else, where x and y have the same type.
         | 
         | In programs, chunks of code that are put together into the same
         | module or function often work with multiple instances of the
         | same type. It's more important not to mix up those instances,
         | than to worry about type errors. The code could be wrong in all
         | sorts of ways, yet statically check.
         | 
         | That's where you need to step up the the argument into "True
         | Scotsman's type systems territory": a sufficiently advanced
         | type system can encode all those properties that prevent the
         | mixups that the everyday type system doesn't. Yeah, well,
         | nobody uses that; nobody understands it outside of a narrow
         | slice of academia. Examples of the technique are such that
         | encoding even a trivial property like "list is in sorted order"
         | results in an a considerable increase of program complexity all
         | concentrated in one place, and less easy to understand than a
         | set of test cases against the obvious program. Yet, it doesn't
         | eliminate the need for testing; nothing does. There can now be
         | a bug in the way the desired property was encoded into the
         | program. Perhaps the sorted property was correctly encoded, but
         | it should have been descending order. The test case will catch
         | it.
         | 
         | When you have a language that is available at compile time, you
         | can execute test cases as part of compilation. Test cases _are_
         | therefore static checks. Anything happening at build time is a
         | static check. Heck, how a git commit message is formatted is a
         | static check, if a repository commit hook validates it.
        
         | tom_ wrote:
         | Squaring is usually value*value.
         | 
         | (a minor nit that shouldn't detract from your point - since in
         | a dynamic language, you'd have potential type errors in
         | addition to this sort of thing...)
        
           | coldtea wrote:
           | > _Squaring is usually value_ value.*
           | 
           | This version is optimized for the value of 2! (thanks,
           | checked).
        
           | [deleted]
        
       | carapace wrote:
       | > Now wait a minute, I'm sure you're thinking. I've never proved
       | sh*t about my functions. I'm betting that you have. And that you
       | do all the time. You're always convincing yourself that your
       | function is doing the right thing. Yours may not be a formal
       | proof (which may be what leads to some bugs), but reasoning about
       | code is something that software developers do all the time.
       | They're playing the code back in their head to see how it
       | behaves.
       | 
       | I sometimes try to point this out when arguing in favor of Formal
       | Methods. You're already using _informal_ Formal Methods anyway.
       | Just write your reasoning down and make the computer double-check
       | it.
       | 
       | "Oh the expense!" I hear them cry. But where's the spreadsheet
       | comparing that with the expense of the faulty reasoning? Where is
       | the Manhattan Project to lower the cost of logic?
       | 
       | And don't get me started on (what Guy Steele Jr. calls) Computer
       | Science Meta-notation, which has no official standard, and isn't
       | a runnable language. (
       | https://groups.csail.mit.edu/mac/users/gjs/6.945/readings/St... )
        
       | karlicoss wrote:
       | > Languages based on the l-calculus make it really easy to "play
       | back the code" in your head.
       | 
       | This really depends on the expression.. I've seen behemoth
       | expressions such that you have no choice but write down
       | intermediate results, or start explicitly breaking it down into
       | sub-bindings so you could step through the code. And yes, you can
       | usually step through subexpressions in the debugger, but if you
       | have to debug anyway, it hardly matters that you can 'play back'
       | the code in your head.
       | 
       | I also noticed there is a tendency in lispy languages to avoid
       | introducing variable bindings just for the sake of naming the
       | subexpression, because it often comes with a `(let ((name
       | (subexpression))) rest-of-code)`, which also results in extra
       | tabulation for the rest of the body. Compare with variable
       | introduction in languages like python/js/c++/rust, etc., where
       | such thing wouldn't cause touching the rest of the
       | function/clause. I guess it's more of a 'functional language'
       | artifact, e.g. in Haskell you'd probably have a similar issue.
       | 
       | The simple rules don't remove the complexity, the evaluation
       | state has to live somewhere.
       | 
       | > I have never had a static type checker (regardless of how
       | sophisticated it is) help me prevent anything more than an
       | obvious error (which should be caught in testing anyway).
       | 
       | Err, really? Never dereferenced a null pointer, or tried using a
       | list instead of a set? Static types _are_ testing, it doesn 't
       | replace it, but massively saves mechanical work on writing dozens
       | of dumb tests. Having tests is very important for documentation,
       | regressions, end-to-end/integartion testing, but every test for
       | something stupid as 'throws on passing null' is just a time drain
       | (unless you're launching rockets or something of course).
       | 
       | Anyway, it's possible to have the best of both worlds by using
       | gradual typing like mypy or JS flow -- you get all the runtime
       | benefits (flexibility/ability to temporary violate invariants)
       | and you gradually harden code which makes sense to type. I really
       | wish Elisp (the lisp I'm mostly dealing with) had some sort of
       | types, at least for simple things list 'associative list',
       | 'string', 'function reference', 'nullable thing', this would
       | massively save me time on catching bugs. It has runtime type
       | checks for `defcustom` things, but never seen type checking
       | anywhere else (except for occasional runtime asserts).
       | 
       | > The biggest advantage of this form of syntax is a form of
       | minimalism -- you don't need spurious syntactic constructs to
       | convey concepts.
       | 
       | > call these things macros, or syntactic extensions. In other
       | words, you can extend the syntax of your language to introduce
       | new abstractions.
       | 
       | I find these two sentences a bit contradictory ;) Anyway, I
       | personally like macros, if used sparingly it can really help. One
       | great upside of s-expressions for me is that you can do some cool
       | things like 'find and replace' for whole subexpressions (for
       | monkey patching third party code, for example). I use `el-patch`
       | [0] in my emacs config and `advice-patch` for surgically changing
       | the default behaviors of some org-mode functions to compile my
       | blog [1].
       | 
       | That said a similar sort of thing is possible, for example, in
       | python with `patchy` [2], and perhaps many other languages? But I
       | guess it's not as organic as in lisps, e.g. `advice-patch`
       | implementation is less than 100 LOC, whereas in case of python
       | you have to rely on existing heavy lifting done by `ast` module.
       | 
       | As of simple syntax, it really gets in the way sometimes, e.g.
       | I'm always annoyed by constant quoting in Elisp because the same
       | type of brackets (only `()`) is used. In comparison, in Clojure
       | it's much more readable with (), [], {} (and more).
       | 
       | > Lisp is not an interpreted language. It is not slow
       | 
       | A bit of nitpicking, but... _which_ Lisp? :) Anyway, these days
       | it 's often meaningless to say 'slow' without having a specific
       | workload in mind and having done benchmarking; hardware
       | improvements make it very hard to reason about.
       | 
       | > all implementations come with lots and lots of levers to tweak
       | performance for most programs. In some cases the programs might
       | need assistance from faster languages like C and C++ because they
       | are closer to the hardware, but with faster hardware, even that
       | difference is becoming irrelevant.
       | 
       | Well, this is true of most languages.
       | 
       | [0] https://github.com/raxod502/el-patch#el-patch
       | 
       | [1]
       | https://github.com/karlicoss/beepb00p/blob/a4fd7cb95e1705412...
       | 
       | [2] https://github.com/adamchainz/patchy#patchy
        
         | zelphirkalt wrote:
         | > I also noticed there is a tendency in lispy languages to
         | avoid introducing variable bindings just for the sake of naming
         | the subexpression, because it often comes with a `(let ((name
         | (subexpression))) rest-of-code)`, which also results in extra
         | tabulation for the rest of the body. Compare with variable
         | introduction in languages like python/js/c++/rust, etc., where
         | such thing wouldn't cause touching the rest of the
         | function/clause. I guess it's more of a 'functional language'
         | artifact, e.g. in Haskell you'd probably have a similar issue.
         | 
         | I have found myself thinking about "Should I really introduce
         | another let form for naming these things I am going to use at
         | the next procedure call, to make it more readable? Or would it
         | make the code less readable, because of the additional
         | indent?". So there is something to this point. Right now, while
         | not writing code, I would say:
         | 
         | The indentation make the scope of the bindings clear. It tells
         | you exactly, until where a binding will be defined. It also
         | allows you to shadow a binding from an outer scope for the
         | purpose of using it in the inner scope. However shadowing
         | should be used with care, because it can also get in the way of
         | readability, if used wrongly. It can however also increase
         | readability, if used well.
         | 
         | In a language like Python, you can only write things at the
         | same level of indentation. It does not make it clear until
         | where these bindings might possibly be used. The scopes are not
         | as separated as in Lispy languages with the let form. That
         | inherently makes it not as readable, I think. "The rest of the
         | function" is not a very precise scope specification. Many
         | bindings might only be used for one or a few subexpressions and
         | not everywhere until the function ends. This only makes sense
         | though, if the code has side effects. Otherwise there will be
         | no difference in where the bindings still exist, because what
         | in Python is the rest of the "function", will be the inner
         | parts of the expression of a procedure in Lispy languages.
         | 
         | I have found myself looking for a thing like let in Python
         | though. I have sometimes thought: "Hey, can't I use a with-
         | block for making a temporary binding?", but that has never
         | worked for me, because I think you need to make a context
         | manager for that? It has never materialized in my style of
         | Python code. (I know Hy, but until it can offer me TCO and
         | lambdas and let forms like Scheme, I am not sure I would like
         | to use it.)
         | 
         | EDIT: Ah and not to forget about let* which can often reduce
         | the amount of indentation a lot. Sometimes I find it good to
         | make use of it.
        
           | Abhinav2000 wrote:
           | When would shadow binding help create more readable code? (I
           | agree with everything else in your post)
        
             | zelphirkalt wrote:
             | I think it would do that, if in the inner scope, you only
             | need a part of something from the outer scope. Lets say you
             | have a list of things and they have the name according to
             | what they are, lets use "things" as a placeholder. Then in
             | the inner scope, you are still working with "things", just
             | not all of the outer scope, but for reading the code of the
             | inner scope, it does not matter. You are still working with
             | "things". OK, this is quite abstract. I am thinking about
             | it in something like nested named lets, where you make a
             | functional update to something, that comes from the outer
             | named let, inside the inner named let and then use it as an
             | argument to a procedure call. Situations like that. And
             | perhaps only, when it is difficult to name this subset of
             | things anything else than the original name. If there is
             | easily available another fitting name, then I would always
             | go for that.
             | 
             | It does make renaming things harder though.
        
       | lmilcin wrote:
       | I think Lisp is going to live forever as a niche tool for people
       | who "get" it.
       | 
       | I use Clojure on a daily basis. Not necessarily because it is
       | best Lisp, but rather because I am working with Java applications
       | and being able to reuse the same Java code I have already
       | developed is a huge boon to me. If I was able to choose, I would
       | be using Common Lisp.
       | 
       | The way I use it is to quickly develop adhoc tools and PoCs and
       | more rarely small UIs in re-frame.
       | 
       | Working with Lisp is a joy for me and I always get this feeling
       | of frustration when I have to go back to Java, especially if I
       | have to translate to Java what I just prototyped in Clojure.
       | 
       | Unfortunately, where I work (financial systems for financial
       | institutions), I have trouble finding enough people, mature
       | enough to be able to even propose working projects in Clojure.
       | 
       | Close in my org structure there is a set of Clojure projects
       | where the company got badly burnt. The code is a mess and a huge
       | headache for management. The guys who wrote it were either
       | promoted (claiming outstanding work on the project but not
       | wanting to maintain it) or left. Now new developers are barely
       | able to change anything without blowing it up.
       | 
       | This underlines the fact that Lisp projects can very easily end
       | in spectacular disasters. You have to be really incompetent to
       | write a typical Java service in a way where it is no longer
       | possible to develop it at all, but in Clojure it is just enough
       | to get couple of people that are intelligent enough to write
       | macros but not experienced enough to understand the dangers of
       | lack framework forcing the structure of your application.
        
         | agumonkey wrote:
         | I've read a few times that Java and enterprise languages are so
         | horribly tedious for the purpose of slowing down armies of
         | monkeys to avoid catastrophic design.
         | 
         | ps: what kind of clojure projects do you have in mind ?
        
         | piercebot wrote:
         | How do you find REPL integration in Common Lisp compares with
         | Clojure? I find myself blissfully productive in Clojure, and I
         | wonder if it's the same for all Lisps
        
           | pjmlp wrote:
           | Common Lisp invented the REPL, Clojure only provides a subset
           | of it.
           | 
           | Have a look at, "The Interlisp Programming Environment",
           | 
           | https://www.computer.org/csdl/magazine/co/1981/04/01667317/1.
           | ..
           | 
           | Or online demos like https://youtu.be/OBfB2MJw3qg for
           | Symbolics.
           | 
           | Ultimately you can try the community versions of Allegro and
           | LispWorks.
           | 
           | https://franz.com/enterprise_development_tools.lhtml
           | 
           | http://www.lispworks.com/
        
         | didibus wrote:
         | > You have to be really incompetent to write a typical Java
         | service in a way where it is no longer possible to develop it
         | at all
         | 
         | I've seen multiple Java services reach this state and require
         | complete rewrite as the only path forward. I think you've just
         | had one kind of experience and are making conclusions out of
         | it, but in your case, I don't think the programming language is
         | a factor. You just happened to see some failed Clojure
         | projects, as I happen to have seen failed Java projects.
        
         | vslira wrote:
         | Have you looked at Nubank? Largest neobank in the world by
         | users and valuation, it's a clojure shop to such an extent they
         | literally bought Cognitect
         | 
         | No affiliation, just a satisfied customer
        
         | VLM wrote:
         | An excellent analogy would be the metric system vs imperial
         | measurement system, at least in the USA.
         | 
         | A productive and profitable country of feet and pounds and
         | inches, but significant progress is usually made in labs full
         | of meters and liters.
         | 
         | One could say the constant translation problems between the
         | systems are something like an intelligence filter which
         | provides its own rewards when not actively translating.
        
         | nojito wrote:
         | > I have trouble finding enough people, mature enough to be
         | able to even propose working projects in Clojure.
         | 
         | It has nothing to do with maturity and very thing to do with
         | maintenance time and maintenance costs of using languages that
         | do not have a robust pipeline of talent.
         | 
         | Using languages like closure do give you a great amount of job
         | security if you manage to sneak it in though.
        
           | lmilcin wrote:
           | > Using languages like closure do give you a great amount of
           | job security if you manage to sneak it in though.
           | 
           | I am not after job security. In fact, I treat "I am not after
           | job security" as one of the important points when presenting
           | to potential employers.
           | 
           | I try to do good job and think for the outcomes for the
           | employer first.
           | 
           | I value trust and I try to make it clear to my boss that they
           | can trust me to always work with best interest for the
           | project and company.
           | 
           | For example, I always make it clear when I make mistakes and
           | then we try to figure out how to solve them.
           | 
           | I think, overall, this brings much better job security than
           | trying to sneak technology to create a project that only I
           | would be able to maintain.
        
         | cle wrote:
         | Honestly I've seen just as many disastrous codebases written in
         | highly structured, statically typed languages. Any language
         | with intrinsically interesting features tends to attract
         | inexperienced (or just bad) developers who don't appreciate the
         | tradeoffs of their tools and design decisions, and think the
         | tool is a panacea. This leads to disastrous codebases, and is
         | not at all limited to lisps or dynamically typed languages. I
         | can point just as many disasters written in statically typed
         | languages, and they tend to be the "interesting" ones with
         | fancy type systems in the ML family like Scala, Haskell, and
         | Rust. (I am a huge fan of both Lisps and ML-family languages.)
         | 
         | I think the important point is to be very aware of this risk
         | and consider it when making technical decisions. Sometimes the
         | tradeoffs are worth it to use a powerful tool, depending on the
         | team and the product, and sometimes you're better off using
         | boring tools that won't distract smart engineers.
        
           | pkolaczk wrote:
           | You can write Perl in any language. Lack of expressive power
           | in the language can also lead to a disaster. Sure, Java maybe
           | doesn't have a fancy typesystem and macros, but then the same
           | kind of clever developers use runtime reflection and bytecode
           | manipulation which tend to end up with just as big disasters
           | if not bigger.
        
             | didibus wrote:
             | Exactly my experience. Most Java code base end up using all
             | kind of clever tricks like you said, runtime reflection,
             | bytecode manipulation, source code pre-
             | processing/generation, pushing all logic to configuration,
             | abuse of compiler annotations, etc.
             | 
             | These actually create a bigger disaster, because unlike
             | Lisp macros or higher order functions, they have even less
             | structure, and are completely non-standard. I'd rather
             | people use well thought out mechanism for extension and
             | "cleverness" than some poor ad-hoc variant to do the same.
        
           | lmilcin wrote:
           | The question isn't whether it is possible to write shitty
           | code in a given language. Every language can be used to write
           | shitty code.
           | 
           | The issue is, given a person that knows the language does not
           | know how to structure their code, what is likely going to be
           | the outcome with regards to maintainability.
           | 
           | My production experience is mostly with C, C++, Python and
           | Java.
           | 
           | As an example, consider typical Java backend application.
           | Even novice developers would typically learn Spring and
           | create applications that contain controllers, views,
           | repositories, services, etc.
           | 
           | They (I mean novice developers) would be constantly repeating
           | rules they don't know why they are following, but heard it is
           | important or seen it as popular. So maybe things like you
           | should have your objects accept injected dependencies.
           | 
           | It does not mean they will know how to use these or even that
           | they will use these productively to create more readable
           | code, but what this does is creates code that has at least
           | some structure.
           | 
           | The code might have huge amount of duplication and redundant
           | or unnecessary constructs, but you will be able to move
           | around, understand what you are looking at (okay, this is
           | controller so I know how this most likely works, this is
           | database layer so I know what I can expect, etc.)
           | 
           | On the other hand Clojure imposes exactly ZERO structure on
           | your application. That is powerful but only if you know how
           | to structure the application yourself, know what kind of
           | choices you need to make and know ins and outs of various
           | options.
           | 
           | If you have no experience creating structure, have been Java
           | dev all your life and have always relied on structure that
           | was given to you and never thought about it until today, you
           | are likely to produce absolutely unmaintainable mess that
           | nobody is going to be able to figure out.
        
             | didibus wrote:
             | > The issue is, given a person that knows the language does
             | not know how to structure their code, what is likely going
             | to be the outcome with regards to maintainability.
             | 
             | > My production experience is mostly with C, C++, Python
             | and Java
             | 
             | > On the other hand Clojure imposes exactly ZERO structure
             | on your application. That is powerful but only if you know
             | how to structure the application yourself, know what kind
             | of choices you need to make and know ins and outs of
             | various options
             | 
             | All I can say is I have Java, C#, C++, JavaScript
             | professional experience and also Clojure.
             | 
             | From my experience, I've noticed that actually I can
             | navigate better even a messy Clojure codebase, because the
             | language is actually quite simple. Even if someone plasters
             | poorly thought out macros everywhere, the rules of macros
             | and macro-expansion are very simple and clear. I can easily
             | figure out what they do and then start to make sense of the
             | mess. And the REPL allows me to very quickly explore
             | everything that is structured confusingly and hard to read.
             | 
             | This is not true with messy C++, C# or Java from my
             | experience. When Java gets messy, you are now dealing with
             | pre-processors, custom annotations, XML/reflection, magic
             | strings, hidden circular dependencies, and ton of coupling,
             | and those can really get out of hand. There's no simple
             | systematic method to unraveling the mess, like there is in
             | Clojure.
             | 
             | And on the data side of things, this is also true. Clojure
             | data-model being immutable also means it can systematically
             | be unraveled. Again, in Java, C# and C++, you have so many
             | hidden data dependencies that can trip you up, realizing
             | the spread of the shared data across a messy code base is
             | quite difficult, in Clojure, the only challenge is figuring
             | out its flow, but not its sharing.
             | 
             | But, to caveat, all the Clojure services I've worked on
             | were developed while I was lead. So it's possible that
             | having me around managed to prevent getting them in a place
             | that cannot be salvaged. And similarly most Java, C++ or C#
             | projects I've led also never ended up in such a rot for as
             | long as I was around. Where as the Java, C++ and C#
             | projects I've found to be unsalvageable mess I've generally
             | inherited from others or people before me. I haven't yet
             | had to inherit a Clojure project where I wasn't involved
             | with from the start. I do wonder what would happen then. My
             | best experience here is open source, till now, most Clojure
             | open source code base I've explored I've found simple to
             | unravel, but maybe open source has a higher quality bias.
             | 
             | Edit: One last thing that keeps me hopeful is Emacs,
             | probably the oldest most distributed development project
             | ever using a Lisp, and I find it pretty easy to understand
             | its code base and add to it.
        
           | mumblemumble wrote:
           | I 100% agree that disastrous codebases are every bit as
           | likely in other languages. But it somehow hurts more in a
           | lisp.
           | 
           | I think that the syntax really is the culprit. In a language
           | like Java, the Byzantine syntax and semantics enforce some
           | minimum level of structure on the code. It's not much, but
           | it's something, just enough to give an experienced programmer
           | a few extra heuristics they can use to make sense of what
           | they're seeing. Even some things that normally drive me nuts
           | about Java, like the way that there's no standard way to call
           | an anonymous function (because the invocation is done through
           | a method whose name is allowed to vary) end up being useful
           | bread crumbs when I'm reading somebody else's code.
           | 
           | In a Lisp, though, everything looks more-or-less homogeneous.
           | You can memorize the names of special forms, but, beyond
           | that, you can't quickly tell whether some thing you're
           | looking at is data, a function, or a macro, or what. It all
           | blends together into a disorienting fog. You need to go trace
           | its provenance to see how some new value is defined or
           | constructed before you can even tell what kind of thing it
           | is. (This, tangentially, might be a great argument in favor
           | of lisp-2, though I haven't spent enough time in a lisp-2 to
           | have an opinion that's worth beans.) It may, in the grand
           | scheme of things, be only a small reduction in what you can
           | assume without careful study. But, in an unfamiliar codebase
           | where you don't know much to begin with, every little scrap
           | of knowledge counts.
           | 
           | It all points to an insight I should have had 20 years ago
           | when I was working in Perl: Features that make a programming
           | language pleasant to write tend to make it _un_ pleasant to
           | read, and vice versa.
        
             | lispm wrote:
             | It may take a while to see the visual clues: indentation,
             | formatting, structure patterns. Beyond simple lists, Lisp
             | uses a variety of list structure patterns for language
             | constructs. It's a bit like learning to ride a bike:
             | initially it looks not possible to balance, steer and move
             | forward at the same time.
             | 
             | One thing that's not that usual is that authors can
             | implement new language constructs themselves. Thus one may
             | need to understand that meta-syntax level when reading and
             | writing new constructs. A starter book like SICP thus does
             | not use this prominent feature: it does mostly NOT define &
             | use macros.
             | 
             | Authors need to learn how to design new macro operators.
        
               | lmilcin wrote:
               | I think it is important distinguish the necessary from
               | accidental complexity.
               | 
               | Macros are typically more difficult to understand but if
               | done well that would be because they are sinking
               | complexity from a bunch of code.
               | 
               | For example, if a macro implements variations of
               | repeating construct, you are removing those variations
               | from your entire codebase and putting complexity of
               | dealing with that into a single macro.
               | 
               | Now, the issue is when you start creating accidental
               | complexity.
               | 
               | For example, there are various techniques that reduce
               | overall complexity just by being consistent in how you do
               | things. Using only hygienic macros or writing macros
               | consistently in a way that allows the reader predicting
               | what they do reduces a lot of perceived complexity.
               | 
               | If the reader of the code does not have to understand the
               | macro to be able to more or less predict what it does and
               | what are basic guarantees it provides, it reduces a huge
               | amount of complexity when you try to read and understand.
               | 
               | If writing a macro can be compared to writing an operator
               | in a language then all other language design rules apply.
               | It would be a bad language that constantly surprises the
               | user.
               | 
               | Unfortunately, Lisp code tends to be much more abstract
               | with lots of complex, custom operators (macros) as
               | building blocks. If these building blocks are not clear
               | and cannot be relied upon (ie. you don't understand how
               | they work and can't predict what they will do) then this
               | is much more damaging to trying to understand how the
               | codebase works than writing unclear functions.
        
               | lispm wrote:
               | > sinking complexity from a bunch of code
               | 
               | Sometimes macros provide domain-level constructs. This
               | creates very dense code - which can improve code
               | understanding.
               | 
               | One thing I would recommend to put extra effort into
               | macros, especially with these aspects:
               | 
               | One should write documentation what the macro expects and
               | what it does. This way this has not to be inferred from
               | reading the often quite complex code. Document the
               | implemented syntax.
               | 
               | The macro should check the syntax of the forms. Provide
               | error messages for rejected forms.
        
             | lmilcin wrote:
             | I spent a lot of time thinking about this.
             | 
             | What makes Perl or Clojure such fun languages to work with
             | is they don't impose structure on you. You can do whatever
             | the hell you want.
             | 
             | On the other hand result of this is the code represents
             | basically how you think about the problem. Which would be
             | very different for every person.
             | 
             | Languages with frameworks like Java+Spring, Ruby+Rails etc.
             | are less "fun" to work with because you are not given so
             | much freedom and likely have to fill in a lot of stupid,
             | mundane boilerplate, but are easier to maintain in team
             | setting because developers know more or less what to expect
             | from the code. Very likely developers come with that
             | knowledge and if you know one Spring application you know
             | how to move around almost any Spring application.
        
               | [deleted]
        
               | didibus wrote:
               | Yes, frameworks are kind of like a smarter programmer
               | starting you off with a code base already, so if you
               | don't really know what you're doing you can't mess it up
               | as much. I've still seen people mess it up, and generally
               | it's when the developers begin to "play" with things they
               | don't understand, like bringing in Aspect J, writing
               | custom annotations, slowly moving logic to configuration
               | files, starting to dynamically load modules in/out.
               | 
               | It's a bit of a struggle because an experienced dev will
               | hate the framework, as they know better, but then as less
               | experienced dev work on the code base it'll degenerate in
               | a way the framework would have survived longer due to
               | availability of documentation and Stackoverflow Q/A.
        
               | lmilcin wrote:
               | There are two more points.
               | 
               | If you are tech lead or manager, these frameworks are
               | really great value because they "give" (or more like let
               | developer find) guidance on a lot of aspects of the
               | application.
               | 
               | If you created your own perfect framework, you would be
               | responsible for providing a huge amount of that kind of
               | guidance, but if you are using Spring your every
               | developer can just google the answer to most (or even
               | almost all) of the problems.
               | 
               | And the second point is, again, if you are tech lead or
               | manager, these frameworks are great help preventing your
               | "highly intelligent" developers from making a mistake and
               | developing their own framework. I put quotes
               | intentionally, because some developers just think
               | themselves to be very good developers but dismiss the
               | risks and costs of developing software that is not core
               | part of the project (aka NIH).
               | 
               | With Spring regulating most aspects of the application
               | any damage from those kinds of actions is also limited.
        
               | vram22 wrote:
               | It is the frameworks that give more rigidity and
               | boilerplate, not so much those languages themselves
               | (although Java does have more boilerplate than Ruby). You
               | have a lot of freedom when you program in plain Java and
               | Ruby vs. using frameworks in those languages. And that is
               | at least partly by design. See the Template Method design
               | pattern, which is the basis of frameworks that use
               | Inversion of Control (IoC).
        
               | lmilcin wrote:
               | Clojure frameworks are written by Clojure developers
               | which means they don't give you structure, only tools to
               | realize your freedom.
               | 
               | Frameworks that give a lot of structure are beneficial to
               | novice developer because they need that structure. In
               | absence of supervision, the various articles on the
               | Internet, stack exchange answers on how to write a
               | controller or other piece of Java app, give a novice
               | developer necessary guidance on how to structure their
               | code.
               | 
               | On the other hand as a more mature, knowledgeable and
               | experience developer you may see that this structure is
               | not perfect. It is repetitive and it has a lot of
               | boilerplate and it does not suit well every application.
               | 
               | You may even figure out that every application has their
               | own perfect framework, depending on its size and other
               | characteristics and the problem it is trying to solve.
               | 
               | Clojure lets you design that framework and then write the
               | application in perfect framework for your application.
               | 
               | That framework might be some zero-code decisions (like
               | decisions on where to put which part of the code) but can
               | also be some set of macros up to full blown DSL.
               | 
               | Assuming you are mature developer, you will know how to
               | use these to solve your problem but if you don't then
               | that's where the problem starts.
        
             | admax88q wrote:
             | It feels like Lisp scales vertically and Java scales
             | horizontally.
             | 
             | Java scales for number of developers, its idioms and
             | ecosystem make it easier to have tons of developers on a
             | single project.
             | 
             | Lisp scales on the size of problem that a small close knit
             | team can solve.
             | 
             | Things like the macro system in Lisp encourage defining
             | your own problem specific DSL, which is great for
             | individual developer productivity, but bad for involving
             | lots of developers.
        
               | kazinator wrote:
               | Like the way those _defclass_ and _defmethod_ macros
               | totally destroy programming at large.
        
         | jimmyvalmer wrote:
         | > You have to be really incompetent to write a typical Java
         | service in a way where it is no longer possible to develop it
         | at all,
         | 
         | Disagree. Anything can be made write-only.
         | 
         | > but in Clojure it is just enough to get couple of people that
         | are intelligent enough to write macros but not experienced
         | enough to understand the dangers of lack framework forcing the
         | structure of your application.
         | 
         | Sentence doesn't parse on multiple levels.
        
           | vram22 wrote:
           | Yes, same thing noticed here. Incoherent sentences above, in
           | your parent's comment.
        
           | base698 wrote:
           | Agree with him. Coming from an OO background I always cringed
           | at the 15,000 line classes with 2000 line methods. Side
           | effects everywhere.
           | 
           | In my naivety I thought functional programs with their
           | emphasis on lack of side effects could help. I then
           | encountered a project that was totally functional but written
           | entirely by people with no functional experience. The entire
           | application was unmaintainable even in the most basic parts.
           | 
           | > Doesn't parse
           | 
           | > Macros
           | 
           | Macros modify code structure at runtime so obviously that is
           | fraught with danger.
           | 
           | > Framework forcing structure
           | 
           | Lisp languages in general have a do it yourself attitude not
           | found in others. While there are frameworks it's common to
           | find projects that have invented from the ground up entire
           | web frameworks, db access and orm.
           | 
           | Imagine if every project you encountered in Java reinvented
           | Rx Java, Hibernate, and Spring.
        
             | SomeHacker44 wrote:
             | > Macros modify code structure at runtime so obviously that
             | is fraught with danger.
             | 
             | You may have inadvertently misspoke, but macros operate at
             | compile time, at least in the Lisps I have used.
        
               | vram22 wrote:
               | I don't know Lisp well, but I remember PG saying in one
               | of his books or essays, that Lisp is a language in which
               | you can compile and run at read time, and the other two
               | possibilities, too.
        
               | lispm wrote:
               | If one uses a Lisp interpreter, macro expansion may
               | happen at runtime.
               | 
               | CLISP example:                 [2]> (defmacro add2
               | (place) (print 'add2) `(incf ,place 2))        ADD2
               | [3]> (let ((a 1)) (dotimes (i 4) (add2 a)))
               | ADD2        ADD2        ADD2        ADD2        NIL
               | [4]>
               | 
               | As one can see the macro form is expanded four times at
               | runtime.
        
               | dzsekijo wrote:
               | This is a confusing example, because in a REPL steps of
               | compilation and evaluation are interleaved.
               | 
               | Indeed, can you write a program for CLISP that works like
               | this:
               | 
               | - takes one command line argument (a file name)
               | 
               | - reads in the given file, interprets it as Common Lisp
               | code, expecting it to deliver a definition for the add2
               | macro
               | 
               | - then runs                  (let ((a 1)) (dotimes (i 4)
               | (add2 a)))
        
               | lispm wrote:
               | > This is a confusing example, because in a REPL steps of
               | compilation and evaluation are interleaved.
               | 
               | The CLISP REPL does not compile, thus it can't be
               | interleaved.
               | 
               | > then runs
               | 
               | It will still be interpreted and the macro will still be
               | expanded at runtime.
        
               | lmilcin wrote:
               | In full Lisp "compile time" is part of application
               | execution.
               | 
               | Now, Clojure is kind of impaired Lisp because it is
               | written for a VM that was not intended to be used this
               | way and so this is not that much pronounced (but you
               | still get REPL, etc.)
        
             | rpdillon wrote:
             | I'm a big fan of Racket, and while I don't write very many
             | macros, my understanding is that in both Common Lisp and
             | Racket, macros are essentially a compiler pass, with no
             | modification at runtime.
             | 
             | https://docs.racket-lang.org/guide/macros.html
        
       | tines wrote:
       | > The best any static type checking will let you do is
       | "array[float]"
       | 
       | I know this guy is smart and all, and I love Lisp as much as the
       | next guy, but this article really reads like it was written by
       | someone who hasn't used Haskell.
        
       | continuational wrote:
       | Lots of articles like this out there about Lisp. To quote Linus
       | Thorvalds:
       | 
       | > Talk is cheap, show me the code
       | 
       | If you think you can write better code in Lisp, well, show us
       | some examples of that.
        
       | sn41 wrote:
       | The author of the article, Anurag Mendhekar, is one of the
       | authors in the original paper on AspectJ [1], which introduced
       | Aspect-Oriented Programming. The origins of AOP are in Smalltalk
       | and the Meta-Object Protocol, I think.
       | 
       | I really liked Aspect-Oriented Programming, despite its great
       | issues with mutually interfering aspects etc. I wonder if it will
       | make it as a big paradigm, but I hope it does.
       | 
       | [1] https://www.cs.ubc.ca/~gregor/papers/kiczales-
       | ECOOP1997-AOP....
        
         | rottc0dd wrote:
         | I have used aspectj in somewhat unhealthy dose in my project.
         | Mostly for reasons not even intended as an use to aspect. We
         | have to work on giant configs reified as giant java objects.
         | And I have was able to collect enough metadata about the code
         | using aspects and keep them tied to the live objects in such a
         | way that I was able to simulate creation, transformation and
         | move of method calls.
         | 
         | yes, a byte code parser and reflection might have achieved the
         | same, but the library made it breezy. Maybe, java is not a
         | hacker's language but ecosystem comes with hackers toolbox.
        
       | jmkr wrote:
       | > That got me through a bachelor's degree in Computer Science,
       | but it always left me wanting for more expressiveness in my
       | programs.
       | 
       | I share this sentiment, and I think a lot of it also comes from
       | Rich Hickey talking about it.
       | 
       | As I relate to what this article expresses, I've noticed that
       | people who write lisp type stuff talk about programming in a
       | different way. In some sense there is an academic feel to it. In
       | another sense there is a maker feel to it.
       | 
       | I think Dan Friedman might have said something similar about
       | static typing not helping too much, but saw dependent types as
       | something interesting
        
       | smlckz wrote:
       | Currently, lisp is not a single language, but a family of
       | similiar languages that originate from _the_ LISP.
       | 
       | The languages' inherent flexibility gives you too much freedom of
       | expression to handle, especially for those who come with
       | experience of strict languages. The minimalism of syntax and the
       | ability to create in-effect a (sub-)language of your own also
       | adds to that expressivity.
       | 
       | But we need guards to protect us from the freedom of too much
       | expressivity, see for example defmacro vs. syntax-rules/syntax-
       | case etc.
       | 
       | From my perspective, I want a language which gives me freedom
       | when I want that, protection (''guardrails'') when I want that
       | too, both if possible at the same time.
       | 
       | On invariants, what dependent typing and property testing are
       | giving us now? How is the room of improvement on that matter?
        
       ___________________________________________________________________
       (page generated 2021-01-31 23:01 UTC)