|
Sure, so to go back to my original point, functional programming is about building a single expression to perform your computation. Obviously this would be unweildly if you had to write your expression on a single line, so we build abstractions out of functions (hence the name FP). To combine these functions in arbitrary ways and stick with the single expression model, we need some way to create a pipeline of functions. So we have the function composition operator . program = (phase 3 . (phase 2 . phase 1)) The input of program is fed to the first phase, then the second phase, then the third phase which will produce the output of the program. Each phase can be replaced or the pipeline extended as long as the inputs and outputs match. This is an infinite composable and is the basis of how FPers architect applications, by splitting each phase in their own pipelines, smaller and smaller until you get to very basic functions. We like it compared to OO because it's straightforward to understand the flow of data. There is no need for a complicated object graph and any piece can be replaced if the types are correct. As in OO with inheritance (maybe not the best comparison), you also need a way to specialize functions in order to get their signature to match the input and output requirements of a pipeline. For that, we can partially apply a function. For example, if I need to connect the function: Int String -> Int (That is a function with two parameters, an int and a string that produces an int) to the output of the function * -> String (a function that takes some arbitrary value and produces a string), I need to be able to partially apply the Int, and get a function that is just String -> Int. In Haskell, all functions are single input, single output. Multiple inputs is just a shorthand for a pipeline of functions. The function Int String -> Int is really just the pipeline Int -> String -> Int. (You can see how this all starts to fit together). The function application operator is the most basic, it's just a way of forcing the order of evaluation. When you're building pipelines, you want to be able to construct a pipeline before you apply an input to the pipeline. To get the correct order of precedence between . and application, you use the explicit $ operator. What I'm trying to convey here is that these operators are akin to inheritance, composition, overloading, etc. in OO. They are ways of working with the basic building blocks of the paradigm to make larger and larger programs. As you can also see, being able to create pipelines is the basic piece of the puzzle, and building pipelines requires automatic currying to facilitate making the pieces fit, while the application operator allows us to control the order of evaluation. |