Violet's Blog

Disciplined Programming

Much debate goes on over static typing of various sorts and dynamic(and a tad less often, untyped) languages. Static typing is ideally best to have the computer check your work before you deploy, eliminating some classes of bugs, dynamic typing is supposed to be easier to write quickly, allowing you to cut out the boilerplate required to develop in statically typed languages and let you test your code while writing parts of it(in some circumstances).

Javascript, Python, Ruby, Lua, Erlang, Prolog, Perl, (many) Lisps, and any number of other language families fall under the category of dynamically typed.

C, C++, D, Haskell, Rust, ML-alikes, C#, Java, Elm, Typescript, FORTRAN, and many other languages of the like fall under being statically typed.

There’s an issue here. C++ and C won’t catch anywhere like the sort of Errors that you might manage to avoid by using Haskell, and it’s easy to argue that Erlang or Python or any number of other dynamic languages can avoid certain bugs you’ll find in C/C++/D.

What’s the real difference then? C and C++ in particular are languages I like to think of as requiring great deals of discipline to write (this is absolutely nothing compared to Forth, a member of an untyped language, so far as I can tell).

Haskell takes knowledge of the proper patterns, which is definitely a related concept in order to write. Much of what it does allows for a great deal of errors to be caught in exchange for what can be some tricky puzzles for those such as myself(a novice with few prospects in the language).

The difference is really between the knowledge of conventions required to avoid misusing a language’s facilities or communicating proper design intention.

I’m going to talk about only a few languages because I’m only fluent enough to speak about a few, C, C++, Haskell, and Python.

C has a lot of different conventions based on what libraries you’re using. C++ has a great deal too(which might be why there seems to be an abundance of code rewriting or avoiding parts of the STL). And as such, if you’re not careful, it is quite easy to assume that a parameter won’t be mutated when it might end up being changed quite a lot.

C++ provides a few tools for indicating how parameters will be treated that refine what C already provided, like const references which prevent any method from being called on it that mutates it(minus parts of the class marked mutable, which is often not mentioned). C++ also guarantees(or tries to) that references differ from pointers in that they’re guaranteed to not be null. Of course, it can’t do lifetime checking like Rust does, so sometimes those references are invalid at the time they’re used.

That’s where a convention comes in, if you need to make sure that a reference still exists where ownership might be shared, you use a shared_ptr, if you need to ensure the object will still exist. If you need to track the lifetime of another shared_ptr or break a reference cycle, you use a weak_ptr, which allows the shared_ptr to be collected if it falls out of frame, but also makes sure that you know about.

You use a unique_ptr if you have an object with single ownership that you are sure won’t pass. If you need a reference to it from an object whose lifetime is dependent on such an owned lifetime, then you use a normal C pointer to refer to it.

I’m going to stop there because this isn’t intended to be a C++ tutorial, and it shouldn’t be because it’s bad at explaining what it is.

So, Haskell does a few things to mitigate this issue. Since it has garbage collection, proper garbage collection that handles cycles properly(I think). It takes work to get a mutable object that allows you to mess up other data’s state, so it’s safe from unintended side effects unless contained within a monad of some sort, and since that tends to be both inconvenient and unergonomic.

Most importantly, the type checking is powerful enough to detect most non-logical(or algorithmic) issues.