PurelyFunctional.tv Newsletter 394: on the feedback of types
From OO to Clojure Workshop!
Watch my free workshop to help you learn Clojure faster and shift your paradigm to functional.
Clojure Tip 💡
on the feedback of types
Well, I knew I was wading into dangerous waters when I brought up types last week. Danger is practically the job description when you write a weekly newsletter . . . ;)
Anyway, I received a very astute question about something I said about types last week. Here's what I said:
Now, you might think that something like more static checks would help regain my confidence. But if you did, you would be mistaken. Static checks are great, but they only give you confidence that you aren't going to mess up production with bad code. They can't give you confidence that the code you've written but haven't run through the compiler will type check on the first try.
Now, I'm not anti-types. I think types are great. This is just not something they can solve by themselves. What you really want with types is continuous type checking. The worst thing is to write 100 lines of code, run the type checker, and learn that you have type errors somewhere you now have to find. The problem isn't that they are hard to find, it's that it takes time to go back and fix them. You are bumped out of flow and by the time you're done, you've forgotten what you were even trying to do.
It's much better to write 1 line of code and find out there's an error. You can fix the line you just wrote, while it's still in your working memory. This is the same confidence that we get when doing RDD (REPL-Driven Development). You write a small piece of code, run it, and confirm the output. It's all about the constant feedback.
Now that I read it, it seems like an unclear mess of ideas. I'm really glad I got good questions about it. Here is John Perdue's first question:
1) I had assumed that the main advantage of running code in the REPL was to do with getting immediate feedback on your assumptions beyond checking types - like making sure your function actually returns the correct value for a given input rather than some other value of the same type (much like you would in example based unit tests). In what way do you mean that static checks give you confidence that you aren't going to mess up production? I thought the point of the REPL was that static checks alone can't give you confidence that you won't mess up production, whereas actually running the code with some real data tells you much more?
Good question. It's true that runtime information can be richer than static information. But that's not what I was getting at. I was trying to pinpoint a certain type of confidence that RDD gives you. Type checking does protect production code from certain kinds of errors. That can give you confidence that you won't break production in those certain ways. However, that's not the kind of confidence I was seeking. I wanted confidence line-by-line or expression-by-expression that I was on the right track, that I wouldn't have to debug a large section of code at the end.
And John's second question:
2) Perhaps because I came from an IDE environment where code is continuously type checked (and you get immediate squiggles when types don't align) I have a blurred concept of "static checking" vs "compiling". How are you differentiating these terms here? I always thought of the static type checks my IDE does as simply being the result of running part of the compiler in the background continuously, and as such don't really think of them as separate things. In what way can code pass the static type check but fail the compiler checks?
Another good question, and thanks for the opportunity to clarify. Static type checking is sometimes only available when you compile, but in theory you could separate checking from compiling and only invoke the checking, or invoke checking and compiling. But that's beside the point. What I was trying to get at is that, for flow, type checking is only as good as the frequency of checks. If you've got a system for fast type checking after every keystroke, that can give you the speed of feedback you need. Whether the feedback contains the right information, that's between you and your type system---but in my experience, no statically typed language does give you the right information.
When I'm doing RDD, I often know that I don't have the type right, but that's because I'm not finished yet. If I had a type checker, it would tell me what I already know. I would prefer to have control of when the type inference ran, and over what part of the code. In RDD I can evaluate one expression, one top-level form, or the whole file. I would like the same for type inference. In other words, don't tell me I have the wrong type! Tell me the type of what I have!
Make sure the function is internally consistent (for instance, that it passes the correct types to other functions). But don't complain that it doesn't meet the expectations of other functions. I'm not there yet. I want the type inference engine to give me information during development but turn into a whole-program type checker to protect production.
But that's not the end of the story. I can't do RDD if I can't run the function when it doesn't return the right type. It would be really cool to have a language where you could run code that was of the wrong type. This is a little bit different from the typical "runtime vs static semantics". It's about being able to run pieces of code without invoking a whole-program check. As long as the code is type consistent within the scope, it should be okay to run. That would let me do RDD and have type checking. The best of both worlds!
Okay! Thanks, John, for the question!
Quarantine update 😷
I know a lot of people are going through tougher times than I am. If you, for any reason, can't afford my courses, and you think the courses will help you, please hit reply and I will set you up. It's a small gesture I can make, but it might help.
I don't want to shame you or anybody that we should be using this time to work on our skills. The number one priority is your health and safety. I know I haven't been able to work very much, let alone learn some new skill. But if learning Clojure is important to you, and you can't afford it, just hit reply and I'll set you up. Keeping busy can keep us sane.
Also, if you just want to subscribe for a paid membership, I have opened them back up for the moment. Register here.
Stay healthy. Wash your hands. Stay at home. Wear a mask. Take care of loved ones.
Clojure Challenge 🤔
Last week's challenge
- Better filename sort - submissions
Please do participate in the discussion at the submission links above. It's active and it's a great way to get comments on your code.
This week's challenge
FizzBuzz, but with no ifs
FizzBuzz is a classic interview exercise meant to filter out people who can't program. It tests that you can write conditionals, loops, and a basic modulo check for divisibility. While it seems kind of insulting to ask someone to solve such a trivial problem, imagine if the interviewee couldn't do it. It's a quick way to check for basic skill before wasting an hour in the interview.
This week, you're going to write FizzBuzz, but just to show off to the
interviewer, you're not going to write any conditionals or loops. Just
to be clear, that means no
Here are the rules:
Write a function that is passed a sequence of integers.
For each integer, if it's divisible by 3, print "Fizz". If it's divisible by 5, print "Buzz". If it's divisible by both 3 and 5, print "FizzBuzz". Finally, if it's divisible by neither, print the number itself. Each printing should be on a separate line.
You can also find these same instructions here. That's also where submissions will be posted. And there's a great discussion!
As usual, please reply to this email and let me know what you tried. I'll collect them up and share them in the next issue. If you don't want me to share your submission, let me know.