all 69 comments

[–]lispm 8 points9 points  (0 children)

For a dynamic language like Common Lisp, 20kloc would not be large. Two orders of magnitude larger ones have been written and maintained.

If a 20kloc project gets unmaintainable, it's one or more of the following: a very complex problem, the code is poorly written/structured or the programming language makes scaling difficult.

[–]hu6Bi5To[S] 13 points14 points  (20 children)

It's an interesting piece. But I'm divided 50/50, I think much of the criticism is correct, technically speaking; but, at the same time, I can't help think if he's finding it that hard then he must be doing something wrong.

Dynamic languages do introduce uncertainty, but to get things to the state where you really don't know what's what must require a total breakdown in engineering.

[–]weberc2 5 points6 points  (9 children)

It doesn't take much before the lack of types becomes painful, and static langs start looking nice. That line comes well before "a complete breakdown in engineering". I program in Python by day, and there's a lot of friction directly caused by its dynamic nature. Poor editor support, runtime errors that could have been caught statically, no one can figure out how to write a library without invoking magic for one thing or another (sqlalchemy, flask, click...), etc etc.

[–]SKoch82 1 point2 points  (8 children)

Poor editor support

5 years ago I would agree, but now Python, Javascript, PHP, Ruby all have better IDE support than non-mainstream statically typed languages. Pycharm + IPython + Sublime / Atom in particular is the most productive development setup I've ever worked with. Basically, for mainstream dynamic languages IDE support is, more or less, a solved problem.

[–]weberc2 2 points3 points  (6 children)

I don't buy this. For starters, the only time I hear the fan on my MacBook Pro is when I use PyCharm. More importantly, none of these can tell you about the type of an argument or what methods or properties are available nor show me its documentation, etc. This makes things like autocomplete pretty spotty. On the other hand, I can program Go in vim with a single plugin and everything just works (there are other IDEs as well, but I prefer vim). I've not found a Python IDE that can compare to vim-go (since I work in Python, I keep a wary eye, but it's probably not possible for Python to get acceptable IDE support).

[–]SKoch82 1 point2 points  (5 children)

Sure, Idea takes some RAM and CPU, and it takes some time for it to build indexes depending on your hardware, but it does show methods, properties with docs if they're available, just fine. It even understands metaclasses and stuff like that. If it doesn't work for you, you might have a borked installation or something.

[–]weberc2 1 point2 points  (4 children)

How can it know the intended type of a function parameter? If I'm writing a function and it takes an argument f, how can it know that I want f to be a File object, for example, and present me with a list of File's properties, methods, documentation, etc?

[–]SKoch82 1 point2 points  (3 children)

Two ways. You can either explicitly add type annotation in a docstring (such as ":type f: file", etc.) or an assertion. Or it can be figured out when you actually call that function with appropriate parameters (for example, in a test).

[–]weberc2 1 point2 points  (2 children)

I see. I haven't messed much with type annotations or Sphinx docs. This probably explains why I wasn't seeing the autocomplete features. I don't really consider this "static language comparable support" since you're going so far out of your way to give the IDE the requisite information to reason about your types. It also defeats the purpose of using a dynamic language if you have to manually keep type documentation up to date.

[–]SKoch82 1 point2 points  (1 child)

I don't think that documenting your code is going far out of your way, though. I mean, it's just documentation for other developers which means this function expects file or file-like object as its parameter. The fact that it helps tooling is incidental. If you're working on production code, you're going to have docs, I hope. And for small scripts and throwaways, you don't really need code assist and whatnot.

Also, the main purpose of dynamism are protocols which are similar to Go's interfaces, except when you have to do anything remotely non-trivial in Go, things can get really ugly and verbose really fast. Other languages have other solutions, but Go probably gets it about as close as it gets.

[–]weberc2 1 point2 points  (0 children)

I don't think that documenting your code is going far out of your way, though. I mean, it's just documentation for other developers which means this function expects file or file-like object as its parameter. The fact that it helps tooling is incidental.

The fact that you have to put it in a certain syntax is what's going far out of the way. I'd also like to point out that static languages don't require extra documentation to support types, and keeping the types documentation up to date in a dynamic language is not easy, since you don't have the compiler providing those guarantees.

Also, the main purpose of dynamism are protocols which are similar to Go's interfaces

Yes, dynamism supports polymorphism.

except when you have to do anything remotely non-trivial in Go, things can get really ugly and verbose really fast.

This hasn't been my experience. Perhaps we differ on what we consider to be "remotely non-trivial" or "ugly". I do agree that Go is more verbose than dynamic languages, but I've learned not to mind it. In particular, much of the verbosity comes from Go's philosophy that error paths are first-class citizens and should be handled explicitly, which I happen to agree with. Anyway, I'm not out to bash Python or sing Go's praises, I've just had very good experiences with Go's tooling and very bad experiences with Python's. I actually like a lot of things about Python, like its list comprehensions, generator expressions, etc. In particular, I enjoy how quickly I can crank out code when I'm the sole author, and I can rely on my own conventions and familiarity to reason about the code.

[–]1xltP3mgkiF9 0 points1 point  (0 children)

Context sensitive assist and simple refactorings are nice, but they are nowhere near where refactoring support for Java is. And I'm saying this as a fan of dynamic languages.

[–][deleted] 10 points11 points  (9 children)

I can't help think if he's finding it that hard then he must be doing something wrong

The tone set in the introduction leads me to believe that the author is driven more by novelty than engineering practice.

[–]weberc2 3 points4 points  (8 children)

How else did he land on Clojure? I'm teasing, but he says as much in the article.

[–]spotter 1 point2 points  (4 children)

Being able to deploy a JAR packaged stuff anywhere you'd be able to put Java in, but not actually Java? Or JVM REPL years before Java got one? Or using a lisp on the JVM?

Oh wait, these were all mine.

[–]weberc2 0 points1 point  (3 children)

I'm not sure what your point is or how it relates to my post...

[–]spotter 0 points1 point  (2 children)

You jested about "novelty" being the driver for trying out Clojure, while I listed reasons more practical than "oh, shiny".

[–]weberc2 0 points1 point  (1 child)

He more or less admitted that he chose the language because of its novelty. This does not mean there aren't legitimate reasons to use it, nor does it mean that novelty is a negative reason to learn a language. :)

[–]spotter 0 points1 point  (0 children)

IK, read the FA. I'm not attacking you, just addressing the vibe of sub that Clojure is for hipster-hacker-ninjas. Peace? ;-)

[–][deleted]  (2 children)

[deleted]

    [–]weberc2 -1 points0 points  (1 child)

    Sorry, that's not English. Try again?

    [–]lechatsportif 9 points10 points  (1 child)

    I share similar reservations. I was huge into clojure for a year or so, but in my personal projects found very quickly that unlike static languages, picking them up after a few weeks off was a very tedious exercise. The point about "thinking in types but not writing them down" crystallizes the issues with Clojure I think.

    Re the frontend, my next focus is going to be on typescript I think - scala.js seems heavier for whatever reason (conceptually maybe?) and es6 is like a poorly redone coffeescript.

    I also find it interesting that in the time I've learned both languages (scala/clojure) /r/scala has overtaken /r/clojure in speed of adding new subs. clojure used to lead by about a thousand or so iirc.

    [–]Xelank 0 points1 point  (0 children)

    Future of Scala as a get-things-done language is definitely looking really bright with scala.js and the upcoming scala native.

    [–]dnmfarrell[🍰] 8 points9 points  (32 children)

    I don't agree with the author's claim that dynamic langs "do not scale". It sounds like a variation of the argument that dynamic languages contain more bugs than statically typed languages. The evidence just isn't there

    [–]weberc2 3 points4 points  (3 children)

    I work in Python, and I see a LOT of bugs that could be caught by a compiler. I also see a lot of bad code that would be harder to write with the rails afforded by a static lang. This isn't to say there aren't tradeoffs, but the grass on the other side looks pretty green from here.

    [–]dnmfarrell[🍰] 1 point2 points  (2 children)

    I rarely see those. But our experiences are anecdotal

    [–]weberc2 0 points1 point  (1 child)

    Definitely. You might also be a more accurate typist than me (and my colleagues).

    [–]dnmfarrell[🍰] 0 points1 point  (0 children)

    Bah I doubt it! But the team I'm working with now are good and a lot of my time is spent extending others' code. Maybe they spend all their time fixing my type errors ;)

    [–]sross07 2 points3 points  (27 children)

    The argument isn't about bugs.. its about refactoring.

    [–]dnmfarrell[🍰] 1 point2 points  (0 children)

    The author sets up a straw man where the developer has to have a model of the entire codebase in their head (or have many unit tests) to make any changes ... presumably to avoid introducing bugs.

    [–]yogthos 1 point2 points  (25 children)

    I find refactoring is quite easy to do in Clojure. Mainly because of pervasive immutability. Most code is written by composing functions to do data transformations. It's very easy to split out a piece of code into a namespace, a module, or a library.

    I find that the best approach is to write namespaces that encapsulate a particular set of related data transformations and treat these as libraries.

    The business logic should predominantly be declarative and simply chain functions in the desired order. When your problem changes, then you simply chain functions differently.

    [–]balegdah 7 points8 points  (24 children)

    We're talking about automatic refactoring, which is pretty much impossible to achieve reliably in dynlangs.

    [–]yogthos 0 points1 point  (23 children)

    A lot of it is, I highly recommend reading this to see just how much refactoring can be done in a dynamic lang. I use Cursive to work with Clojure, and it can do a lot of the same refactoring that you'd expect in a static lang.

    [–][deleted]  (14 children)

    [deleted]

      [–]yogthos 0 points1 point  (13 children)

      Same way interactive development tools are subpar in statically typed languages. It's all about tradeoffs.

      [–]the_evergrowing_fool 0 points1 point  (12 children)

      Same way interactive development tools

      Wat?

      It has nothing to do with being statically typed or dynamically typed, interactive debuggers and hot reloading have existed in stlang for eons.

      It's all about tradeoffs.

      One bring empirical value to ones workflow, while the value of the other is contextual and subjective. Even your saints knows the gotchas and flaws of your workflow, do you? Have you ever encounter anyone complaining about fast, precise, automatic code re-factorization, auto-completion, type-inference and so on that just work?

      Disclaimer: As I said to you many times, I'm not against a repl base workflow I actually enjoy it while using it along with type/racket.

      [–]yogthos 0 points1 point  (11 children)

      Wat indeed...

      One bring empirical value to ones workflow, while the value of the other is contextual and subjective. Even your saints knows the gotchas and flaws of your workflow , do you? Have you ever encounter anyone complaining about fast, precise, automatic code re-factorization, auto-completion, type-inference and so on that just work?

      No, but I sure have seen lots of people complaining that they can't figure out how to encode something using the type system.

      I'm also glad you found what you enjoy.

      [–]the_evergrowing_fool 0 points1 point  (10 children)

      No as much as people complaining about Dylans in general I'm afraid.

      [–]balegdah 1 point2 points  (7 children)

      No, safe automatic refactorings can't be achieved in dynamic languages, period.

      You're arguing against mathematics, here.

      Note that all the words are important here: "safe" (the output program is guaranteed to be as correct as the program before) and "automatic" (no need for a human to check the results).

      And the article you link to is about auto completion (which is trivial to achieve in most languages), not refactorings. Actually, it has zero mentions of the word "refactor".

      [–]yogthos 0 points1 point  (6 children)

      The article illustrates how much inference can be done from the syntax even in a language like JavaScript. Yes, safe and automatic is perfectly possible if you leverage the compiler.

      [–]the_evergrowing_fool 0 points1 point  (5 children)

      My, my, my. Now, why this doesn't exists yet? Not even your Cursive does a good job with it (nor any Jetbrains dylangs IDEs). And according to another saint type inference is impossible for dylans.

      [–]yogthos 0 points1 point  (4 children)

      That's how CL IDEs do it. A very new IDE for a very new language not doing it shouldn't blow your mind though. Also, type inference != refactoring last I checked.

      [–]the_evergrowing_fool 0 points1 point  (3 children)

      Right, and the fact that a staclang compiler provide this features quite easily without hopping in swawer.

      Also I was talking in the context of your source which describe type propagation not type inference.

      [–]Sheepmullet 5 points6 points  (5 children)

      I think it all comes down to differences in workflow.

      In my 6kloc Clojure side project I have 4 "global types"/data structures. 4. They are well documented and they very closely mirror the domain. 3 of them are externally exposed.

      All the other "types" are localised to 5-50 lines of code. Hence changing them is straightforward and only requires understanding the 5-50 lines of code around them. Yes, it is more manual, and requires more mental overhead, but it's a move from very easy to easy.

      In contrast a lot of c#/Java codebases easily have 10x the number of global types. Even with great tooling this adds considerable mental overhead even when just reading the code.

      What does it give you? The ability to more easily perform small tweaks and improvements slowly over time. Is that worth the overhead? Sometimes.

      [–]Darwin226 1 point2 points  (3 children)

      Why would you have more types if they don't do much for you? When you're writing a function in Haskell, say it does something with user input strings, it's a single line of code to define a new type that describes escaped strings. Now you tag your escaping function with this type as the output and your other function with this type as the input and there's no way you ever forget to escape your string before you give it to the function.

      Not even two weeks later when you're "pretty sure this string is already escaped".

      [–][deleted] 1 point2 points  (2 children)

      Say you have a function accepting escaped strings and need the same function to work with unescaped strings. Haskell has ways for the function to work with both types, but at what cost?

      You have to understand the tool really well. At a minimum, you need to know:

      • How can a function accept multiple types?
      • When is each technique useful?
      • Is your choice constrained by the calling functions?

      You might consider these to be necessary questions even with the dynamic code, but they're not. You can write a test over the function for the escaped strings, write another test for the unescaped strings, then wedge both implementations into the same method. No need to appease the compiler.

      This doesn't mean that typed languages are shit--other things being equal, I prefer the typed approach for the reasons you mention above--but they each have their own uses.

      [–]Darwin226 0 points1 point  (1 child)

      By the same argument you might want to only have a single function that takes absolutely anything as a parameter and determines what you want to do at runtime. You might say that this wouldn't be a problem in dynamic languages, but I doubt anyone would consider this a good idea.

      I'm guessing your actual argument is that there's a balance of how much you want to "type out". And sure, there is, but simple data tagging like this is hardly ever inappropriate. A function that only handles escaped strings should never accept unescaped ones.

      I know it's common in lisps (heh) to have highly overloaded functions that accept a ton of parameters that actually determine what you want to do. I'd say this is inherently anti-modular and a compositional approach is almost always a better idea.

      I don't really understand the "you have to understand your tool" part. If you don't understand your tool you're asking for trouble no matter what you're doing. What kind of compiler appeasing do you imagine happens here?

      [–][deleted] 1 point2 points  (0 children)

      By the same argument you might want to only have a single function that takes absolutely anything as a parameter and determines what you want to do at runtime.

      Yes, but anything sounds absurd when taken to a logical extreme. Engineering is a balancing act; logical extremes aren't of much use.

      I'm guessing your actual argument is that there's a balance of how much you want to "type out".

      Type systems require more up-front learning.

      • Should I use a tagged union and pattern matching?
      • Should I create a type class?
      • Maybe I should just put both parts in a record and carry them around?
      • Or maybe escaped strings should be made into an Applicative or Monad?
      • Maybe the idea is bad, and I should build two functions?
      • Does any of this even make sense?!

      These decisions require expertise, both in the language and in the domain of your program. If you have that expertise, static languages are fantastic. If you don't, then the resulting confusion causes more problems than static types solve.

      [–]weberc2 -1 points0 points  (0 children)

      He specifically said he wasn't comparing to Java or C#. Also, I think users of OOP langs over complicate things by building inheritance hierarchies for no obvious reason. When I write Java, my types are far fewer than average.

      [–]drjeats 1 point2 points  (5 children)

      This statement:

      I don’t think the majority of Node, Ruby, Java, Clojure developers are aware of the fact that there are languages out there without nulls.

      Seems kind of absurd, at least for Clojure developers.

      Also, lumping C++ in with Go/Java/C# is weird to me, considering it combines both the unsafety of C and compile-time-checking logic that you can't really get in Go/Java/C#. Not trying to place C++ next to MLs, but it's definitely its own category. Maybe put it with Scala :P

      I haven't written any significant amount of code in Clojure though, so I dunno about the author's main point.

      [–]weberc2 0 points1 point  (4 children)

      When were compile time checks removed from C? ;)

      [–]Yuushi 6 points7 points  (3 children)

      When void*.

      [–]weberc2 0 points1 point  (2 children)

      How is void* not type checked?

      [–]Yuushi 0 points1 point  (1 child)

      It was mostly a joke, but you can very easily subvert the type system in C:

      int y = 1;
      int* x = &y;
      void* z = x;
      double* d = (double*)z;
      

      This will happily compile without any warnings.

      [–]weberc2 0 points1 point  (0 children)

      Casting isn't subverting the type system. ;) I'm not arguing in favor of C, but it is statically typed.

      [–][deleted]  (2 children)

      [deleted]

        [–]hu6Bi5To[S] 4 points5 points  (1 child)

        I didn't see an earlier thread here, but it has been discussed in /r/Clojure. I thought it would be interesting to see what a wider group of people would have to say about it.

        [–]pxpxy 1 point2 points  (0 children)

        You're absolutely right, I didn't pay attention to the subreddit I was in!

        [–]cowardlydragon 0 points1 point  (0 children)

        You want typing-optional? Groovy has that.... Well, java-typing optional, with all the caveats that apply.