6 ms·
I think you misunderstood Monads, or the role type theory has to play in modern programming. Large projects inevitably benefit from static guarantees enforced
by adimitrov 8y ago
I think you misunderstood Monads, or the role type theory has to play in modern programming.
Large projects inevitably benefit from static guarantees enforced automatically by your environment. That can be a 3rd party static code analysis tool or the compiler. Even just a linter will improve code quality and thus developer happiness and productivity.[] Having your compiler enforce* the functional core/imperative shell, and exposing your business logic only through functional components is what makes a strongly typed language of the ML family stand out over, say Clojure.
Mutating state is no problem in a strongly typed functional language. In Haskell, just put your computation in an ST Monad. You can even still expose a functional signature that doesn't leak the ST monad if your algorithm is faster with mutation.
[*] Overall. Some people will probably be unhappier, because they have to follow "arbitrary" rules now, but those would usually have been the worst offenders.
- pankajdoharey 8y agoThere have been many articles on this topic. There isnt any evidence to suggest that static guarantees makes your code better. Ofcourse what does make your code better is immutability. But complete immutability isnt practical and even Haskell people understand that but they continue to pretend that programs are about mathematical purity. If that isnt enough claiming static typing removes the need for testing is complete bunk.
- pwm 8y agoHave you written programs in Haskell/F#/Ocaml? Static guarantees, especially of expressive type systems, absolutely make your code better and their benefits compound as your system gets bigger. The type checker acts as a guardian of the soundness of your whole domain. And yes, expressive static typing removes the need for a whole class of tests, namely the ones that you'd have to write in other languages if you are disciplined enough to care about the soundness of your domain model. I personally loath writing these type of tests but I do when I can't use the power of say Haskell because I care. Immutability also makes you code better but it's an orthogonal concern and utilising both is a smart move.
- pankajdoharey 8y agoIf it were true, we would all be writing in c++ and there would never be a Stack overflow or Null pointer exception. But thats isnt true.
- pwm 8y agoAny language that has null, including C++, does not fall into the category of languages with expressive type systems. As soon as you have proper sum types the null issue goes away + the whole big world of working with ADTs open up.
- Chris_Newton 8y agoMutating state is no problem in a strongly typed functional language. In Haskell, just put your computation in an ST Monad. You can even still expose a functional signature that doesn't leak the ST monad if your algorithm is faster with mutation. That works reasonably well in some situations, but not all. We often work with local, temporary state, meaning something mutable that is only referenced within one function and only needs to be maintained through a single execution/evaluation of that function. (Naturally this extends to any children of that function, if the parent passes the state down.) If that function happens to be at a high level in our design, this can feel like global state, but fundamentally it’s still local and temporary. I/O with external resources like database connections and files typically works the same way. We can also have this with functions at a lower level in the design. An example would be using some local mutable storage for efficiency within a particular algorithm. However, not all useful state is local and temporary in this sense. We can also have state that is only needed locally in some low-level function but must persist across calls to that function. A common example is caching the results of relatively expensive computations on custom data types that recur throughout a program. A related scenario is logging or other instrumentation, where the state may be shared by several functions but still only needed at low levels in our design. Now we have a contradiction, because the persistence implies a longer lifetime for that state, which in turn naturally raises questions of initialisation and clean-up. We can always deal with this by elevating the state to some common ancestor function at a higher level, but now we have to pass the state down, which means it infects not just the ancestor but every intermediate function as well. While theoretically sound in a purely functional world, in practice this is a very ugly solution that undermines modularity and composability, increases connectedness and reduces cohesion. And weren’t those exactly the kinds of benefits we hoped to achieve from a functional style of programming? If anyone would like to read more about this, we had an interesting discussion about these issues and how people are working around them in practice over on /r/haskell a couple of years ago: https://www.reddit.com/r/haskell/comments/4srjcc/architecture_patterns_for_larger_haskell_programs/ https://www.reddit.com/r/haskell/comments/4srjcc/architectur... Spoiler: We didn’t find any easy answers, and everyone is compromising somewhere.