Resource - Safe and composable way to handle "resources"

Elixir’s kernel has Stream.resource/3 function, that acts “resource-like” (it defines resource acquiring, usage and releasing phases). As a “resource-like” facility it also guarantees resource releasing once it was acquired (no matter what usage phase returns or even raises). However, it biased toward “stream-like” resources (as the name of the module suggests). So every time I want to safely handle a resource in my code, I found myself adopting Stream.resource/3 for “singleton-resource”.

E.g. in case of WithTimeout (also got a post about it) local task supervisor is spawned under the hood, so we need to ensure it’s properly released (no matter what happens in between). And adopting Stream.resource/3 for non-stream-like-resource could be quite cumbersome.

So I also decided to extract this repeatable pattern into separate library.

While doing so I’ve came across a noticeable use case — handling several resources. Exposing some kind of @spec use_all!(list(resource(a)), (list(a) -> b)) :: b when a: var, b: var felt unnatural. On the other hand, when you found yourself “flat-mapping” your data types (Resource.use!(r1, fn a -> Resource.use(r2, fn b -> ... end) end)), it is the monadic glimpse whispering you. So long story short, I’ve equipped the library with for-comprehension batteries to be able to compose resources (I exported general purpose for-comprehension from Bindable).

As a disclaimer: there is no reasonable filtering semantics for Resource (so there is no Bindable.Empty implementation for Resource, that is why if is not available for Resource inside for-comprehension).

At the end of the day, the library exposes composable facility for safe resource handling.

Resource (hex)
Resource (GitHub)

5 Likes

Some great concerns on Stream.resource/3 from hissssst