Live data from Hacker News

Why static languages suffer from complexity

hirrolot.github.io

261–270 of 306 posts

Re: Why static languages suffer from complexity

#261

Earlier quoted context omitted.

Oh, I'll never program in a dynamically typed language again. I'm sold on that. What I'm speaking to is the notion that types are the best model for solving most/all problems in software engineering.

Do people commonly think types are a solution to most or all problems? Other than correctness I am not sure what software engineering problems a type system actually solves, and the rest of the debate is about the expressiveness of the type system (or lack thereof, which forces suboptimal engineering practices in some languages).

Static typing is just another form of static analysis. However, it's static analysis enforced by the language rather than a third party tool. That allows me to be confident in my dependencies too if I see them putting the type system to work.

Protobuf is moving us that way with microservices too. Since they're a strongly typed message format, it's harder to make mistakes in the interface between two services.

I also like that languages can have complete local static analysis. Sure, the business requirements might be large and spread across many areas, but I will break them down into smaller chunks and encode invariants into the type system so that if the small chunk compiles, I am confident it does exactly what I expect, and I don't need to remember exactly where it fits in the larger picture

Re: Why static languages suffer from complexity

#262

Having used Clojure for a while now, I will say having 90% of things be a primitive, map, or vector goes a long way in and of itself. A lot of types concocted in a more conventional language just don't need to exist, IMO, and they create so much baggage around themselves.

I have the exact opposite experience. I can't think of anything I got more sick of than every freaking method in every rails project having `params = {}` where you have no idea what keys are required or expected or ignored. Easily 90% of these should have been named structures instead of these arbitrary data grab bags.

Agree that "map oriented" code bases are pretty bad. Always an unmaintainable mess, usually developed by single dev, painful to refactor. Seen this with Groovy back when some people thought this language had any merit.

Re: Why static languages suffer from complexity

#263

This article incorrectly states that Zig has "colored" `async` functions. In reality, [Zig async functions do not suffer from function coloring]( https://kristoff.it/blog/zig-colorblind-async-await/ ). > Yes, you can write virtually any software in Zig, but should you? My experience in maintaining high-level code in Rust and C99 says NO. Maybe gain some experience with Zig in order to draw this conclusion about Zig?

> incorrectly states that Zig has "colored" async functions

This was indeed weird to read, given that only Zig (and soon the JVM) solves this problem, and is well known for the fact. Especially when language design and type theory are an area of interest.

But hey, silver lining: Zig still kind of came out on top.

Re: Why static languages suffer from complexity

#264

Earlier quoted context omitted.

> The more rich the type system the more you can express Why is this interesting? You pay an extremely heavy price in terms of language complexity. In practise, you almost never have the invarants at all or correct when you begin programming, and your programs evolve very rapidly. Since with dependent types you loose type-inference, you now what to evolve two programs rather than one. Moreover proofs are non-composit…

> Why is this interesting? You pay an extremely heavy price in terms of language complexity. As you say, there is no free lunch. Not having a useful type system introduces its own complexity. It depends on what abstractions you find most useful. The present limitations of dependently typed languages will not be limitations tomorrow. Evolution in the field of proof engineering is providing new frameworks for making pr…

> Not having a useful type system introduces its own complexity.

I agree, I am not promoting dynamically typed languages. My intuition about this is more that there is a sweet-spot between automation and expressivity that gives you the best software engineering experience in most practical programming tasks. Milner's let-polymorphism is closer to that sweet-spot than full-on dependent types a la Calculus-of-Constructions

> extract programs from the proofs.

In practise you don't have specifications in > 95% of programming tasks. What, for example, is the full specification of a climate simulation, or of TikTok? One could argue that the shipped product is the (first and only) specification. Every program I've ever been part of constructing started from an informal, intuitive, vague idea what the software should do. This includes safety-critical software. To quote from a famous paper [1]: "We have observed that the errors we find are divided roughly evenly between errors in the test data generators, errors in the specification, and errors in the program."

> If you can't think of the right theorems or specifications I doubt you will write a correct program.

I strongly disagree. I have written many programs that are, as far as I can see, correct, but I am not sure I fully know why. Here is an example: the Euclidean algorithm for computing the GCD. Why does it terminate? I worked out my informal understand why at some point, but that was a lot later than my implementation.

More importantly: in practise, you do not have a specification to start off with. The specification emerges during programming!

> how does a sound type theory end up this

I'm not sure what you are referring to. Type inference and type checking can be undecidable. For example System F, a la Curry.

> extract a full program from a specification

Let me quip: Proof are programs! In other words: if you want to be able to extract a full program from proofs, the specification / proof must already contain all the information the eventual program should have. So any bug that you can make in programming, you also must be able to make in proving. Different syntax and abstraction levels clearly corresponds to to different programmer error probabilities. But fundamentally you cannot get away from the fact that specifications can and often do contain bugs.

[1] K. Claessen, J. Hughes, QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs.

Re: Why static languages suffer from complexity

#265

Earlier quoted context omitted.

> The more rich the type system the more you can express Why is this interesting? You pay an extremely heavy price in terms of language complexity. In practise, you almost never have the invarants at all or correct when you begin programming, and your programs evolve very rapidly. Since with dependent types you loose type-inference, you now what to evolve two programs rather than one. Moreover proofs are non-composit…

>your programs evolve very rapidly. Since with dependent types you loose type-inference, you now what to evolve two programs rather than one. Yes, just like you have to evolve your specification/documentation. Similarly, in the exploratory phase you'll stick to very 'rough' typing and next to no proofs and as the program gets clearer and solidifies, you can continuously refine your types (with the amount of refinemen…

> just like you have to evolve your specification/documentation.

That is correct, and also one of the core reasons why in the vast majority of cases either no specification/documentation exists, or will only cover a small case of the actual specification. For example I would bet money that not a single function in the C, C++, Java and Python standard libraries is fully specified, in the sense of nailing down the program up to observational equivalence. (Aside: I can still count the programmers who would be able to sketch the observational equivalence using in e.g. C++.)

> If your change cascades through the entire program, then it's because all the invariants get invalidated and your 'tiny' change actually changes a good deal about the semantics (and thus the spec) of the program.

This is not borne out in practise.

A lot of code refactoring I've done was trivial (e.g. changing the order or arguments), but ripples through the program and proof structure. HoTT was invented in order to automate some of those trivialities. Java exception specifications are an example: you call a different library function somewhere and need to change all exceptions specs up to the main function, rippling through millions of LoCs. That's why exception specs were abandoned. Another example are termination proofs (note that a full specification must involve termination proofs, which is why the existing expressive type theories don't give you unrestricted recursion, and also the reason why program logics are typically only for partial correctness). In my experience, termination bugs are rare, and it would be insanely counterproductive if I had to refactor all proofs globally just because I've made a slight change to some printf somewhere in a large code base.

> unit tests, but you don't see people constantly bemoan

The reason is that no programming language forces you to do unit tests. In contrast, expressive type-theories constantly force you to prove a lot of trivialities.

> declarative, they're generally magnitudes smaller.

I don't know what you mean by declarative (other than: leaving out some detail). But they cannot be smaller in general: if every program P had a full specification S that was shorter (here full specification means specifying P up to chosen notion of observational equivalence) then you've an impossibly strong compressor which you can use to prove that every string can be compressed even more. Contradiction.

What you see in practise is that you only specify some properties of the program you are working on. For example sorting routines in C, C++, Java etc. I have never seen a specification that says what happens when the sorting predicate is nicely behaved (e.g. returns a b on second). It's fine to omit details, but that limits the correctness you get from your spec (and also limits program extraction). Moreover, if you only work with a partial specification, you can ask the question: what level of partiality in my specification gives me the best software engineering results. My personal and anecdotal experience (which includes dependently typed languages) has consistently been that the full automation given by let-polymorphism is the sweet spot for non-trivial programs (lets say > 10k LoC).

Re: Why static languages suffer from complexity

#266
post #192

Earlier quoted context omitted.

What you appear to be saying is that people who like type systems must be ignorant because with experience you suspect they would think differently. This seems to me to be extremely uncharitable point of view. But let's roll with it. I advocate type systems. I've also worked in several non-trivial projects in lua. Several non-trivial projects in python. Several trivial projects in common lisp. Several trivial project…

Common Lisp has a type system, if I understood your meaning right.

"Type system" is being used here to mean "static type checking" while CLisp has some facilities for this they are implementation-specified and not commonly used. (Even its dynamic typing checking is not commonly used.)

Re: Why static languages suffer from complexity

#267

Earlier quoted context omitted.

Rust's borrow checker isn't a type system. It's a static analyzer that tries to determine if it can figure out when to free your allocation.

Why do you think that’s not a type system? Literally all type systems could be described as “a static analyzer” that tries to assign and validate properties over the code it’s analyzing. All compilers also rely on the results of that static analysis to direct codegen. Rust’s type system implements substructural typing, and the borrow checker is an integral element of that type system.

Based on your definition, then any static analyzer is a type system, because type information and the usage of those types is basically all that’s available for static analysis.

Types define what operations can be performed. The borrow checker looks at allocations and determines when they can be freed. It really has nothing to do with types. An ideal implementation of the borrow checker could be totally type unaware and work with dynamically typed languages.

Re: Why static languages suffer from complexity

#268
post #192

Earlier quoted context omitted.

What you appear to be saying is that people who like type systems must be ignorant because with experience you suspect they would think differently. This seems to me to be extremely uncharitable point of view. But let's roll with it. I advocate type systems. I've also worked in several non-trivial projects in lua. Several non-trivial projects in python. Several trivial projects in common lisp. Several trivial project…

Common Lisp has a type system, if I understood your meaning right.

Common lisp is dynamically typed.

There are type hints that you can give the compiler, but I believe that's only useful for generating faster assembly. Also you can get compile time warnings from macros, but that's much closer in nature to getting a parse error (something pretty much every language does that I'm aware of static or dynamically typed).

I wouldn't be surprised to learn that there exists a common lisp typing extension that someone made with macros (after all racket has something like that iirc), however if it exists I didn't use it regardless.

Re: Why static languages suffer from complexity

#269
post #215
post #114

Earlier quoted context omitted.

What if the JSON represents a list, or an int? Also, how do you then access nested objects, like data['key'][0]['attr'] in Python?

> What if the JSON represents a list, or an int? Then you write one short operator (and I agree that some static languages make this more cumbersome than it should be) to say so, and either handle the case where it isn't, or explicitly declare yourself partial and not handling it. > Also, how do you then access nested objects, like data['key'][0]['attr'] in Python? With lenses, something like: data ^? (key "key") >>>…

Sure there are solutions.

But my main point is that HideousKojima's "statically-typed" solution would result in a runtime type error if it was given unexpected input, just like a dynamically typed solution.

Re: Why static languages suffer from complexity

#270
post #53

Earlier quoted context omitted.

Yup, I find this completely insane behavior to think that you somehow benefit from types not being there. You just make it way harder for people to understand your code and contribute to it.

The example is entirely ridiculous. Types are names too. Does foo(bar: baz) solve the issue? Languages are there to convey meaning.

At least I can navigate to the definition.
Post reply on HN