Skip to content

Instantly share code, notes, and snippets.

@kbilsted
Last active November 13, 2022 00:38
Show Gist options
  • Save kbilsted/abdc017858cad68c3e7926b03646554e to your computer and use it in GitHub Desktop.
Save kbilsted/abdc017858cad68c3e7926b03646554e to your computer and use it in GitHub Desktop.

1. Separation of immutable and mutable logic

Quite a lot of different people have been on the same trail of thought. Gary Bernhardt's formulation of a "functional core, imperative shell" seems to be the most voiced.

"Boundaries" - Gary Bernhardt

"Imperative shell" that wraps and uses your "functional core".. The result of this is that the shell has fewer paths, but more dependencies. The core contains no dependencies, but encapsulates the different logic paths. So we’re encapsulating dependencies on one side, and business logic on the other side. Or put another way, the way to figure out the separation is by doing as much as you can without mutation, and then encapsulating the mutation separately. Functional core — Many fast unit tests. Imperative shell — Few integration tests

https://www.youtube.com/watch?v=yTkzNHF6rMs
https://www.destroyallsoftware.com/talks/boundaries
https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell

There are only two roles of code - John Sonmez

All code can be classified into two distinct roles; code that does work (algorithms) and code that coordinates work (coordinators).
https://simpleprogrammer.com/2012/10/21/there-are-only-two-roles-of-code/

Object layer, valuelayer - Andy Matuschak

Great talk, but specifically on the separation of side-effects at the at the 27 minutes 53 seconds mark.
https://realm.io/news/andy-matuschak-controlling-complexity/

A Modern Architecture for FP - John A De Goes

Interpreters of the functional core
http://degoes.net/articles/modern-fp http://degoes.net/articles/modern-fp-part-2

Hexagonal architecture - Alistair Cockburn

Hexagonal Architecture is an architecture defined by establishing a perimeter around the domain of your application and establishing adapters for input/output interactions. By establishing this isolation layer, the application becomes unaware of the nature of the things it's interacting with.

Create your application to work without either a UI or a database so you can run automated regression-tests against the application, work when the database becomes unavailable, and link applications together without any user involvement.

http://alistair.cockburn.us/Hexagonal+architecture
https://github.com/jschairb/sandbox/wiki/HexagonalArchitecture

2. Test isolation - nomock

Growing Object-Oriented Software, Guided by Tests Without Mocks - Vladimir Khorikov
http://enterprisecraftsmanship.com/2016/07/05/growing-object-oriented-software-guided-by-tests-without-mocks/

Test Isolation Is About Avoiding Mocks - Gary Bernhardt
https://www.destroyallsoftware.com/blog/2014/test-isolation-is-about-avoiding-mocks

To Kill a Mockingtest - Ken Scambler
http://rea.tech/to-kill-a-mockingtest/

Mocking is Tautological - Mark Sands
http://marksands.github.io/2014/05/14/mocking-is-tautological.html

3. Blogs on Functional core, Imperative shell

"Functional core & Imperative shell" : OO design, and isolated tests without mocks
http://blogs.perl.org/users/mascip/2014/06/functional-core-imperative-shell-oo-design-and-isolated-tests-without-mocks.html
http://blogs.perl.org/users/mascip/2014/06/functional-core-imperative-shell-explanation-with-code.html

Business-Friendly Functional Programming, Part 2: Testability - Yang Bo
http://rea.tech/business-friendly-functional-programming-part-2-testability/

mokacoding - unit and acceptance testing, automation, productivity
http://www.mokacoding.com/blog/functional-core-reactive-shell/

Functional Core, Imperative Shell - Sean Hammond
http://www.seanh.cc/posts/functional-core-imperative-shell

BoochTek, LLC
http://blog.boochtek.com/category/programming/architecture

Slides/books

Functional Programming in Ruby - Vitor Capela
https://speakerdeck.com/dodecaphonic/functional-programming-in-ruby

Enemy of the State by Justin Spahr-Summers
https://speakerdeck.com/jspahrsummers/enemy-of-the-state

Functional Programming in Swift - slide 55+
http://www.slideshare.net/SaugatGautam2/functional-programming-in-swift

Test-Driven Development with Python - Harry Percival
https://books.google.dk/books?id=fTLJAwAAQBAJ&pg=PA403&lpg=PA403&dq=%22functional+core%22+%22imperative+shell%22&source=bl&ots=A4g25w7wWY&sig=x0bu4NZWiXrpzWPziAH7Y78AKgM&hl=en&sa=X&ved=0ahUKEwi0jq3em-zPAhUqG5oKHYNJBIE4ChDoAQhRMAk#v=onepage&q=%22functional%20core%22%20%22imperative%20shell%22&f=false

Quotes...

programming, when stripped of all its circumstantial irrelevancies,
boils down to no more and no less than very effective thinking so as to avoid unmastered complexity,
to very vigorous separation of your many different concerns.
-- Dijkstra EDW512
https://www.cs.utexas.edu/users/EWD/transcriptions/EWD05xx/EWD512.html

@danielt1263
Copy link

Another talk for the Test Isolation section:
Test Doubles Are a Scam - Matt Diephouse https://www.youtube.com/watch?v=7AGQ9dhWCX0

@olleharstedt
Copy link

Is it possible to design a framework to enforce this pattern?

@maletor
Copy link

maletor commented Sep 19, 2020

You mean Haskell?

@olleharstedt
Copy link

You mean Haskell?

Haskell does in fact not enforce this design pattern. It's just pure discipline from the programmer's side to not put IO monad everywhere.

@maletor
Copy link

maletor commented Sep 23, 2020

You know what, that's a good point. At the end of the day there isn't too much "effectful" code:

  • Net/HTTP/Socket I/O
  • File I/O
  • Random I/O
  • Logging

Maybe a few others. It really seems like it wouldn't be too hard to isolate these at the top. The real challenge is keeping a handle on the imperative code at the top when your pure code depends on outputs during the intermediary steps, because it inevitably will.

@olleharstedt
Copy link

olleharstedt commented Sep 24, 2020

The real challenge is keeping a handle on the imperative code at the top when your pure code depends on outputs during the intermediary steps, because it inevitably will.

Yeah, this is the issue. :) Whenever you do IO, you might need to check if it was successful, for example, and then take different action depending on the result. That's why you can't just gather all IO in command objects and do them "later", or after the pure code is done. I had an idea about "stage-oriented programming", where each stage is a read-process-write pipeline, and you put any number of those after each other. The "process" stage will always be pure this way. I've outlined another alternative here, where generators/yield are used to bubble side-effects up, wrapped in promises (PHP): https://old.reddit.com/r/PHP/comments/ifnrvu/effect_types_in_php_using_psalm_and_amphp/

/**
 * @param string $path Remote file location
 * @param int $file Binary blob
 * @return Generator<WriteIO|HttpIO>
 * @throws Exception
 * (If you change the return type, Psalm won't compile.)
 */
public function saveFileToCloud(string $path, int $file): generator
{
    yield $this->writer->write('Saving file ' . $path);  // Yield WriteIO

    $request = new Request('https://google.com', 'POST');
    $request->setBody($file);

    $response = yield $this->http->request($request);  // Yield HttpIO (like curl)

    if ($response->getStatus() === 200) {
        yield $this->writer->write('Successfully saved file ' . $path);
    } else {
        $msg = 'Failed to save file ' . $path;
        yield $this->writer->write($msg);
        throw new Exception($msg);  // Exceptions are not part of the effect types (but could be in another type system)
    }
}

But as for the IO monad, this does not enforce functional core.

@maletor
Copy link

maletor commented Sep 24, 2020

@olleharstedt, check this out: effects-as-data.

It's linked from redux-saga. Whereas redux is typically thought of as client side concern, it can make sense in a backend (within request response loop). And redux-saga looks like a great pattern, using generators to pause execution and handle side effects separate from data and declare these interpreters.

I think we "figured it out" relatively quickly. Great chatting with you. There will, of course, always be more to learn.

@kbilsted
Copy link
Author

Please elaborate on exception handling in these frameworks and interpreter implementations.. it seems when you go down the interpreter route you have to implement a lot of details, say exceptions!

@olleharstedt
Copy link

olleharstedt commented Sep 24, 2020

@kbilsted Both exception handling and division by zero is not dealt with here, I think. You can check out experimental programming languages like Koka and F* that deal with this properly. Again, this does not enforce functional core, only more side-effect discipline. It Koka, they mark a difference between total and pure functions, the latter allowing both exceptions and division by zero.

@maletor
Copy link

maletor commented Sep 24, 2020

I encourage you to read about exception handling. There's nothing much to it.

https://redux-saga.js.org/docs/basics/ErrorHandling.html

Then you could put errors on the so called "root" as well.

Like I said, nothing much to it. The generator pattern is powerful.

@kbilsted
Copy link
Author

@maletor thanks, essentially you just removed the ability to throw an exception one place and catch in another by marking it an explicit codepath which you need to cater for all the way from your catch-statement down to your throw statement.

@kbilsted
Copy link
Author

should we convert part of this debate into text somehow? Should I convert this gist to a repo so we can grow this resource together?

@olleharstedt
Copy link

(You also get concurrency for free with generators - by yielding an array of promises: https://amphp.org/)

@olleharstedt
Copy link

(And another aspect with effects-as-promises is that you can mock the effects instead of the dependencies when designing your tests. The downside being that you bind the test more tightly to your implementation. But there are several upsides as well - e.g. testing only certain types of effects, being able to ignore all effects, and less mocking code.)

@olleharstedt
Copy link

should we convert part of this debate into text somehow? Should I convert this gist to a repo so we can grow this resource together?

Dunno. The pattern would have to be tried out in a bigger application, I think, before knowing if it's applicable.

@olleharstedt
Copy link

Oh, you already made a repo. Nice. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment