Type parameterisation is done for us to later discover, for instance, that we could test our CRUD service layer with all combinations of errors a database can produce as a single test case. How many applications do that?...
Any implementations should go with property based tests/laws. Else it is fairly hard to convince even ourselves why not a Java/Python/any imperative alternate. Let's don't be biased for no reason.
It is easier to convince that concurrency management and related library development with Scala (well, FP) is better as we love compositions than call backs and waiting. But application level FP usage is justified, for instance, when you are able to say “Let me push to master, coz it will work for sure”. That confidence comes easily with type driven development and laws.
While abstraction of concrete implementations to a generic method is a valid pathway, let us also make sure we don't pollute our abstraction based on concrete implementations. It makes the code unreadable, complex and sometimes who knows...pointless.
In almost all situations, the design of our abstractions should be based on normal thinking with common sense, and not based on how bad the concrete implementations are
Example:
- We discovered
monoidisusefulto addtwoimmutable Maps, and we believe monoid was notdesignedspecifically to this problem in the first instance. - Monoid abstracts the
behaviourofassociative appendof two values andhaving a unit when appended doesn't change the result. Theabstractioniscleanandpowerfulenabling us to solve a variety of problems in thetype classworld! - In other words,
Monoidis a perfect example of a clean abstraction not influenced by anyspecificsolutions.
To a certain extent a "sense of solution to a usecase" allows you to abstract better
compared to you having a "complete understanding of the solution to a usecase".
- All of our functions should be total, side effect free (meaning, things that you are not sure about on how it works). Or in simpler terms, the function should work for all possible
Inputs - The functions should be deterministic. Passing x should always return y. Even our system should be following this principle, and not just in isolated functions.
- Mixing state management along with behaviours are not allowed. No one wants two threads of execution to mutate the state of an account balance, which is a public variable in a class that has
debitandcreditmethod in it. State and behaviours are to be always separated. This is achieved through algebraic data types, type classes and functions. - Believe in function passing, that may turn out to be your dependency injection. Believe in higher order functions. Pass the function A to the function B, instead of calling function A in function B.
Thats all FP is demanding us to do. Any usage of of Monad Transformers, Free Monads and advanced combinators are just secondary and it requires only learning. Usage of advanced constructs doesn't ever mean, our app is robust and testable.
If your app fails at any time with a bug, there are things to fix beyond "just patches". Obviously, there are no abstractions without a bit of leak and there are almost no abstractions which I know that can't be abused !