News for hackers. Stuff that matters.
Re:Diminishing returns of static typing by danharaj on Monday October 02, 2017 @11:48AM (#15384739)
List typing isn't as superficial as it seems. The following has happened to me multiple times, perhaps in the last month:

I have a large code base. I want to replace a fundamental data structure to support more operations/invariants/performance guarantees. I change the type at the roots of the code base. My instance of ghcid notifies me of the first type error. I fix it. This repeats until the program compiles again. I run the tests. All the tests pass.

This is insane in Python/C/Ruby. I've had to do it in C and Python. In Haskell I do it with impunity.

The type system doesn't just check what my program does, it is the compass, map, and hiking gear that gets me through the wilderness.

Re:Diminishing returns of static typing by DubiousPusher on Monday October 02, 2017 @12:39PM (#15385335)
Yup, love that about statically typed languages. This happens all the time for me in C#. I have several libraries I like that do a lot of code generation. When the project is young, directly handling the generated classes works well but as the project grows, I inevitably want to wrap the handling of the generated classes. It's awesome to be like, welp... it's time to handle this one type differently. Change the return type of an interface or the type of a container and just have the compiler tell you everything you broke.
Re:Diminishing returns of static typing by underwater on Monday October 02, 2017 @07:00PM (#15388708)
I’d argue that code generation is an anti—pattern that is only necessary because of static typing. A dynamic language would let you change the implementation of all generated objects simultaneously.
Re:Diminishing returns of static typing by subwayclub on Monday October 02, 2017 @09:44PM (#15389451)
Code generation is, in essence, the ultimate static construct since it allows you to compile any feature you would achieve through late binding and reflection, but with a static code path. Which in turn lets you leverage the compiler toolchain more and bring more errors towards compile time(where they're cheap to resolve).

Dynamic languages can always lean on runtime features, but that's also their peril. Late binding deprives you of leveraging the tools in favor of "trust me".

In both cases you can get a maintenance nightmare, of course. The point as I see it is to move things toward the runtime when the error case is not troublesome, and towards the compiler when automating in more safeguards would help.

Re:Diminishing returns of static typing by yorwba on Monday October 02, 2017 @07:35PM (#15388858)
Code generation by macros is an important feature of Lisps, which tend to be dynamically typed. Maybe it feels less like code generation when you don't also have to produce the correct type annotations, but a good inference engine can eliminate most of those from statically typed languages as well.
Re:Diminishing returns of static typing by bjz_ on Tuesday October 03, 2017 @06:20AM (#15391366)
You can actually do a huge amount of metaprogramming in dependent type systems and still have the the power of static checking. We're still figuring out how to improve the ergonomics to the level that it matches macros and other code-gen methods, but it's super exciting stuff.
Re:Diminishing returns of static typing by DubiousPusher on Tuesday October 03, 2017 @12:48AM (#15390249)
That may be entirely true and everything you can achieve with Roslyn and T4 templates may be achievable through some more elegant construct in another language.

But I've never had the luxury of choosing a tech stack for it's purity of design. So the feature is wonderful in my day to day regardless.

I'd love to build 3D experiences in a language like lisp or scheme. It'd be great fun to learn but I don't currently have the luxury of the time it would take to ramp.

And I certainly don't have the political capital to convince my entire dev team to change.

Re:Diminishing returns of static typing by dragonwriter on Monday October 02, 2017 @07:03PM (#15388718)
In principle, this should be possible with static typing with a robust enough type system, too.
Re:Diminishing returns of static typing by throwawayjava on Monday October 02, 2017 @10:13PM (#15389593)
A lot of generated code could be done with reflection or meta-object protocols.

There's a good reason people generate code instead (in statically and dynamically typed languages!): performance.

Re:Diminishing returns of static typing by fabioz on Monday October 02, 2017 @08:28PM (#15389095)
I find that people doing these claims have seldomly really worked in large Python codebases...

Personally, I find it pretty workable in Python with a big codebase (but you have to respect the rules like having a good test suite -- you change the time from compiling to running your test suite -- which you should have anyways)...

I find that the current Python codebase I'm working on (which has 15 years and around 15k modules -- started back in Python 2.4, currently in Python 2.7 and going to 3.5) pretty good to work in -- there's a whole class of refactoring that you do on typed languages to satisfy the compiler that you don't even need to care about in the Python world (I also work on a reasonably big Java source codebase and refactorings are needed much more because of that).

I must say I'm usually happier on the Python realm, although, we do use some patterns for types such as the adapter protocol quite a bit (so, you ask for a type and expect to get it regardless of needing a compiler to tell you it's correct and I remember very few cases of errors related to having a wrong type -- I definitely had much more null pointer exceptions on java than typing errors on Python) and we do have some utilities to specify an interface and check that another class implements all the needed methods (so, you can do light type checking on Python without a full static checking language and I don't really miss a compiler for doing that type checking...).

I do think about things such immutability, etc, but feel like the 'we're all responsible grown ups' stance of Python easier to work with... i.e.: if I prefix something with '_' you should not access it, the compiler doesn't have to scream that it's private and I don't need to clutter my code -- you do need good/responsible developers though (I see that as an advantage).

Re:Diminishing returns of static typing by orclev on Monday October 02, 2017 @09:29PM (#15389382)
> I definitely had much more null pointer exceptions on java than typing errors on Python

Null pointers are a type error. The fact that several nominally "statically" typed languages don't differentiate between nullable and non-nullable types is a significant source of failure in their type systems. Using a modern language that properly identifies nullable values as a distinct type from non-nullable ones goes a long way towards eliminating a whole host of problems. It will be interesting to see what things look like in 10 years or so once Rust has had time to really displace a significant portion of the C and C++ code in the wild, and hopefully Kotlin has killed off Java (and if we're really lucky Typescript has done the same more or less with Javascript).

Re:Diminishing returns of static typing by agentultra on Tuesday October 03, 2017 @09:13AM (#15392138)
I'd worked in OpenStack for 4 years and with Python in general for about 10 years.

I can say from my experience it is definitely possible to maintain large codebases in Python. The type errors of the superficial variety that the OP refers to were usually caught before they made it to production (and were rare besides if you were experienced enough to avoid them). It requires discipline to maintain tests and write code in a way that avoids errors.

I've been learning OCaml and Haskell for a couple of years along with formal methods using TLA+ and Lean. I used to think type theory was the accounting of maths. I still think that's at least partly true but the power it brings you as a programmer is quite powerful.

I find working with Haskell or OCaml to be much more productive. Instead of stepping through a debugger or following tracebacks (a descriptive error) I get prescriptive errors as I make changes to a Haskell codebase. The propositions in the type system form a much better specification than unit tests alone.

I still like Python and C for many reasons and will continue using them where appropriate. However I think Haskell/OCaml offer quite enough power that everyone should at least consider what they bring to the table.

Re:Diminishing returns of static typing by jonnytran on Tuesday October 03, 2017 @12:51AM (#15390264)
I do the exact same thing on large codebases of Ruby, but instead of the compiler type-checking errors, it's the test suite errors.

It's true that static type-checking proves the absence of an entire class of errors. But it doesn't prove that the code does the correct thing; it could be well-typed but completely wrong. On the other hand, tests prove that the code does the correct thing in certain cases. ...Of course, it's up to the developers to actually write a good test suite.

The faster we can all accept that there are pros and cons to both, the faster we can come up with a solution that takes advantage of the best of both worlds. That's the whole point of this OP.

I, personally, have always wondered about ways to dial in to the sweet-spot over time as a project matures. At the start of a project, shipping new features faster is often more important. But if the project survives, maintenance (by new developers) and backward compatibility become more and more of a priority.

Re:Diminishing returns of static typing by catnaroek on Tuesday October 03, 2017 @01:16AM (#15390346)
> It's true that static type-checking proves the absence of an entire class of errors. But it doesn't prove that the code does the correct thing;

So prove it yourself. Proving things about programs that rely on dynamism is invariably much harder than proving things about programs that don't.

Re:Diminishing returns of static typing by erfgh on Tuesday October 03, 2017 @05:15AM (#15391157)
> This is insane in Python/C/Ruby.

It's only insane if you don't have test cases with good coverage, in which case you are very-very-very screwed, statically typed or not.

Re:Diminishing returns of static typing by rndmio on Tuesday October 03, 2017 @07:43AM (#15391704)
Testing is not the answer to the problem of knowing if I refactored everywhere necessary. In a statically typed language I don't even have to worry about what do I do if my function is passed a var of the wrong type, it won't compile.
Re:Diminishing returns of static typing by Sir_Cmpwn on Monday October 02, 2017 @03:39PM (#15387221)
I don't really the lumping of C in with Python and Ruby here. The C compiler picks up on that, too. All over this comment section people are calling C weakly typed, I don't get it. Is it because void* exists? Every language has something like that.
Re:Diminishing returns of static typing by mikeash on Monday October 02, 2017 @07:28PM (#15388824)
It's not just that void * exists, but that it's basically mandatory.

C's built-in arrays are super weak, so you need some library to do proper resizable arrays. Since C doesn't have generics, such a library will use void * as the type for putting values into the array and getting them back out again. You'll be casting at every point of use, and nothing will check to make sure you got the cast right, other than running the code and crashing.

Re:Diminishing returns of static typing by flukus on Monday October 02, 2017 @08:34PM (#15389125)
> C's built-in arrays are super weak, so you need some library to do proper resizable arrays. Since C doesn't have generics, such a library will use void * as the type for putting values into the array and getting them back out again. You'll be casting at every point of use, and nothing will check to make sure you got the cast right, other than running the code and crashing.

There are other options though like macros and code generation. Code gen in particular can give you more options than generics without sacrificing any type safety.

Re:Diminishing returns of static typing by comex on Monday October 02, 2017 @10:28PM (#15389671)
I don't know why macro-based containers aren't more popular. You can have a quite usable interface, and even the implementation isn't that ugly. Example:

http://attractivechaos.github.io/klib/#Khash%3A%20generic%20...

This approach isn't just more strongly typed than using void * for everything, it's also typically more efficient. For an array, you can put larger structs directly in the array rather than being forced to use a pointer. For a hash table, the same applies, plus you can avoid expensive indirect calls to compute hashes. (There are alternative non-macro approaches that trade off that overhead for other types of overhead, but you can't do as well as with a specialized container.)

I guess you could ask, at that point why not just use C++? And a lot of people do, and the people left writing new C programs are often traditionalists who don't want to switch to new approaches. And to be fair, there are disadvantages to macro-based containers, like increasing build time. But I still think there's room for them to see more adoption.

Re:Diminishing returns of static typing by mobiletelephone on Tuesday October 03, 2017 @07:55AM (#15391751)
I use a code generator. It has a great type system, it's composable, mature and it can even compile pretty fast with the right tooling. It's called C++!
Re:Diminishing returns of static typing by flukus on Tuesday October 03, 2017 @06:29PM (#15396741)
Templates are good for some things but they only do a fraction of what code generators can do. With code generation you can generate types from database tables, web APIs, etc. You can do things like declaratively declaring database views and generate huge chunks of an application. It can handle all sorts of boiler plate code that you can't do with templates alone.
Re:Diminishing returns of static typing by kerkeslager on Tuesday October 03, 2017 @07:58AM (#15391764)
In this thread, a.lot of people are conflating strong/weak typing with static/dynamic typing.

Static typing versus dynamic typing is fairly binary: if your types are checked at compile time they're probably static, while if they're checked at run time they are probably dynamic. Haskell, C are statically typed, Python, JavaScript are dynamically typed.

Strong/weak typing is more of a spectrum. A strong type system can check many properties of programs and accommodate many patterns as types. A weak type system, on the other hand, can't check many properties of programs, and has to be bypassed to accommodate common patterns. JavaScript has probably the weakest type system, because it checks almost nothing ("hi" + 42 returns "hi42" even though this is nonsensical, {}.foo returns undefined rather than throwing a type error). C is fairly weakly typed because you can add disparate types (int* + int returns int* even if you intended to add two integers) and the type system has to be bypassed with void* to do anything sizeable. Python, ironically, is slightly stronger, in that applying operators to objects of types with no defined relationship throws exceptions ("hi" + 42 errors). A spectrum from weakest to strongest might look something like: JavaScript, C, Go, Ruby, Python, Java, C#, OCaml, Haskell.

My personal experience is that the difference between static and dynamic types isn't very important to my development process or code quality. I have to run and unit test my code to verify it, so the checks happen regardless of whether they happen at compile time or run time. But the difference between strong and weak typing is huge. Strong types catch more bugs, but perhaps more importantly, they catch those bugs where they occur. A type error when adding "hi" + 42 is far more useful for debugging than a mysterious unit test failure on a completely different function where it's returning "Hi42username" instead of "Hi Username" because you added the wrong variable. A segfault 30 lines later is harder to debug than an error when trying to add an int to the value at an int*.

Re:Diminishing returns of static typing by dozzie on Monday October 02, 2017 @08:16PM (#15389045)
> The C compiler picks up on that, too.

Except when it's not. Just five days ago I was debugging an error in my Erlang port driver that was caused by me passing receiver (ErlDrvTerm, an int in disguise) in the place where I wanted number of iterations. The funnier thing was that the declaration of the function had the arguments in correct order (and that's what guided me), but definition had them swapped. The compiler did not catch that bug, because, well, both are ints, so apparently the declaration and definition match, don't they?

Re:Diminishing returns of static typing by comex on Monday October 02, 2017 @11:20PM (#15389863)
That’s why people should use one-element structs instead of typedefs for that kind of use case. A struct is a distinct type that can’t be accidentally mixed up with random integers, but its memory representation and efficiency will generally be identical; and you can add one-liner conversion functions to minimize syntactic overhead when you do need to convert to/from raw integers. Same idea as ‘newtype’ in Haskell; it works pretty much just as well in C, at least for ‘ID number’ sorts of types where you’re usually just shuttling values from place to place rather than doing any arithmetic. (For types where you do need arithmetic, it gets pretty ugly in languages without operator overloading. Except Go, which doesn’t have operator overloading but does have builtin support for defining distinct versions of integer types.)
Re:Diminishing returns of static typing by yawaramin on Tuesday October 03, 2017 @12:36AM (#15390189)
In fact, make the structs opaque so that you retain full control over the data and its invariants.
Re:Diminishing returns of static typing by muricula on Monday October 02, 2017 @03:53PM (#15387338)
void * is part of it, but you can also implicitly cast from between integer types, and also between integers and enums. Think of passing an enum or an int into a function which takes a long as an argument.
Re:Diminishing returns of static typing by Sir_Cmpwn on Monday October 02, 2017 @04:00PM (#15387401)
I mean... I don't really think that's a strong case for calling C's type system weak.
Re:Diminishing returns of static typing by bitwalker on Monday October 02, 2017 @05:52PM (#15388277)
I suspect most people which state that C has a weak type system are really talking about the fact that C has a weakly _enforced_ type system. You can break the type system's rules rather easily (or perhaps it's more accurate to say, the type system is too permissive), either way it doesn't provide you the same guarantees a stronger type system provides). At least that's my take on it.
Re:Diminishing returns of static typing by fulafel on Monday October 02, 2017 @06:51PM (#15388657)
This.
Re:Diminishing returns of static typing by alloyed on Monday October 02, 2017 @06:47PM (#15388643)
I would say it's mostly a matter of use: In C you deal with void* or typecasts all the time, whereas in higher level languages it's much less common, either because the type system is smarter, or the constraints that it does have are more strictly enforced. For example: you can happily compare a char* and an int in C, but other languages like python might error at the thought.
Re:Diminishing returns of static typing by orclev on Monday October 02, 2017 @09:50PM (#15389484)
C is incredibly permissive with regard to its types which are themselves very anemic. With the exception of the numeric primitives, C really only has a single type, the pointer, everything else is just syntactic sugar for various forms of pointer arithmetic. For instance arrays in C are just a shortcut for some pointer plus an offset multiplied by a constant determined at compile time based on what you've claimed is the underlying struct or primitive of the array. Importantly C is perfectly happy to take any random pointer into arbitrary memory and allow you to map any set of offsets into it. It's worth looking at for instance Rust that at least in theory allows the same thing to be done, but only by explicitly opting out of static checks via unsafe declarations. In normal safe code Rust will statically verify that a given reference (pointer more or less) is in fact referring to the type you're code is expecting it to, rather than the C approach of simply assuming the program is correct. Looked at another way, as far as the C compiler is concerned nearly everything is a pointer, and one kind of pointer is entirely exchangeable with another kind of pointer (with at most a cast being required, but probably not even that if it's the entirely too common case of being a void pointer). This is in contrast to nearly every other statically typed language that will either at compile or runtime verify that any given reference is the appropriate type before dereferencing it. C++ nominally at least has a more powerful type system, but since it was designed (in theory at least) as a superset of C, C's permissivity blows a giant gaping hole in its type system.
Re:Diminishing returns of static typing by Sir_Cmpwn on Monday October 02, 2017 @06:51PM (#15388660)

    /t/tmp.1q8r9dZAtX > cat test.c
    int main() {
    	char *test = "test";
    	int i = 10;
    	return test == i;
    }
    /t/tmp.1q8r9dZAtX > cc test.c
    test.c: In function ‘main’:
    test.c:4:14: warning: comparison between pointer and integer
      return test == i;
                  ^~
Re:Diminishing returns of static typing by hoytech on Monday October 02, 2017 @09:57PM (#15389527)

    $ python -c '"test" == 10'
    $