Yeah, there were several such stories during what I think of as Lisp's angsty mid-life crisis during and after the AI winter :) A couple more good ones:
"Programs written by individual hackers tend to follow the scratch-an-itch model. These programs will solve the problem that the hacker, himself, is having without necessarily handling related parts of the problem which would make the program more useful to others. Furthermore, the program is sure to work on that lone hacker's own setup, but may not be portable to other Scheme implementations or to the same Scheme implementation on other platforms. Documentation may be lacking. Being essentially a project done in the hacker's copious free time, the program is liable to suffer should real-life responsibilities intrude on the hacker. As Olin Shivers noted, this means that these one-man-band projects tend to solve eighty-percent of the problem."
Yes, you're right. I'm not very familiar with Haskell, but I was thinking about it when I called the title "An alternative.."
I think of Haskell as being to tests what up-front design is to Agile. It's great if you're trying to carve out right and wrong behaviors along the dimensions it's been designed to help with (and my vague sense is that linear types and other such advances are trying to expand that set of dimensions). But how would you ensure that your program meets its performance guarantees? Or is guaranteed not to block between these two memory barriers? Tests are less rigorous and more work, but if you're willing to put in the elbow grease they'll work for anything you throw at them.
That sounds about right. Static types can only guarantee a certain subset of behavior. Unit tests work for a huge amount of behavior, because they run at run-time. They are very versatile. But there are still some things that unit tests cannot test.
In particular, unit tests cannot guarantee the absence of behavior. As an example, Haskell can use its static type system to say, "this function (and any functions it uses) is guaranteed to be pure". Or you can say, "this function (and any functions it uses) is guaranteed to only use certain side effects, but other side effects do not happen".
Languages like Idris go even further and can guarantee that a function terminates (never goes into an infinite loop)!
As another example, Haskell has Software Transactional Memory, and it uses the static type system to guarantee that arbitrary I/O cannot occur, which is important because the transaction might be retried multiple times.
I don't remember which language it was in, but I remember reading about a language that used static types to guarantee that exceptions are always handled. In other words, you will never have an uncaught exception.
Nulan uses its static type system to make a distinction between blocking impure functions and non-blocking impure functions. In JavaScript, there's a big difference between blocking and non-blocking, both in terms of behavior and performance. By using the static type system, it's possible to say "this function (and any functions it uses) is always blocking, and never non-blocking".
As you are aware, Rust uses its static type system to guarantee that pointers are always used correctly, and that data races cannot occur, even with concurrency.
But the biggest reason I like static types is because they make refactoring easier. Let's say you have a type that represents a person. So you have a name property, age property, etc.
Now let's say you want to add in a new property. Some of the existing functions will need to change to accommodate this new property. Without static types, you need to carefully go through your entire code base and find the functions which use the type, and then change their behavior to work with the new property.
Unit tests won't help you with that, because the unit tests are all testing the old properties, not the new property. So in the end, you have no choice but to just bite the bullet and spend a significant amount of time searching through the code and making the changes.
But with static types, it's as simple as adding the new property, compiling, fixing any type errors, compiling, fixing any type errors, etc.
Each type error tells you the file and line where the error occurs, making it easy to fix. You don't need to search through your code to find the places which need fixing: the type system does that for you. And after all the type errors are fixed, it's basically guaranteed that your code is correct.
Another example: a function used to always return a result, but now you need to change it so that it can potentially fail. So anybody that uses that function now has to account for the possibility of failure. With a static type system, you simply make the change and fix the type errors.
With unit tests, you better hope that you have 100% code coverage, because you're going to need it to find all of the functions which need to be changed. And don't forget that the unit tests need to be updated, and you probably need to add in new tests as well. That takes time, in addition to the time spent fixing the code.
So the end result is that it's faster and easier to refactor with a static type system, and it's more likely to be correct as well.
So I think static types and unit tests complement each other: they can both do things that the other cannot. Static types can make guarantees that unit tests cannot, and static types also remove some of the burden from unit tests, which speeds up development without sacrificing correctness.
On the other hand, unit tests can make certain guarantees that static types cannot. So in the end, I think you need both.
P.S. Static typing also helps tremendously with things like smart IDEs. I think Unison is a really cool example of that:
Typeclasses are amazing. They're similar to multimethods, but more powerful, and they have the same performance as regular functions.
You can use them to solve the expression problem, giving you a huge amount of flexibility, power, and conciseness. Or you can use them to create dynamically scoped variables which actually work correctly (unlike dynamic variables in most languages).
The thing is, typeclasses are only possible with static types. They're the main reason I changed Nulan to be statically typed.
----
By the way, I would like to point out that the static type systems in languages like Haskell is very different from the static type systems in other languages like C, C++, or Java. So even if you like/dislike one of them, you might end up liking the other. I used to lump all of the static type systems together, but I think that's a mistake.
There are bugs which are not caught by C, C++, or Java, but which are caught by Haskell. And there are programs which do not type check in C, C++, or Java, but which do type check in Haskell.
Yes, I wanted to say something similar in response to your phrasing that "type systems (including dynamic and static type systems) are another way to carve out the territory". When you say "dynamic type systems", in particular, I think Python and Ruby, and the modularity guarantees there seem much inferior to tests. But Haskell and the more advanced type systems might well disprove my thesis.
You're right: dynamic type systems tend to provide very weak guarantees. But I'll still take those guarantees over silent failures.
In my opinion, static guarantees (including static types) are the best, followed by unit tests, followed by dynamic guarantees (including dynamic types).
Yes, that's exactly the trade-off I've been thinking of. When I program I like to start out writing tests at the start when I don't yet know what properties I should try to enforce. Over time I start finding places where tests aren't good enough, and start feeling the need for more rigorous checking. But by then I've painted myself into a corner with my choice of language, and it's too hard to switch to a more rigorous platform.
In Mu my plan to break this dichotomy between coverage and soundness is to allow arbitrary metadata to be attached to instruction operands. As a result you aren't stuck with the type annotations and checking that I build into the system. You can have arbitrary metadata and will be able to write programs that can deduce and check this metadata to enforce all sorts of properties. Arbitrary checkers feels to me like a generalization of Lisp macros.
Arbitrary metadata is cool. Clojure has arbitrary metadata, which the compiler uses to optimize function performance, but you can use it for other purposes as well:
I mostly agree with this about how lisp advocacy can be misleading. My one quibble is with his deemphasis of macros. I think they're only #8 on his list because he's been using Racket. A more fluent system for macros makes them far more broadly applicable.
I absolutely agree that "everything is an expression" deserves to be #1. Rust is the first non-functional language to steal this idea: http://lucumr.pocoo.org/2012/10/18/such-a-little-thing. That probably explains Rust's relative success with macros as well. My big insight doing Wart was to realize that "homoiconicity" was really nothing more than "everything is an expression". There's nothing special about lists in particular besides that.
I think the "enlightenment" happened when the only other language people had programmed in was at the level of Fortran or C. Here's another quote in response:
"There have been two really clean, consistent models of programming so far: the C model and the Lisp model. These two seem points of high ground, with swampy lowlands between them. As computers have grown more powerful, the new languages being developed have been moving steadily toward the Lisp model. A popular recipe for new programming languages in the past 20 years has been to take the C model of computing and add to it, piecemeal, parts taken from the Lisp model, like runtime typing and garbage collection." (http://www.paulgraham.com/rootsoflisp.html)
At this point it's only macros that separate Lisp from other languages (though languages like Rust have been trying to steal that as well). So don't go looking for enlightenment. Everybody's trajectory is different.
:) i just gor back here. now trying your steps above Sir. Thank you. As far as i'm concerned i'm also sharing my resources to everyone, Arc deserves a major attention now.
Thanks for asking, GenArx. We've never had good support for Windows since the people here don't use it much, but your heartfelt questions got me to dust off an old Windows machine and try to install Arc on it. I'm going through this process now, and will try to write up better instructions once I succeed. So far I've successfully installed Racket (click on the 'download' link at http://racket-lang.org) and Git on Windows (run the installer at https://git-scm.com/download/win). Can you do those steps while I figure out what to do next?
Let's keep chatting offline. There might be issues I run into that you know how to deal with, or vice versa. Can you send me an email? My address is on my profile.
Edit 16 minutes later: I've gotten Arc running on Windows. Here are the instructions I followed:
0. Install Racket and Git like above.
1. Open Git Bash from the Windows menu and run this command:
git clone https://github.com/arclanguage/anarki
2. Open Racket from the Windows menu and run these commands one by one:
Now you should be at the Arc prompt and ready to try out some programs.
I'm sure there are other issues once I start running programs, but at least the core loaded without errors and I can now help fix other issues as we run into them.
[issue] "I've reformatted my Windows and tried your installations instructions above. When I first followed this instructions it worked out just fin [maybe because of the prior mzscheme installations I've made my own]. This time around I'm following your instructions without first installing my first instruction method, and here's the error:
The error has to do with looking up the variable "Rtl". The "R" probably comes from a bug in Racket's reader where certain things pasted into a Windows terminal window are parsed incorrectly. Where you typed "(tl)", somehow it saw "Rtl)".
In my experience, the bug doesn't occur as long as the text I'm pasting has a newline at the end. Maybe you could try that. That is, instead of just selecting the text you want to copy, select a blank line after it too.
Alternatively, you could type everything by hand instead of pasting... but that sounds pretty painful. Hopefully you don't have to resort to that.
EDIT: I tried to reproduce that pasting issue with Racket 6.4 on Windows 10, and I don't get it anymore. You're using Racket 6.4 too, so are you using a particular version of Windows?
I would want to follow my mzscheme instructions to install arc, but I'll let it pass for now because i want us to solved this Issue.. If I do that your instructions might work but not knowing what's exactly going on without mzscheme, (i think so) :)
Hi highCs, this is off-topic but I was thinking about you just yesterday. I updated your bugfix at http://arclanguage.com/item?id=19310. Could you pull anarki and let me know if it's still working for you on Windows? Many thanks.
We were also wondering what your setup looks like. Are you using cygwin when you run Arc on Windows?
Hi akkartik, thanks for the update. I'm not using cygwin, but raw racket for windows, sublime and terminals. But I've not run Arc since a while - december or so. Actually, I growing more and more interested by poker. It's kind of the perfect answer to the frustration I got in software - mostly due to my struggle to find a job with great programmers and also my struggle to find great co-hackers. In poker, I win money when the others are wrong; guess how I like that this days...
Programming is my life. I like to think I'm a master of it. But I'm completely alone. Also I hate my jobs, I hate big companies, I hate bullshit, I hate TDD, I hate code reviews, I hate estimations; it's a pain man.
Poker, while definitely not as deep as software, is the exact opposite. They do TDD? I take money. They do code reviews? I take money. bullshit? a fountain of money...
But there is Arc. The most beautiful language I've ever used. I could build so much with it. Wow it could be so awesome. But, let's be honest, it's now or never.
Btw, I think I have an idea to fix email. What's missing is a delivery date (of the task / answer / content) set by the sender or the receiver, which would allow to sort emails. Anyway, I'm throwing a bottle in the ocean. If you have a project you want to do using arc (or another language that would fit the task better), let me know.
I was quite addicted to poker (no-limit hold 'em) for a couple of years (2005-2008) until it became hard to play online in the US. Now I just play with play money on some mobile app every few months :) It's not true that when people play badly they always lose money. It took me a while to realize that my compass of how well I was playing had to come from within. Otherwise the worst thing that could happen to me was to play badly and win a hand. I'd then be giving away money for a long time, going on tilt, etc. But yeah you're right that in the long run the better player wins, particularly in cash games. Tournaments seem like more of a lottery (maybe I'm just not very good).
I'm constantly looking for people to hack with as well. Some ideas:
a) We could work on anarki. For example, try out the latest version sometime when you have time and let me know if mktemp broke again on Windows. Help kinnard and me expand the install instructions for Anarki to Windows. Neither of us knows how to run Arc there, so this would be very valuable. These small-ish ideas might be the start of a larger project.