Sure, I get what it is but what I don't get is how I would use it in my world of mutating data and side-effects.
Sure, I get what it is but what I don't get is how I would use it in my world of mutating data and side-effects.
I understand what you mean, because even though I love FP a lot, I still struggle to conceive whole systems (other than simple map-filter dataflow ones) this way. So far I keep the FP principles for small pieces, hoping later on I'll find a way to close the gaps.
[1] John Backus tried to design purely functional languages a long time ago and he hit the IO wall (he mentions it in some article). Haskellers had issues at first and found that Monads were a nice functional way to express IO-like logic. It's still something weird, a lot less clean than purely functional expressions to be evaluated.
Most of us probably already follow best practices in terms of small functions, no global state, promises, composability etc. But at the end of every chain I have a db query and most of those mutate some data or other.
Hopefully some day it clicks.
While you wait for that, try writing useful code despite not fully understanding it.
Tell me what you think of the following code examples:
> sum . take 2 $ [1,2,3,4,5]
3
> -- now lets pretend we have a list of numbers we got from the DB
> let mockDBResponse = return [1,2,3,4,5] :: IO [Int]
> -- first lets try the exact same code as above
> sum . take 2 $ [1,2,3,4,5]
3
> sum . take 2 $ mockDBResponse
<interactive>:46:16:
Couldn't match expected type ‘[c]’ with actual type ‘IO [Int]’
Relevant bindings include it :: c (bound at <interactive>:46:1)
In the second argument of ‘($)’, namely ‘mockDBResponse’
In the expression: sum . take 2 $ mockDBResponse
> -- what to do? Use <$>
> import Control.Applicative ((<$>))
> sum . take 2 <$> mockDBResponse
3
1) Applying pure functions to pure functions sum . take 2 $ [1,2,3,4,5]
2) Applying pure functions to monadic functions that implement fmap of the Functor typeclass sum . take 2 <$> [1,2,3,4,5]
When I noticed that adding brackets around $ allowed me to apply pure functions to monadic ones a lot more made sense to me.See "Turning the database inside out with Apache Samza" by Martin Kleppmann: https://www.youtube.com/watch?v=fU9hR3kiOK0
It is, again, like saying "I don't see how this helps me in my world of uncommented code". You could just comment your code, right?
(Or if that's too much work, or it's a shared code base and you can't get buy-in, just start going forward?)
What you want to avoid is side-effects. One class of side-effect would be database operations. So let's imagine you have this code:
let products = [{id: 1, price: 10.12, desc: 'foo'}, {id: 2, price: 5, desc: 'bar'}];
for (i = 0; i < products.length; i++) {
products[i] = formatPrice(products[i]);
}
console.log(products);
Looks good, right? I mean, presumably formatPrice just, I dunno, turns prices from numbers into strings like "$5.00", or whatever. But what if formatPrice looked like this? function formatPrice(product) {
product.desc = '';
saveToDatabase(product);
product.price = '$' + product.price.toString();
return product;
}
There might be some reason why that's the exact functionality you want, but it's terrible practice to write; your price formatting code is performing database operations, which are side effects. Don't do that. :)What you want is to chain together a number of simple functions. And sure, one of them could even be "saveToDatabase". That's fine. What's not fine is having some random function, somewhere, randomly mutating your database.
If you think FP requires you not to use a database, you've badly misunderstood FP. What it's asking is for you to be explicit about what you're doing. Pure functions are easy to reason about; global variables are hard. Database operations are especially hard to reason about, which is why you want them locked away safely at the start and end of your processing chain, and not just as random side effects of otherwise unrelated code.
The book in question gives the following explanation for what a side-effect is: "We'll be referring to effect as anything that occurs in our computation besides the calculation of a result."
This is in chapter 3, and they provide the following list for what is a possible side-effect:
- changing the file system
- inserting a record into a database
- making an http call
- mutations
- printing to the screen / logging
- obtaining user input
- querying the DOM
- accessing system state
With that in mind, there is not much I can do in my code and have a functional app without side-effects.Not quite. Let's take one of those side effects as an example... say accessing system state.
You probably aren't just accessing system state. You probably want to update it in some way right? Your function to update it could easily be pure.
Take obtaining user input for example. Getting the user input is usually the most boring part of the program isn't it? The majority of what you do with that user input could very likely be pure functions.
You also get the benefit that those side effects have a set of laws to govern their behavior in failure cases.
It's refreshing getting rid of the whole class of bugs caused by developers using harmless sounding method names that subtly alter their data.
It did require substantial rewriting of the basic units of the program I tried it in, so there is that...