Experience is just data. So one day when you go to Everest hiring a sherpa might not be needed. Why? Because a simulator can be created, based on his knowledge and all the other knowledge about Everest.
Regarding your other questions:
Humans will build the machines that build the machines. In fact they do it now. Then we will build machines that can build machines based only on a design. That's the future.
The thing with the machines is a little bit different, but essentially it is much better to work from an office 2-4 hours a day, controlling a mining machine compared to actually mining.
Once a person can read there is almost no need for anyone else to teach him anything. I never listened in class, didn't have a notebook and got A's when I wanted to. I was reading the books that we were supposed to, but I prefered to explore them my way and I usually read them before it was needed. I know other people like me. I also know that people are curious in general and like to explore if given the opportunity.
And here comes my last answer - these people will do that because they want to see the result. Because they will enjoy doing it. And when they no longer find it interesting they will move on to something else. Because not doing anything is depressing. Doing it against your will - even more.
For me it is anything that helps me not fix on the problem. Standing in front of the computer, staring at the code always pushes me in the same direction; once I am up to something else, but still 'lightly' thinking about the problem I come up with better solutions. It's almost as if I am thinking about it I can't think of anything valuable; once I stop thinking about the issue at hand everything just happens to fall in place.
Parenthesis in Lisp are not more than those in C. It's just that there is only one type of parenthesis and the accepted convention is to pile all the closing ones at the last line. When you have some time play with them in both C and Lisp and count them.
With the .NET Framework actually declining, Swift still being far away from the server and frameworks like Meteor, JS seems to have a really good position.
With that said I will root for Clojure + ClojureScript. One could theoretically build a framework much more advanced than Meteor, on the same code-sharing principles.
If you ask me, no chance to rule the web. JavaScript has been the standard for a very long time now. Because JS is not a flawless language dozens of js precompilers have been created till now, so the competition is pretty rough. Swift tries to be functional and provides you a way to use immutable data structures, but it is neither functional nor opinionated enough to force immutablity. With that said I personally don't think that it brings something new to the already existing web ecosystem. It sure is a nicer alternative to obj-c though.
Mocks, stubs, shims, etc. are usually just hiding the fact that the code to be tested is terrible, even in OOP languages.
Furthermore side effects have nothing to do with testing one's code. Why? Because you have to test your own logic, not I/O or something else. I/O is already tested by someone else. So you actually don't care where the data comes from - the filesystem or a hardcoded string - you care if the output is correct after the respective transformations are applied to the input.
And when you think about it testing shouldn't be that needed at all. In the OOP world, where state and identity are helplessly tied together and we are not working with values, but with references to values, tests might be a necessary evil.
In the land of Functional however writing tests shouldn't be needed, at least in theory. Why? Because if you write small composable functions you should be able to test them right in the REPL. Corner cases? You have to think about them right from the start. TDD forces you to do so - then why shouldn't you when you have the immense power of a REPL? Once a function is ready you are not supposed to change it much. If you do you should change the inner workings and not the input/output. If you need to change the i/o most probably you need a different function. But what is more important is that if you needed to change the i/o and had tests for this function you would actually need to change the tests as well, which is more work (double? triple?) and no added value.
Once a function is ready you are not supposed to change it much. If you do you should change the inner workings and not the input/output.
This presupposes that you will always come up with an adequate design, and that you won't have to refactor this library of functions. For large enough systems, this is almost never the case. For sizeable systems in production, you will always come up against things you haven't thought of before.
Granted, proper factoring of your functions in the first place is going to help a lot. (Small composable functions.) However, if you claim that you can code such that you never ever have to change your mind about function signatures, never have to propagate changes across the system, even in large production systems, then you're either not seeing something, or you have some technique which you should be selling or basing a consulting practice around. (And/or, maybe I have something new to learn.)
EDIT: I have written projects in Clojure, including a multiplayer game server. This is the experience I'm basing this comment on.
In the previous post I started with in theory. What this means is that theoretically things can go like this and in practice they have gone for me until now (with Clojure, so +1 for you too :)). It doesn't mean that it will work for everybody or even for me in the future.
Then: Granted, proper factoring of your functions in the first place is going to help a lot. hence -> Once a function is ready you are not supposed to change it much. much not= at all.
And you are absolutely correct - eventually you will have to change some of your functions. And when you need to do so you should focus on the logic and take your time until you understand it completely. Only then refactor the funciton. So the emphasis should be on the developer completely understanding the task and not on the developer finding a cheap way to change the code and rely on tests to catch his errors.
Furthermore if you change the signature of a function, tests are just another place where you need to change the function as well. In practice the tests that you wrote are not doing you any good because you cannot test the reinvented function with the old set of tests.
Sure, tests might catch a few errors here and there at some point. But after all what is more time consuming - maintaining hundreds of thousands of tests or focusing on your single, small, composable function and your problem?
I am part of a team that develops a very large .NET (with C#) product with hundreds of projects in the solution. So far the primary use of tests is to be maintained. They caught a few bugs, true, but nothing that our QAs were not going to see anyway. In imperative OOP land tests are a necessary evil, but with time I started doubting even that thesis.
One thing I've found is that what would help tremendously with large production projects results from epiphenomena in code, and is sometimes not what is sexy from a language design standpoint.
I am part of a team that develops a very large .NET (with C#) product with hundreds of projects in the solution. So far the primary use of tests is to be maintained. They caught a few bugs, true, but nothing that our QAs were not going to see anyway.
I was a fly on the wall when Extreme Programming was being formulated in Smalltalk. (Not part of the Chrysler C3 project, but I was working for a Smalltalk vendor and heard about what was going on.) Test Driven Development, or at least good Unit Test coverage, is essential for an agile style project in a language like Smalltalk. I'm not so sure unit tests are as essential for languages like C#. TDD does produce more testable code, but it has considerable "cultural overhead."
Two important questions to ask are: What aids refactoring? What stops refactoring? The answers to these questions are also different for large codebases as opposed to small ones. I have noticed that Swift's enums are tremendously useful in this regard, but they are not "sexy" so this doesn't get discussed very much.
> Two important questions to ask are: What aids refactoring? What stops refactoring? The answers to these questions are also different for large codebases as opposed to small ones.
Part of the point of thorough unit tests that really test all the external behaviors of all your classes (or functions, if you're doing FP) is that it can give you the courage to refactor. If all the tests run for this class, then I didn't change any external behavior of the class. If the whole project's unit tests all pass, then I can be highly confident that my change did not introduce any bugs.
Note well the condition, though: if the tests really test all the external behaviors of all the classes. For that to be true, you almost have start with TDD from the beginning of the project.
As long as there is some sort of logic tests can be written. The question is can we prevent writing tests at all by designing better our applications and by taking advantage of FP languages and tools like the REPL? And I believe at least in theory we can. In practice life happens so this theory is certainly not applicable to every case on the planet.
When things are decoupled refactoring is not that dangerous. If all pieces of your program are small, intelligible functions you can always just refactor a single, simple function and you should not need tests to rely on - all you have to do is make sure that this function is working like it worked before the refactoring (or better).
I disagree that I/O can be totally decoupled. The API that I/O functions generally adhere to is too weak for your application: on error, what do you want to happen? This pushes "I/O" functionality back into your logic, and that requires testing.
In this specific example, if you write the first file, but fail the second, you might want to undo the write to the first file. Or write another file to say you failed. Or write to a log. These sorts of things are important, and mocks can be a good way to fake it.
You should be testing your own code and not catching exceptions. I/O will either return an error or the string you need. There is no third option. So put aside the I/O - concentrate on testing the string transformations and on testing the logic that handles the error.
Btw in functional languages catching exceptions is not idiomatic, it breaks the functional approach.
In this example you are not supposed to test undoing the file. Undoing (deleting) is what I/O does and this part is tested by someone else, somewhere else. You only need to test the arguments that are passed to that specific I/O function. If they are correct your program is correct. If they are hardcoded you shouldn't be testing them at all.
You may want to write a log. Again, test what you are about to pass to an I/O function. Don't test if the file is actually written. If you supply the I/O function with the correct arguments and the function fails to write the file, the issue is not in your code - search for it elsewhere.
I read recently (in another article here on HN) a quote from Adele Goldberg that in OO, "everything happens somewhere else". This was not meant as a compliment to the OO approach.
Your approach seems to be "the IO happens somewhere else". I'm not sure that that's much more of a compliment.
I/O is someone else's code, not yours. So it's someone else's responsibility to test it. You should just test the arguments that you pass (in case they are generated) and trust that the I/O is properly tested by its author.
In OOP everything happens somewhere else because anything can hold a reference to the variable and change its value right under your nose.
Why is I/O always somebody else's code? (In my world, it's definitely not always somebody else's code, but I'm in embedded systems, which is an unusual environment.)
I mean, somebody has to be the "somebody else". What if it's you? Now you have to properly test the I/O code. (If you can get away with it never being you, that's fine, for you. Other people might not have that freedom, though.)
> In OOP everything happens somewhere else because anything can hold a reference to the variable and change its value right under your nose.
You're referring to a variable changing when it shouldn't. I don't think that's what the quote means. In OOP everything happens somewhere else because there's always another layer of indirection, and so the code that should change the variable isn't in the function (or file) that you're reading. This is why I say it's similar to your approach - the I/O is never in the function that you're reading. It still has to be somewhere, though. And the same thing that OOP does to the details of the computation (move it somewhere else), your approach does to the details of the I/O - it's not where you're looking, it's scattered somewhere else. For the same reason people complain about the effect of OOP in this regard, I distrust your approach for the I/O.
In OOP everything happens somewhere else because there's always another layer of indirection, and so the code that should change the variable isn't in the function (or file) that you're reading. - and because of that when you are debugging it may change right under your nose, with no code obviously responsible for that. There is a nuance, but the idea is the same - only the context is different. It's just that when you are debugging it hurts the most.
In the context of the article I/O is someone else's code. If it were my code it should be tested elsewhere, as if someone else wrote it. Not in every place that my application uses it.
In the particular case with writing the actual I/O code - I don't have enough experience, so I can't tell you. Maybe tests are absolutely necessary. Maybe mocks are the only way. I don't know.
What I do know is that programs transform input to output - input -> transformations -> output. You never care where the input comes from nor where the result of your transformations go. All you care is if your transformations are correct.
Your program may be very complex, with many inputs that you don't control, scattered around your logic. So prior to writing tests you have to decouple those dependencies from your logic and only then proceed with writing tests. If you are using functional language you may reduce every piece of logic to the simplest, smallest function possible, test it in the REPL, document it and move on without actually writing tests.
You don't need to mock it. In your tests just manually call the code that is going to be executed if the I/O call returns an error. This goes for both OOP and FP.
I write fairly large backend systems that are continuously growing and facing evolving requirements. I follow those principles, and have yet to write a single test. It's just so much more robust to separate I/O code from other code, and verify that the building blocks of the system work.
I do this in Clojure, but am looking to migrate to Haskell soon to enforce this even further.
I have used both languages quite a bit. What I miss from Clojure when doing Haskell is having access to a seemingly infinite collection of libraries (thanks to the JVM/maven/etc). There are of course many libraries on Hackage but the JVM ecosystem dwarfs most others.
Tests are very useful for distinguishing between "what a function does" and "what you think a function does". Especially when you're building on functions written by others. Types and documentation help too, but if someone's already misunderstood something, such descriptive information might still fit nicely with their incorrect world view.
Stolen from Wikipedia: "FRP is a paradigm using the building blocks of functional programming." I doubt OP was referring to FRP.
In any case: in a functional programming language, you'd just unit test the building blocks (functions). Assuming every function is pure and total, these unit tests should be succinct and mirror your business logic quite closely.
Not with unity tests, that's a certainty. Unity tests do not test business logic.
Yes, when people say that no tests are needed, it's hyperbolic. Tests are always needed, but you do not need to test all corner cases, just the ones created by business logic.
What struck me the most in your comment is the fact that people just disappear. It's the same with me. If I don't call the other person won't either. If I call he/she is busy, on vacation, doing something else. If we meet we can stay up all night, chat, laugh, have a nice time. Then we both go on our separate ways.
I really didn't understand how this works. I know that now I work a lot. But I wasn't like that when I was younger. Nevertheless it was always me that had to keep a relationship going. If I didn't do anything things faded away pretty quickly. If I did the relationship agonized for a while before ceasing.
I know that I am not unpleasant - people don't talk with someone for hours just to be polite. And only one person has ever told me that she always hated me because of how annoyingly smart I was. But then again I went to university where one of my fears was that someone is smarter than I am (and probably a lot of folks actually were).
When not at work most people relax with a game of cards, small talk about repairing the car, the new update for the phone, etc. What I do for fun when I am not programming is read about consciousness studies, biology and nutrition, philosophy, physics. If the person is up for it we can talk for hours on such a topic. But most people most of the time are not. I have two kids and I like talking about them - kids are fascinating and incredibly funny at the same time. However people who don't have kids don't have a clue what I am talking about (I was the same when I didn't have kids). People who have kids usually just want a beer. I like running, hiking, riding a bike - good topics for a conversation. Until I say that it's fun to ride a bike at night, while it's raining.
So I was at lunch with my colleagues one day and it suddenly hit me - the problem is not that I am smarter or a genius or the like. It would be nice, but actually most of my colleagues are as smart as I am and some of them are smarter. The problem is that I cope with pain and hurdles differently. When people stop to rest I usually push some more. When they seek a shelter from the rain I want to ride more. When they need to chat with someone I need to read a book. I am a hyperactive introvert with a strange set of interests. I am a little different and because of that it's much harder for me to make friends.
You say that you have started so many projects. To me this means that you have a lot of energy and it's quite possible that people just don't want to play catch up with you – it’s tiring for them and they may actually prefer to just relax.
> The problem is that I cope with pain and hurdles differently. When people stop to rest I usually push some more. When they seek a shelter from the rain I want to ride more. When they need to chat with someone I need to read a book. I am a hyperactive introvert with a strange set of interests. I am a little different and because of that it's much harder for me to make friends.
Shared suffering & growth is how you form the strongest bonds.
If you're biking w/ people and they stop to rest (or b/c of rain). Time to make a choice. You can keep going, or stop with them.
If you keep going, you'll grow, your legs will get stronger.
If you stop, they'll grow, their legs will get stronger. But you have to keep stopping to see it happen. In time they'll stop less often. You'll love watching them get stronger, and they'll love that you were there to witness their growth. You're sharing in their suffering, even if you're not winded or uncomfortable yourself.
Fast forward to the future, now you're struggling with something physically or emotionally, and one of them may stop to be by your side. They could've pushed past you but they want to be there for you. You were there for them.
What I actually meant is that people are usually not up for the challenge at all.
That being said I have found a few people that may be ready to walk with me. Since I am very grateful I am willing to wait for them and help them whenever they need it.
I too find myself in the same conundrum. People and contacts just disappear if I don't put effort in it. It is always me who have to follow up for some reason. How did you get over this?
Well, to be honest I didn't. I just started dedicating my time and efforts only for a few select people that are worth it. Otherwise social interaction can be too tiring and even lead to depression. Empty conversations, not loneliness, are what I hate the most.