3 ms·
I've been taking a kind of middle ground lately: build functional APIs. Move your side effects as far outward as possible and make your library's interface take
by orthecreedence 4y ago
I've been taking a kind of middle ground lately: build functional APIs. Move your side effects as far outward as possible and make your library's interface take everything it needs and return everything it computes. No HTTP calls, no database access, no random number generation. For example, don't have your library build the query and then run it, have it built the query and return it so it can be run at a higher-level.
I know this seems obvious, but it was a breakthrough I had with organizing the way I built things a few years back and it has been serving me amazingly well. I think I originally stumbled onto the idea through a talk on domain-driven development.
The result is that you get a lot of the benefits of functional (easy testing, portability, etc) but you don't have to deal with all the oddball patterns of recursion or currying or things that, yeah, sure, makes sense on some theoretical level but makes me want to gouge my eyes out when I try to read it. I've tried fully-fledged functional programming in a few cases and it doesn't click with me. I appreciate and understand that some love it, but in the end if your exposed public API is functional, the internals are less important.
To me the important thing is the mindset: move your side effects as high up the chain as you can.
- i_hate_pigeons 4y agothis is what I learned is functional programming and I've also benefited greatly from it
- ledauphin 4y agothis is also known in some circles as "functional core, imperative shell." I agree that it's an excellent intermediate paradigm.
- mdcds 4y agoI tried learning Haskell for fun while working as a Python dev. Understanding importance of managing side-effects definitely influenced how I changed my approach to code organization at work.
- adamkl 4y agoThe term for this that I have come across is "stratified design" and goes back to the book Structure and Interpretation of Computer Programs by Abelson and Sussman. I have no idea why this approach isn't more well known (especially compared to typical "layered" design approaches) as the benefits are so great! https://medium.com/clean-code-development/stratified-design-over-layered-design-125727c7e15 https://medium.com/clean-code-development/stratified-design-...
- jmt_ 4y agoIs there a good open-source example of this paradigm? I'm interested by the idea but struggling a bit to see how it would actually be implemented for something like, say, a regular REST/JSON API.
- orthecreedence 4y agoI can give you a link to a project I'm working on: https://github.com/stamp-protocol/core/tree/master/src https://github.com/stamp-protocol/core/tree/master/src It's a cryptographic identity protocol. It's architected such that the identity is not a document, but rather a DAG of transactions that build the document (more here: https://stamp-protocol.github.io/ https://stamp-protocol.github.io/). This core library uses the "functional" api aspect I'm talking about. It lets you create and run DAG transactions, but doesn't save them or generate keys for you or anything like that. When you create a transaction, you have to pass in all the data required to complete it, and when it completes, it returns back a full transaction set (or an error). There are some utility functions in there that are not entirely functional, but the gist of it is that 95% of the things you do have no side effects and have to be dealt with outside of the library itself. As far as a REST API, you're dealing with a large number of inputs/outputs (side effects) so this paradigm might be limited. However, you can still think in terms of side effects (HTTP request, HTTP response, database, third-party HTTP calls) and compartmentalize your logic from those operations into functional units: if I `POST /users?name=andrew&likes=[dogs,programming]` then instead of going routes/user::save() -> models/user::save(params) -> models/db::run_query(qry, params) you might do something like routes/user::save() -> models/user::create_user_instance(params) -> models/user::create_user_query(user{andrew, [dogs, programming]) -> models/db::run_query(qry, params) Here, you flatten out your call stack, functionalize your calls (create_user_instance and create_user_query will ALWAYS return the same values given the same params) and move your final database call up the stack (as high as you can afford to). Obviously calling the DB in a route is dumb, but hopefully you get the general idea.
- kephasp 4y agoYou may find it interesting to study FP a bit more, because recursion, when understood, is pretty universally felt as way more readable than its imperative alternatives. I also usually find currying extremely practical and easier for me when it's available. Most of us that only use FP in our day job don't do it out of masochism, AFAICT. I certainly don't.
- grumpyprole 4y agoYes, recursion can be used to define a function inductively, just as we would in maths. However, recursion is the "GOTO" of functional programming and we prefer to used structured programming if possible, e.g maps/folds/unfolds etc. That would be a better reply for the parent post.
- kephasp 4y agoOf course I use combinators more often than I write recursive functions. But if someone finds recursion less readable, I still think they're missing something crucial. And I don't think recursion is a GOTO that should be avoided. It's a useful tool that's easy to read and reason about.
- grumpyprole 4y agoI agreed with your parent post and upvoted you, but too many inexperienced with functional programming claim it is all (or too much) recursion. I think this should be challenged. Yes recursion is a useful tool, just like GOTO and is sometimes necessary or more practical. But it is better to e.g. use a fold if you can because that further constrains and aids reasoning. For example, a fold will always terminate unlike general recursion. I recommend looking into recursion schemes, if you aren't familiar with them.
- kephasp 4y agoI read about them a few times, but I never saw them proposed as the main access to recursion. Are there examples of that use?