Hacker News new | ask | show | jobs
by Crazywater 3309 days ago
They're so right about that. As a frontend dev, I don't care about that extra 1-2% of bugs that, say, ownership types could catch if it kills my productivity by having to annotate everything.

However, if I can figure out what a method actually returns and click through to it in the IDE, that's the real gain from types for me. Type analysis has to be fast to be able to do that.

My impression of research in academia is that it's disproportionately focused on even stronger types that are even harder to compute. Not everyone builds spaceships.

3 comments

   research in academia is that 
   it's disproportionately focused 
   on even stronger types that are 
   even harder to compute. 
Type inference for simple types in sequential computation is a solved problem, Milner and Damas showed how to do it in [1] in 1982. However, most extensions of Damas/Milner cross from decidable type inference to undecidability, so some annotation is often necessary, e.g. Scala, Rust, Haskell (kinding).

Most work on expressive typing systems (e.g. dependent types) has a different aim, namely using types for the verification of arbitrary program properties. This is undecidable, but we want (and need) as much automation as possible to lower the cost of verification, whence a lot of research is about partially automated type inference for very expressive typing systems. For most standard front-end dev, verification is not an issue since

- the field has fairly low correctness requirements that can easily achieved with testing

- typically you don't have strong specifications to verify against. No formal spec, no verification.

[1] L. Damas, R. Milner: Principal type-schemes for functional programs, http://web.cs.wpi.edu/~cs4536/c12/milner-damas_principal_typ...

Just going to point out here that you are conflating Javascript problems with dynamic language problems. Try Clojure, a well designed dynamic language, and you'll find you are a lot more productive with it than any statically typed language.

Indeed over the weekend I was able to design and build a GUI DSL, layout system, model-view databinding, command event system, and serialisation/deserialisation logic for a side project of mine. This sort of stuff would have taken weeks of effort in a static language.

Clojure is a fine language, and I wrote it professionally for a few years.

However, I don't see why this would be true:

> This sort of stuff would have taken weeks of effort in a static language.

I've also written plenty of things in typed languages, and in many ways they can be faster to develop in than dynamic languages. Of course, you do need to change the way you program a bit to really take advantage of types.

I think the main advantage Clojure has over most typed languages is macros, but there is work being done to rectify even that.

I used to think this, until I used Clojure. I'm surprised you used it professionally and have that mindset, I write far less code in Clojure and get far more done. The productivity gains are amazing, especially when I can just upload new code on the fly as the program is running.
Part of the issue is maintenance. Coming back to code you haven't worked on in a while can require more time to load back into your head than a typed language. And speaking from experience [1], refactoring in a typed language also tends to go more smoothly.

> I'm surprised you used it professionally and have that mindset

I used to have your mindset when I first started working with Clojure :)

Keep in mind that not all typed languages are created equal. Clojure is still much better than Java. But I find I'm quite productive with OCaml, due in large part to the simple but powerful type system, plus the fantastic module system. OCaml does have some drawbacks compared to Clojure, but they're not related to static typing. The main disadvantages are the macro system (even the new extension point stuff is cumbersome compared to a proper macro system) and the smaller library ecosystem.

[1] I've done a lot of refactoring in Clojure, and while it wasn't terrible, types can help a lot.

[Edit] To be clear, I do think Clojure has a lot going for it, but I don't think any of it's advantages are due to being dynamically typed, and there's no reason a statically typed language can't have the same benefits.

It's because it's a LISP with good ecosystem. LISP is already strongly, but dynamically, typed from what I'm told. So, you get quite a bit of benefit. You could probably do the same in a statically- or gradually-typed LISP. Main difference is you'll catch a few more interface errors without tests. Also, if using specs or types, one can do automated generation of testing, enabled better static analysis, and/or improve optimization in compilers. Basically, the tools know a bit more about what you're trying to do.
Throwing that back at you: Don't conflate Java problems with static language problems...
Java almost always lost in productivity or correctness studies on programs of significant size unless it was going up against monstrosities like C or C++. So, yeah, it's a holdover from Worse is Better effect and corporate sponsorship rather than illustrative of something great in static 3GL's.
> This sort of stuff would have taken weeks of effort in a static language.

For you, maybe. For someone familiar with that static language? They might have gone faster.

Productivity in a language is highly subjective so it shouldn't come into the equation if you're trying to compare statically and dynamically typed languages.

For the record, I've been a C# developer for 12 years. A Clojure dev for about ~6 months. Also, productivity is not subjective.
It is unless you're measuring the differences with an apples to apples comparison. Examples of how it was done in case studies or empirical research on software development:

1. Features developed by teams with different languages.

2. Features per day per team.

3. Lines of code that took.

4. Defect rate found after all testing and review.

5. Time taken to modify N lines of existing code.

6. Defects introduced during modifications.

7. Overall productivity rate at end of project for each that factors in initial lines of code minus what was reworked to get real lines of correct code produced averaged across each day usually.

Clojure vs Typed Racket vs commercial Smalltalk vs Ocaml or Haskell w/ meta-programming might tell use something objectively. Right now, you have no measurements on typed alternatives similar to your current tool. Your claim that it's more productive is highly subjective albeit I'll give you that prior measurements with small, sample size confirmed strongly-typed, dynamic languages give major productivity boost over statically-typed 3GL's. LISP and Smalltalk winning in that area over predecessors to C#. Your language vs similar ones remains worth measuring, though.

To be fair, C# is not the pinnacle of statically typed languages. Go is quite a lot nicer from a productivity perspective, and but there is also Haskell and OCaml if you want a more expressive type system.
Does Clojure support something that JS doesn't, or is it mostly that JS has warts that introduce errors that slow you down? I would think that Clojure is more or less a subset of JS, and if you're disciplined about using that subset, you should be equally productive in either language.

Also, a frequent problem I have in JS and Python is that they don't scale very well; type documentation must be updated manually and inevitably becomes incorrect with time; determining the type of a return is always a matter of extensive grepping, etc. With most statically typed languages, the IDE can tell you this right away, but in the worst case you just look at the function signature; does Clojure have a solution for this problem?

Thinking of Clojure as a subset of JS is a new one. It's more accurate to say JS is a badly implemented Lisp! These are the main reasons Clojure works so well:

1. It is a Lisp: The only type is the 'list', and in Clojure you also have 'maps' too (basically a dictionary in JS). That's it. Any object in JS is a map in Clojure, which keeps everything nice and simple. The main power of this is you only ever need to support lists or maps in your functions, and then your functions work for every data structure you ever create! For example clojure.core ships with 'get-in' 'assoc-in', 'diff' etc, all functions that work on your structures no matter how complex.

2. It is a functional language: JS is imperative, meaning you have to fit your ideas to the language, breaking everything down into for loops and if statements. With Clojure you fit the language to the idea, and think in data transformations instead. Then you create a set of functions that deal with that problem specifically, all wrapped up in single namespace. For example a 'tilemap' ns that contains a set of functions for converting to/from screen and grid coordinates. Or getting the neighbours of a specified tile from the 2d array. The end result is a custom DSL that reads almost like English.

3. It is 'immutable by default': All data structures are persistent. The problems you have with scaling in JS and Python is all because of mutable local and global state, as it becomes very difficult to know what is changing what. Clojure doesn't have this problem, as values can't be edited, only new ones returned. There is still mutability of course, but it is restricted to maybe 10 locations in your codebase. The mental burden this lifts from your shoulders cannot be understated.

4. It has a REPL: Let me ask how much effort you think building a HTML editor is? Or the UI designer in Visual Studio? With Clojure, I can just make edits to the GUI code on the fly and press F10 and voila the changes appear in-app. Instant editor, for free. This is what i was doing the other day; editing my GUI code (re-arranging controls, changing positions and sizes) and then immediately inspecting the result in the running program. The productivity gains are huge when you remove the laborious 'write code, wait for build, navigate to place in the app, finally test your changes, find error, end program, change code, repeat' cycle from your workflow.

5. Macros: Lisp macros are just Lisp code again. Literally the whole language available to you at compile time. The clojure.async library for example uses this to provide Go style channels for async programming. It means you can write code that looks great, and still compiles down to something performant at the end.

Thanks for the response! +1! Let me take a stab at these points, bearing in mind that I'm not a JS fan or a Clojure opponent; just playing devil's advocate for my own learning purposes:

1. I accept that there are only lists and maps and primitives in Clojure, but I think the same is true of JS. The problem is that functions depend on these map/list structures having a particular schema, so in essence you can't just pass any old list or map to a function and have it do the right thing. I guess I don't see how Clojure and JS differ here.

2. What's stopping you from programming JS in a functional style? It's very possible to avoid `for` in JS (not sure what Clojure uses instead of `if` though). The JS standard library offers many functional features, and there are libraries dedicated to exactly this. I don't see how JS differs from Clojure here, except that it also supports an imperative style and "less is more".

3. If you're disciplined, you can do the same in JS or Python, but I accept that having rails in a language is better than resorting to discipline. The problems I mentioned with scaling JS and Python were related to typing (see point 1), not mutating state. I'm not sure what you mean by mutability introducing scaling problems, but Go is a mutable-by-default language which scales very nicely, both in terms of project size and parallelism (while I accept that immutability makes for easier parallelism, JS and Python's lack of parallelism have nothing to do with mutability except in the runtime). Clojure supports parallelism, which JS does not, so +1 for Clojure here.

4. I hear this a lot from Clojure proponents. The only REPL I've used is Python's, and I'm not a fan, but I understand that the Clojure REPL is a different beast entirely. I'll have to take you at your word here. Clojure beats JS here.

5. JS doesn't actually compile, so you can do all the metaprogramming you like. I also understand that JS has generators and coroutines, which should make async JS suck less; however, I haven't used them yet.

It seems like the biggest advantages Clojure has over JS are:

A) REPL B) Parallelism C) Rails + conventions > discipline

Thoughts?

True Javascript has maps, and you could use immutable.js for your structures. It also has functional capabilities too thesedays. But the difficult part of language design is actually making all these pieces work together fluidly. This is what Clojure achieves.

Immutable.js is great until I need to use a 3rd party lib that doesn't support the structures, so there is a copy involved and performance is lost and the code gets ugly. Functional constructs were botched into JS, so most code out there is a litter of imperative and functional, as well as containing lots of mutable global state. It's object orientated, losing much of the benefits that come from being functional first.

> I'm not sure what you mean by mutability introducing scaling problems

Side-effect free programming makes reasoning about your code easier. More here: https://stackoverflow.com/questions/214714/mutable-vs-immuta...

> The only REPL I've used is Python's

I've not used it, but the point is you can change code and upload it as the programs runs. Great for tweaking stuff. And because there's no global mutable state, you don't have to restart the REPL very often as everything just works.

> JS doesn't actually compile

Actually it is compiled on page load. Also JS has no macros, but it does have some metaprogramming capabilities at runtime (reflection mainly). Again though, having a feature doesn't make it any good. Clojure's consistent and easy API blows JS out of the water.

> Immutable.js is great until I need to use a 3rd party lib that doesn't support the structures, so there is a copy involved and performance is lost and the code gets ugly.

Don't use a library for immutability, just don't mutate. Easy-peasy.

> Functional constructs were botched into JS, so most code out there is a litter of imperative and functional, as well as containing lots of mutable global state. It's object orientated, losing much of the benefits that come from being functional first.

Yeah, multi-paradigm code is a pain.

> Actually it is compiled on page load. Also JS has no macros

If we're being pedantic, it compiles continually during interpretation as most popular implementations use JIT compilation. But my point is that macros wouldn't benefit such a language because there is no discrete compile phase.

> it does have some metaprogramming capabilities at runtime (reflection mainly)

I guess I'm not very familiar with metaprogramming. Most anything I can do with macros in a compiled language I could do at runtime in a dynamic language without special standard library or language support. I'll take you on your word that metaprogramming is useful and that Clojure does it better.

Anyway, I don't think you answered one of my questions--how is Clojure's IDE support? Can it do gotodef or tell you about the types of parameters, etc?

The "documentation for humans" effect is also the only effect that has had at least some empirical validation[1]. Interestingly, it is not necessary to have the type names/annotations actually statically checked.

[1] https://sites.google.com/site/stefanhanenberg/

[EDIT] Clarified which effect

> That's also the only effect that has had at least some validation scientifically

Which effect, sorry? I can't identify the referent of "That".

Sorry, fixed. They also studied "time to completion" and "correctness" for a new task, and for those the dynamic languages did better.
Thanks. After commenting I read the first paper in the page you linked. The one about trying to give an advantage to dynamic languages (Groovy vs Java), and measuring time-to-completion, for 4 tasks.

Turns out a static language has a time advantage when using an API, but a dynamic one has an advantage when writing code (that nobody will reuse).