85 lines
3.9 KiB
Markdown
85 lines
3.9 KiB
Markdown
tl;dr: I'm thinking of dropping finalizers from Conduit. If you have
|
|
use cases that necessitate finalizers, __please share them with
|
|
me__. [Github issue for examples.](https://github.com/snoyberg/conduit/issues/343)
|
|
|
|
People who have followed the history of the Conduit library will
|
|
likely be aware of how passionately I've argued for the necessity of a
|
|
finalizer concept. For those unaware: when you have a Conduit pipeline
|
|
like the following:
|
|
|
|
```haskell
|
|
runConduitRes $ do
|
|
sourceFile input .| takeCE 100 .| sinkFile output
|
|
-- lots of other stuff
|
|
```
|
|
|
|
Without finalizers, the file `input` will remain open until we exit
|
|
the `runConduitRes` call (unless the file is less than 100 bytes). The
|
|
reason is that, in a pipeline, downstream is always in control. When
|
|
`takeCE` stops consuming from `sourceFile`, `sourceFile` will never be
|
|
called again. Finalizers allow `sourceFile` to tell downstream "hey,
|
|
if you don't call me back, please close up my file." And so they're a
|
|
Good Thing.
|
|
|
|
They're also a Complicated Thing. The internals of Conduit include a
|
|
lot of complexity around finalizers. As a user, you hardly ever see
|
|
it, which is by design. But if you start dealing with the guts of
|
|
Conduit, it's there. (Have a look at
|
|
[this commit](https://github.com/snoyberg/conduit/commit/e7f0cb77987a23c3d259e413efa33f45f7069f79)
|
|
to get an idea.)
|
|
|
|
They also complicate the story around associativity of Conduit
|
|
significantly. Or more to the point: without finalizers, we can get
|
|
properly behaving associative and identity laws\*. With finalizers, we
|
|
have to start making up claims about reordering of finalizers. Which
|
|
is almost always fine in practice, but clearly not a mathematical law.
|
|
|
|
\* Caveats: I've never written the proof of it completely. Also, it
|
|
relies on using the type paramter on the `Pipe` type to eliminate
|
|
leftovers, but leftovers are not a topic I'm raising right now.
|
|
|
|
None of this is new information; so why am I writing this blog post
|
|
now? The first is that I'm already working on a breaking change to
|
|
Conduit to standardize naming and eliminate some legacy type and
|
|
operator names. See
|
|
[the discussion](https://github.com/snoyberg/conduit/issues/283) and
|
|
[initial comparison](https://github.com/snoyberg/conduit/pull/338/files)
|
|
for more details. This naturally leads me to ask related questions.
|
|
|
|
More to the point: after having worked with Conduit for years, my
|
|
initial concerns about prompt finalization seem to have been
|
|
overzealous. While the code above _can_ happen, it doesn't happen that
|
|
often in practice, and even that level of resource overholding isn't
|
|
usually that bad. Regardless, if the situation really does call for
|
|
guaranteed promptness, we can still get it (a trick I'm fairly certain
|
|
I learned from Gabriel Gonzalez):
|
|
|
|
```haskell
|
|
runConduitRes $ do
|
|
withSourceFileResource input $ \src ->
|
|
src .| takeCE 100 .| sinkFile output
|
|
-- lots of other stuff
|
|
```
|
|
|
|
It's not quite as elegant, but works well enough. And again: I'm hard
|
|
pressed to think of real life code I've written that would really
|
|
warrant this. __BIG REQUEST__ If you have examples of Conduit-using
|
|
code where finalizers are vital, please send me examples.
|
|
|
|
While on the one hand, making this change would be to admit I've
|
|
sucked up huge amounts of extra work in maintaining Conduit over the
|
|
years, I'd be very happy to cut the dead weight now, unless someone
|
|
out there wants to convince me otherwise. Feel free to discuss
|
|
wherever desired, but the main discussion I'll be sure to follow will
|
|
be
|
|
[conduit issue #343](https://github.com/snoyberg/conduit/issues/343).
|
|
|
|
There are some natural follow-on questions that come from this also,
|
|
which I'll likely broach in a later blog post. To give a taste and
|
|
hopefully encourage some thoughts from others:
|
|
|
|
* Should the `Pipe` types upstream finalizer concept disappear as
|
|
well?
|
|
* Can we remove the `Leftover` data constructor from `Pipe`, making
|
|
`Pipe` a full category, and then move the tracking of leftovers to
|
|
`ConduitM` and its codensity approach?
|