4 ms·
Does anyone know of an example of a non-trivial codebase written in this style? How do you do this cleanly when, e.g., you need to make a network call and then
by lackbeard 8y ago
Does anyone know of an example of a non-trivial codebase written in this style?
How do you do this cleanly when, e.g., you need to make a network call and then based on what the result returned to you is, either do something with a local database, make a different network call, or return a result to your user. Also, error handling...
It seems to me like monads must be the logical conclusion to this style of programming, or else you wind up with a mess (or just abandoning this technique.)
- _greim_ 8y agoThe Elm architecture seems to use pretty much this exact pattern. The imperative shell parts are all buried within the Elm runtime, and you contribute the functional bits. Redux also to a lesser extent.
- arwhatever 8y agoHave been using Elm architecture for a while, and also have pushed my code toward an immutable core/mutable shell architecture since watching Vladimir Khorikov's video on the subject at Pluralsight, but had never considered how the two are interrelated. Thank you for the big light bulb lighting up in my head just now.
- dnautics 8y agoBasically anything written in erlang or elixir follows this paradigm. It's really extremely productive to code this way... I wrote a job scheduler from scratch in 3 months and never once had to write or use a mutex or semaphore. Immutability makes you very confident about your code. Similarly, my UI guy wrote a UI in basically functional react. It's amazing. With very little js experience I made code patches that.. just worked because I was guaranteed that no function calls had mysterious side effects...
- lackbeard 8y agoKnow of any good open-source examples?
- PhearTheCeal 8y agoRabbitMQ
- dnautics 8y agoRiak? Couchdb? I know there's a lot of Phoenix examples out there but it obscures some of the key points in fp. I'd show you my code, but it's closed source atm. For a fp react example: https://github.com/streamproject/cryptopotamus-web https://github.com/streamproject/cryptopotamus-web
- mmartinson 8y agoAgree that Elixir and Erlang lend themselves well to this style, but I strongly disagree that everything written in them does or that the languages help enforce this pattern in any way. It's quiet the opposite really from what I've seen. Elixir places absolutely no constrains on when and how IO happens, and provides extremely useful primitives for shutting state between (VM) processes in otherwise stateless code. A library function that looks totally pure could, for example, boot an entirely different subsystem that fired a missile into the sun before providing a return value and you'd never know it if you didn't read the docs, or use one of many pieces of fantastic beam tooling to inspect the runtime state of the system. This is part of what makes these languages pragmatic to work in. There are foot guns everywhere, but the VM ensures you sign into the foot gun registry whenever you use them.
- pwm 8y agoThe goal is that everything in the core is pure data types and functions. Now, depending on your problem domain, your core can be small or huge compared to the shell. Your question was all about IO/effects. They are impure and belong to the shell. If what you work on does mostly IO then you will have a lot of impure code lying around by definition. Still, the idea is that you should keep the amount of time spent in impure land minimal, ie. drop into pure structures/computation as soon as possible during program execution and only dip out of on occasions when you have to do IO. Here is a good talk about these ideas: https://www.youtube.com/watch?v=US8QG9I1XW0 https://www.youtube.com/watch?v=US8QG9I1XW0
- lackbeard 8y agoI guess what I'm getting at is that in any non-trivial program with many external dependencies and cross-cutting concerns, almost all of your code is the "shell", so either I'm missing something, or this is stating the obvious (write pure functions where you can) in a very roundabout way. I've never seen a program written explicitly in this style that wasn't a trivial example.
- ramchip 8y agoThe functional core can return a symbolic description of actions to take, which the shell executes, a bit like a simple interpreter. This is a good example: https://www.theerlangelist.com/article/spawn_or_not https://www.theerlangelist.com/article/spawn_or_not (Note the context is Elixir, so it’s talking about lightweight processes, not OS processes. It explains how to keep that stateful / effectful code very simple, and have all the real logic be pure functional code.)
- yen223 8y agoI think it's more that a lot of programs aren't actually doing anything terribly complex, logic-wise. A lot of apps out there do nothing more than fetching data from one service, and dumping it out to another.
- pwm 8y agoThe system I've been working on in the past 6 months is non-trivial and written in this style. Roughly 2/3 of the code is in core and 1/3 is in shell.
- justinpombrio 8y agoLiterally anything written in Haskell. When you write stateful code in Haskell (such as code that reads from a database, makes a network call, or does IO, to use your examples), that code will be wrapped in a Monad. Thus the type signature of the code shows that it's stateful code, and Haskell's type checker will ensure that any code that calls it also has a stateful type. For example, if a function takes in an `Int` and returns an `Int`, and along the way may directly or indirectly perform IO, that function will have type `Int -> IO Int`. Now of course, you don't have to cleanly separate a functional core from a stateful shell. But if you don't, all of your code is going to end up wrapped in nested Monads declaring all of the ways that it's inadvertently stateful, and that's a very painful way to program. So Haskell pushes you strongly towards having a functional core and stateful shell. For projects written in Haskell, the wiki has a long list: https://wiki.haskell.org/Haskell_in_industry https://wiki.haskell.org/Haskell_in_industry
- smadge 8y agoI agree, but I think it still takes some coding self-discipline to write code with a functional core and stateful shell in Haskell. There’s little stopping you from having every return type wrapped in the IO monad. It’s not any more unnatural to do that than it is to code in any imperative language.
- justinpombrio 8y ago> It’s not any more unnatural to do that than it is to code in any imperative language. Yeah, I guess the `do` notation makes it pretty painless. If you start mixing monads, though, things get hairy quickly.
- waluigi 8y agoMonad Transformers aren't _that_ bad, the MTL style of doing things makes it all pretty painless. It also provides a huge opportunity for testing. At a very high level, you describe all of your effects as a series of embedded, compostable DSLs that you define interpreters for. The awesome part is that you can switch out the interpreters at will, so you can, for example, replace something that handles network requests with something that returns dummy data almost effortlessly.
- kellysutton 8y agoWe use this pattern in our Ruby apps to safely move $1B+/month at Gusto. You don’t need full blown monads to do this, just need to be cognizant to how you separate the what (functional core) from the how (imperative shell). I recommend giving it a try!
- arianvanp 8y agoXmonad is written according to this pattern Most of the code (Layout.hs and StackSet.hs) is pure and has extensive unit (property-based) tests for all the pure functional core. https://github.com/xmonad/xmonad/tree/master/tests/Properties https://github.com/xmonad/xmonad/tree/master/tests/Propertie... Then there is a layer that talks to X11 and interfaces with the functional core. (Core.hs and Main.hs) https://github.com/xmonad/xmonad https://github.com/xmonad/xmonad
- charlieflowers 8y agoI am a fan, but you're right, it can get awkward. The good part is the set of pure functions that take input and compute something from it. But the awkward part (assuming you're coding in a typical mainstream imperative language) is the "transaction script" that gets an input, passes it to the pure functions, and then takes the result and writes it out. If there's no event loop and you're not using promises or something like that, it forces an awkward boundary right down the middle of your code that just feels unnatural. Still, I think it's worth it for many problems. The vast majority of your code is pure functions -- simple, understandable, testable. The price you pay is this unnatural seam at each point of IO.
- vmchale 8y ago> It seems to me like monads must be the logical conclusion to this style of programming, or else you wind up with a mess (or just abandoning this technique.) Monads are nice, but there's a lot of work on algebraic effects/effects in general which may pan out into something useful (and more general).