Functional Programs Are Imperative Pumpkins
blog.eventsourcing.com
blog.eventsourcing.com
I see the implication being that when all is said and done, functional programs are really just imperative programs in disguise (I don't have an opinion on the matter).
[0] https://en.wikipedia.org/wiki/Cinderella#Cendrillon.2C_by_Pe...
The implication was that rather systems composed of functional programs but non-functional databases are not functional (i.e. the functional components are dwarfed by the imperative nature of mainstream databases)
Every Haskell tutorial seems to introduce the IO Monad to illustrate this distinction.
Unfortunately there are some roadblocks, like little information and conversation about the topic and no real mainstream implementation of these ideas.
I've been working on a system, that has is based on an immutable data layer and it is just a wonderful thing to work with, as long as you have enough disk space.
Where I think this could lead to something great is if the DB is alike this:
- Relational
- You have normal tables. Consider them your up-to-date cache. Most scenarios will be covered here (ie: The log recomputing is NOT at the app level, but at the DB level).
- You have a event log where everything is stored, plus your "tables" for the info you need up-to-date. But you don't need all to be a table if not make sense, and do full re-computation if desired.
- Your backup, sync and load history is around the event log.
- You don't even need a "table" if you wanna only a "index", this lead to me:
- You submit a data request (like a POST), and the DB convert it to the tables and log, but:
- You can instead (or also!) hook here and build secondary indexes and do other stuff. You can do it async, put the computation in a queue, do validations, etc as fit.
So, what is not here and need tooling is marry the "normal tables" + "event log" + "logic that route this", so putting:
Data -> WAL -> Router -> Event Log | Tables | Index | ping external tools
And
Request Data -> Router -> Get it from:Event Log | Tables | Index | ping external tools
I'm not affiliated, not even a user, it just sounded interesting when it was submitted here.
PipelineDB excels at workloads where you know the queries you want to run ahead of time, where your workload fits within the confines of SQL, and where there is a high degree of distillation (aggregations, sliding windows, etc.). We have large customers including Charter Cable / Time Warner Cable, MediaMath, SmartNews, MOAT Analytics, Cradlepoint Networks, Paddy Power Betfair, and others using the system at scale and we offer a clustered edition of PipelineDB under a commercial license, but PipelineDB's single server edition is open-source.
We have a live chat room here for technical questions:
It is great that you mention that 90% of the time you want "the last version". That was one of my discoveries with event sourcing as well.
So what I came to is that re-computation (foldl) is a nice generalization, but isn't very practical. Instead, index the events and build the state on demand by querying the indices. This way, [plurality enabling] domain modelling moves to the code (as opposed to the database) and allows for quick changes in how you see the information.
You can read more about it at https://blog.eventsourcing.com/why-use-eventsourcing-databas... and https://blog.eventsourcing.com/lazy-event-sourcing-ed7e59007... as well as other articles on that blog.
The classical example is how avoid duplicated data or do validation across tables. One of the requirements of my app is deny a sale if the customer have pending invoices. The tricky part with invoice is that entry must be fast (I'm competing against DOS apps that fly at data-entry).
So when ES say "Record the event, then build the read side at later time", instead me (and I think, many of us) wanna the opposite "Put into the event log after we have do validations".
Of course, some actions can be delayed, but some need be as-now. How do this elegantly?
My initial attempt is code against the tables and use triggers for recording the story, but that is not as nice...
In lazy event sourcing, you don't "build the read side at a later time", it's built upon receipt — in a form of indices, as they are THE read side and you pull this data and form domain objects on demand (by querying your indices), just transforming one representation (relevant events) into another (domain objects).
Does this help?
And how you build the indices?
I saw Rich Hickey's talk about Datomic recently and immediately thought it would be ideal as part of a distributed runtime. Without having actually programmed in Erlang, but having seen plenty of Joe Armstrong's talks, I understand that the point of replicating data in messages rather than passing around pointers is so that if a machine dies, the data isn't lost. But if you built a distributed runtime that used Datomic as a distributed heap on which you allocate large amounts of data which you expect to pass around a lot, you could instead pass around only pointers to the data and not worry about data loss, and save big time on message throughput. Does anybody know if something like this already exists?
What monads allow you to do is have a purely functional model to represent IO, among a great many other things. Haskell is, in a sense, the best imperative programming language ever created, because it lets you manipulate imperative statements in a purely functional manner. You can stick statements in data structures, compose them, replicate them, generate them and only evaluate them when you want to.
But that's not the goal. Functional programming is there to provide a better formalism for writing programs, it doesn't want to change the way the computers actually execute the programs (or how the systems eventually internally work).
That means more work for the compilers to figure out how to make the same old imperative program out of a functional one, which is a good thing (it's generally the direction the compilers evolve).
Benefits of functional programming are not in how programs are executed (quite to the contrary - it's actually more effort to make them as efficient as good imperative programs), but in how the programs are written by people. I think the article is a little confused about this.
For another example to see this, even notion of non-pure function goes already in this direction. Today, having lots of (small) functions seems to have negligible downsides, but historically this wasn't the case at all, look at any better program that had to fit into memory of an 8-bit computer. These were rarely written in this style, because compilers were simply not capable enough to produce a reasonably compact spaghetti machine code out of those (by means of inlining, for instance).
Let me return to that last example. Take a program written in procedural style, and an identical program written in a big ball of mud style (all variables are global, one big control flow). This is a discussion from like 1960s. It's harder to write for human, and harder for compiler to compile and make efficient program in procedural style.
Yet, I am sure today, you would prefer procedural style. It makes sense (trust me, I actually work with big-ball-of-mud programs written 30+ years ago at work, AMA). I don't care what work the compiler has to do, I care about my own time.
Functional programming is just another step on that road.
As a side note, I really think people who prefer FP should avoid (over)selling it as more computationally efficient approach, because it still isn't (even though it probably will be in the future). It's a little bit of optimistic dishonesty that IMHO hurts adoption of FP and feeds the misconception that I talk about.
But what you said is true too.
It's important to distinguish between imperative code and IO. Many pure algorithms are most naturally expressed imperatively. Viewed in this way, Haskell is actually pretty bad at imperative programming.
Myers Diff is an example. See diffST2 at https://hackage.haskell.org/package/uni-util-2.3.0.1/docs/sr... for the Haskell version. Notice how simple expressions in Myers's paper, such as `V[k−1] < V[k+1]`, explode into ugly things:
vkplus <- readArray v (k+1)
vkminus <- readArray v (k-1)
(x,l0) <- if vkminus < vkplus...
Here we've paid a syntax tax for mixing the "imperative" array-read with the "pure" value comparison. Similarly, notice how the 10-line recursive `scan` is a less-readable implementation of a one-line while loop in the Myers paper.So in practice, Haskell is rather painful to use as an imperative programming language.
So you could write
case readIntArray# v (k +# 1#) realWorld# of (# vkp, _ #) ->
case readIntArray# v (k -# 1#) realWorld# of (# vkm, _ #) ->
case vkp <# vkm of
0# -> ...
1# -> ...
with syntax sugar reasonably close to V ! (k+1) < V ! (k-1).The problem is the state tokens; I ignored them by using realWorld# and _ above, but that makes the code not referentially transparent. So in practice, Haskell always passes them around in a monad (Except in all the core libraries, which use things like accursedUnutterablePerformIO to go back down to the thunk level: https://github.com/haskell/bytestring/blob/master/Data/ByteS...). But reads are (mostly) pure so it shouldn't break anything to provide a pure interface.
Alternatively, the verbosity can be seen as a syntax problem, with the corresponding solution of a better do-notation: https://www.reddit.com/r/haskell/comments/4dsb2b/thoughts_on...
so you'd get something like
(x,l0) <- if (<- v ! (k-1)) < (<- v ! (k+1)) ...
(again using ! for readArray, which has some impracticalities)That's still not quite as good as C, it really needs the notion of sequence points: https://en.wikipedia.org/wiki/Sequence_point
But in terms of I/O with the outside world, monadic patterns become useful in Haskell because the type of your main function is also `IO ()`. That's the "escape hatch", not the use of monads in itself.
no program can be purely functional
Pure functional programming means building up a machine. This machine will eventually be executed by the runtime system (at which point IO will happen) but from the programmer's perspective a program can still be purely functional.By this I mean that a function that takes an Int and outputs an Int will have type `Int -> Int`, and a function that takes an Int, does logging, and outputs an Int will have type `Int -> IO Int`. Both are purely functional -- they're pure transformations from an Int to some other thing. For the first function that other thing is another Int, for the second that other thing is an action that can be performed by the machine which will return an Int.
So from the perspective that matters to us a developers programs can be purely functional.
(This becomes even more useful with something like PureScript's granular effect tracking https://github.com/purescript/purescript/wiki/Differences-fr...)
Do-notation shows that there's a trivial mapping between monadic code and imperative code. For all practical intents and purposes, things happening in the IO Monad are not 'functional', because your mental model needs to consider the mutations they represent.
What functional programming encourages you to do (as opposed to OOP) is segregate the pure aspects of your program, under the theory that pure code is easier to write, maintain, and test.
Classic OOP encourages modeling your problem as a collection of inherently stateful objects, making effect segregation more difficult.
Of course, you can write in a functional style in Java, or an imperative OOP style in Haskell, but the language isn't going to help you as much.
Pure object-orientation is a bad idea for other reasons (e.g., the binary method problem), but “inability to control effects” is totally bullshit.
Functional programming is a paradigm, and it emphasizes the segregation of effects. OOP traditionally does not.
(0) Functional programming is a paradigm that encourages expressing computation as expression evaluation, and discourages relying on more computational effects than are necessary to obtain a desired result.
(1) Object-oriented programming is a paradigm that discourages directly manipulating data structures from different parts of a program. Objects expose operations that act on a data structure hidden from the rest of the program.
(2) Effect segregation is a feature of some programming languages, notably Haskell and its descendants. An effect system classifies computations according to their possible effects, just like a type system classifies computations according to their possible results. You wouldn't call static typing a paradigm, right?
The fact still stands that it's closely aligned with the functional programming paradigm, and not OOP. Programing paradigms and language features are closely related: certain features make certain paradigms easier, and paradigms spur the development of particular features.
I agree with the point that a program can't be purely functional, but lately I've been wondering if you could still have a programming language that is purely functional (preferably also total). It is designed for writing data transformation libraries and needs to be called into by a stateful language that handles IO and orchestration. If you've seen Gary Bernhardt's talk Boundariers [1], this language would be the purely functional core.
There are functional options for almost all of the big ecosystems now.
A lot of FP practitioners tend to come from academia and most of the research currently lies in provability, so a lot of the FP community seems interested in strongly typed languages, because they overlap the language research communities, but I doubt anyone in the community would claim they are more tuned to FP, because that's just a lie. In fact, a lot of the challenge of typed FP languages is designing a type system powerful enough to express all types of functions, so they're often more restrictive in how they can implement FP.
Records, for example.
Functional and imperative are paradigms that can "encode" the other, so to speak.
Wouldn't "Dirty sea of state" be more appropriate?
You so not need to have pure code to have functional code. You don't even need to have pure code to use a monadic approach.
That's just not true in our domain. While it may be true in some categories, we do not program in an abstract category. In our domain, "pure" functions are distinct enough that we call them "pure" functions and even in Haskell we draw a line between effectful code and pure code. As a core concept in modern programming, the word "function" makes no judgement about how many side effects are there.
I'm not sure why people think otherwise. Or perhaps all Lisp and OCaml programmers are just liars and cheats.
EDIT: I just had a long conversation twitter about this and I'd like to offer an example of a functional style we commonly use even with side effects: tail recursive loops. We often use this modeling technique even in handling I/O because it dramatically simplifies the logic for consuming a stream.
I think there's some naming confusion here. That's because programmers overloaded the original meaning of "function" so that it's equivalent to "procedure". So now when something behaves like an actual mathematical function, we have to explicitly call it "pure".
PS. i remember seeing you in some Yogscast videos, used to be a huge fan of those. funny to meet you like this.
Every single concept is overloaded.
P.S., Wow. Someone here who knew about my hobby? I figured it'd never happen.
Of course they don't. But they can still be considered a useful model for programming in the same sense that Newtonian physics is a useful model of our world.
The article in question acts like it is a big surprise gotcha that functional programs have imperative elements. This shouldn't be a surprise to anyone and in fact it's a feature! The accessibility of that feature is often a major component of what languages are judged on.
This isn't a point of semantics, it's definitely the case that we use functional techniques in an imperative paradigm to great success.
It's not that functional programs have imperative elements or not. That's kind of besides the point.
The point is that putting functional components into workflows that deal with arbitrarily mutable data is reducing the potential we have with the functional model.
The article is simply an open-ended invitation for a discussion on how to do this (and it spark a little bit of that conversation) with some of the proposed options on the same blog.
I think your position is that because there is an escape hatch in languages like Haskell, that negates the usefulness of equational reasoning. I'll argue this isn't true, in the same way it's useful to pretend ⊥ doesn't exist and that we don't bork our unbound recurison or whatnot. Once we do that, we now can reason about our functions as if they exist as arrows in a cartesian closed category (of types). This is incredibly useful. We just have to restrict ourselves to a subset of the language. We still get that same benefit even if part of our program call impure functions. This is the reason we want to model as many side effects as we can in the types (an IO monad or Task in Scala).
I saw your twitter convo, I don't know what you mean by this:
>"If we define functions as pure, then functional programming doesn't exist outside of extreme specialist applications."
All kinds of Haskell applications are built with side effects. Are you under the impression UnsafePerformIO is the only way to interact with the world in Haskell?
The article is not that interesting in itself, but the conclusion is a good starting point for a conversation that may be.
> Systems are largely not functional. But they can be.
So, how?
My current view is that by eliminating or reducing the need to serialize domain objects, we're creating a system which is easier to argue about and provides an easy way to adjust domain models as the understanding or circumstances evolve.
I too have been mildly obsessed with the kind of thinking you're developing in your post - very nice article!
I've been very attracted to event-sourcing since I realized that now that React and redux have mostly solved the UI problem what remains to be solved is the problem of how to store our beautiful event-driven data.
EDIT: re-read your comment, felt the need to say that your "current view" is exactly mine too
I know this thread is about functional programming and so condescension is the norm, but isn't it about time we change that part of our culture a bit?
You offer significantly fewer insights than the OP. Zero arguments, just name calling. The author reads this thread and is a real person. Are you proud?
I, for one, found the article an insightful articulation of things I kinda sorta already knew but hadn't acted on. Also our team is in the middle of building an Elixir backend with a classically designed mutable DB so the article helps me reflect. Maybe now is the better time to change this.
Moving off of Elixir certainly isn't being considered :-) We chose it particularly for its concurrency and robustness features, which are important to us (we're a messaging component for marketplace/platform sites so deliverability and concurrency are very high on the list). We'd have picked Erlang if Elixir didn't exist.
You can read more about it at https://blog.eventsourcing.com/lazy-event-sourcing-ed7e59007... and https://blog.eventsourcing.com/why-use-eventsourcing-databas...
If you have any thoughts or questions, feel free to reach out to me (my nickname is the same almost anywhere, twitter, gmail, etc.) or Eventsourcing gitter room http://gitter.im/eventsourcing/eventsourcing and I'll be happy to talk about these things.
After a bit of consideration, I figured F# makes more sense for me. It has a large ecosystem covering many technologies, its all moving to open source / any platform. It also has lightweight processes, and lots of tooling.