Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
@danielt1263

This comment has been minimized.

Copy link

@danielt1263 danielt1263 commented Apr 29, 2019

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

@olleharstedt

This comment has been minimized.

Copy link

@olleharstedt olleharstedt commented Sep 7, 2020

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

@maletor

This comment has been minimized.

Copy link

@maletor maletor commented Sep 19, 2020

You mean Haskell?

@olleharstedt

This comment has been minimized.

Copy link

@olleharstedt olleharstedt commented Sep 21, 2020

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

This comment has been minimized.

Copy link

@maletor 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

This comment has been minimized.

Copy link

@olleharstedt 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

This comment has been minimized.

Copy link

@maletor 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

This comment has been minimized.

Copy link
Owner Author

@kbilsted kbilsted commented Sep 24, 2020

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

This comment has been minimized.

Copy link

@olleharstedt 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

This comment has been minimized.

Copy link

@maletor 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

This comment has been minimized.

Copy link
Owner Author

@kbilsted kbilsted commented Sep 24, 2020

@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

This comment has been minimized.

Copy link
Owner Author

@kbilsted kbilsted commented Sep 24, 2020

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

This comment has been minimized.

Copy link

@olleharstedt olleharstedt commented Sep 24, 2020

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

@olleharstedt

This comment has been minimized.

Copy link

@olleharstedt olleharstedt commented Sep 24, 2020

(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

This comment has been minimized.

Copy link

@olleharstedt olleharstedt commented Sep 24, 2020

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

This comment has been minimized.

Copy link

@olleharstedt olleharstedt commented Sep 24, 2020

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