There are two well-known ways to make things impossible to misuse. One way is to remove all but the simplest and safest features. You could call this the Haskell Report approach, where the original language avoided most security holes by forbidding user input.
(Yes, I know that wasn't the point of the Haskell Report. Fortunately, most of the really good Haskell fans have good senses of humor.)
The other approach is less popular. It allows people to perform complex
tasks—even dangerous ones—but it prefers to make them safe by
making the safe things easy to do correctly. You could call this the CUC
approach, where a language designer would never say "Hey, let's
provide a builtin where you can load arbitrary data from the Internet and
automatically populate values into your single global variable namespace!
Including $is_admin
and $is_verified
and
$discount
!"
It's easy to believe that the next decade of professional programming will deal with the tension between functional programming and just-get-something-done-now programming. (Objects have won, at least enough. Genericity has won, at least enough. JSON and REST have won, at least enough to wash the taste of SOAP out of our mouths.) Popular belief may claim that functional programming is interesting because it can provide cheap parallelism with the flip of a switch. Popular belief is wrong, but popular belief is popular.
The interesting parts of functional programming aren't the silly utopianisms that "Learn Haskell and your programs will automagically scale to 256 cores and beyond!" but the way we can steal useful features of functional languages as patterns for real programs that aren't solely pure, functional, typeful, lazy, and referentially transparent.
To call back to the Haskell Report reference, we handle user input all the time, but we can be safer and saner about it.
Many of your programs will benefit from pervasive laziness. (Most of mine do.) Many of your programs will benefit from reducing side effects, especially global side effects. It's not just Haskell; you're probably already using dependency injection too.
Another great feature of functional programming is immutability, where things can't change after you've created them.
This is in truth a great feature of any API. I reviewed some code from a slightly less experienced developer a couple of hours ago. He's competent with Perl, though his code has a C accent that comes from electrical engineering school. Good C programmers handle errors. Robust C code spends probably at least 30% of its lines of code on error handling. This code was no exception.
The bad news is that C has few possibilities for abstraction. In particular, C doesn't make it easy to create immutable data structures. The good news is that Perl does.
The biggest suggestion I made was to consider a pattern I've discovered while using Moose pervasively: perform all of your verification and validation at the point of object construction and resist the temptation to make mutable objects. This pattern obviously needs a catchier name.
The benefit to your API is that if you can create an object, the object is always in an consistent state. Any validation errors get reported from the point of the code that attempts to create the object. This keeps the scope of the error reporting to the point at which it matters the most (at least to the constructor API). This reduces the possibility that someone will manipulate the object and make it invalid.
You have to go to great lengths to misuse such an object (or such an API) because you have to bypass its interface altogether, if the interface forbids mutation.
You get to remove a lot of error checking code because within the API you can assume that the object is always in a valid state. (Anyone who's violated that contract gets to keep the broken pieces.)
This pattern would work just as well in C or Python or PHP or Java. Nothing is specific to Haskell or Perl that makes this pattern impossible in other languages. It works better in Haskell than in Perl because the language supports it much like it works better in Perl than in C because the language somewhat supports it and great libraries make it easier.
The technique works, if you're diligent about figuring out what changes from what stays the same at each level of your design. That may mean some classes or data structures need to break into smaller pieces. That's not always easy, nor always simple. It's often not obvious.
The benefit, though, is what the functional programmers have been saying all along. Reducing mutable state can help us write better code and less code and code that's easier to use correctly and much, much more difficult to use incorrectly.
(One of the reasons I use Perl 5 so much is that Moose makes it really easy to make my classes immutable. Any object system worth using should do the same.)