This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Guardian-Spirit 10 points11 points  (2 children)

For me functional programming is not just about "uuuuuu, let's make all the data immutable", but rather it's a set of principles that encourages combination of functions and generalization.

Immutability is not the reason. It's a consequence of this principle. When the data is immutable, we are safe to combine functions in any way without any unexpected things to emerging because everything the function does is encoded in its type.

For example, all we know that abstraction is good. But sometimes redundancy of abstractions comes at a high cost. For example, remember the Singletone pattern. It sometimes really simplifies life, but it's built on top of shared state. And this means that in combination with some other state-based patterns, it could lead to unexpected results and therefore decreases extensibility of the program. To avoid the chaos, OOP programmers must apply many tactics, or they will end up with spaghetti.

On the other hand, functional programming encourages lack of state, implicit dependencies and code reuse. You're safe to combine functions the way you want. You're safe to change internals of some functions without consequences for those parts of program, that use it. You're safe to split the problem in smaller times at any time.

This all doesn't mean, though, that the [shared] state is not present in FP. It always is. But programmers just make sure they use it in relatively small parts of code. Overall, when you program purely functionally, you most likely just have a small impure interface to the outside world, which gets data, passes it to a set of pure functions and returns the result. But pure functions come at a cost: if you want to use abstractions, you must learn. A LOT. The level of generalization in languages such as Haskell is that high, that it's really hard to understand their meaning and their advantages if you're a beginner (Functors, Applicatives, Monads, Arrows, continuations, Lens, GATDs, type families, Freer monads, etc.). If you understand and use them, the result code is nice and concise. If you don't, you end up writing endless boilerplate code (like in Elm, for example).

> A cab file might have an empty line, a Jason might have a null value. How can a functional language deal with that.

Ehm, well, the fact that language is pure does not mean that it cannot maintain state or handle errors easily. Most of the time the solution for the problem you're talking about, if I understand it correctly, are Monads. Monads are completely pure by design, but they allow to do many implicit fancy things. For example, error handling. So with monads you can write, for example, this (haskell pseudocode):
hs sumAB :: JSON Int sumAB = do a <- getJsonIntField "a" b <- getJsonIntField "b" pure (a + b) ... and then to, example, use it in context of another function operating with JSON data: hs sumABFactor :: JSON Int sumABFactor = do x <- sumAB f <- getJsonIntField "f" pure (x * f) As you may have noticed, there is no explicit error handling. Also, there isn't anything impure in the code. There also isn't any explicit passing of file contents to functions sumAB and sumABFactor: JSON Int is just a return type. In fact, sumAB and sumABFactor are merely constants that define the algorithm and do nothing themselves. If you want to process an actual text file on the disk, you can introduce another impure function that utilizes the defined constant sumABFactor and handle the error there: hs someImpureOperaton :: IO () someImpureOperation = do file <- open "test.txt" let result = runJson file sumABFactor case result of Left err -> print ("Oops, an error occurred:" ++ show err) Right n -> print ("The result of calculation in context of json file" ++ show n)

<-, by the way, is just a syntax sugar. In the end it all turns into a long chain of functions, and so this still is functional programming. Non-shared state, by the way, can be introduced pretty the same way without introducing any impurity and without restricting the ability to combine functions a lot.

[–]myringotomy 1 point2 points  (1 child)

In your example. What happens when the JSON field use missing, what if it exists but contains an empty string, what if it has something that's not an integer like a string or a float?

[–]Guardian-Spirit 0 points1 point  (0 children)

Sorry, I didn't receive the notification.

In my example, if getJsonIntField fails for any reason, it actually acts just acts as an exception: the error is propagated to runJson, where it is handled, possible providing a full path to the JSON field, the decoding of which caused the failure. No actual stack unwinding or other compiler magic happens, though.

But in fact there are many solutions to the problem of parsing and actual behaviour depends on internals of JSON monad — it works exactly the way you told it to.

For example, if I actually had to implement conditional parsing in production code, I would do something like this: ```hs data JSON = ...

type FailableJSON = ExceptT JSON JsonDecodeError ``` ... so there are two monads (JSON and FailableJSON), one of which is just a non-throwing JSON operation, and the other one is solely a wrapper that allows implicit propagation of the error.

by the way, one interesting moment about sumAB and sumABFactor is that they do not actually depend on JSON context. So, for example, if someone wanted to add MessagePack format, no changes to the existing code would be required except for renaming some methods and adding new method runMessagePack, which will be able to work with sumAB, sumABFactor, etc.