Rationale
It can be difficult to manage resource lifetimes, especially when laziness is involved. Simply local scoping can be inadequate. Resource scopes can decouple resource lifetimes from creating scope.
Plan
- Generalize from resource scopes to general scopes (succeed/fail/exit)
- with-open et al make scopes
- Some design work and code has been done
- find it and write it up
- scope work in streams branch
Use Cases
- REPL usage: open stream and it closes before I lazily consume it. Doh!
- Start an activity, and then have nested resource lifetime automatically tie to it
- Task A calls Iib B which allocates resources
- Prevent resource lifetimes from nesting
- lib C knows it doesn't want its lifetime governed by Task A
- All works across threads
Problems
- Other than memory allocation, the proper freeing of resources (e.g. closing open files) is on the user
- thus, easy to get wrong
- 'He who acquires releases' is one strategy
- can be automated, e.g. with-xxx
- but with-xxx puts an envelope around the availability of the resource, tied to the acquirer
- we trip over this when we create and return lazy seqs that reference resources
- anywhere else?, could we limit the scope of the solution to lazy seqs?
- even then, needs care
- e.g. closing on seq exhaustion not triggered by partial consumption
- can be automated, e.g. with-xxx
- piggybacking on GC gets around this
- i.e. finalizers
- but is bad, because we can't know when/if they will run
- non-option?
- reference counting works in some langs
- but not on the JVM where aliasing is common and undetectable
- is it possible to wrap resources with a counter/tracker?
- open question
- can tag things as closable and call close everywhere defensively
- but everywhere is a big and dangerous place
- Fundamentally - how do you know when something is a 'resource'
- and how do you know when no one cares about it anymore?
- and what does it mean to care?
- could just fold down to - care means would be unhappy if closed
- and what needs to be done about it when that is the case?
- who knows what that is?
- and how do you know when no one cares about it anymore?
- Is the problem as simple as closing?
- are there ever two paths?
- e.g. happy path saves and unhappy path discards
- are there ever two paths?
- are there any similarities to exception handling?
- one part of code has problem, another deals with it
Issues
- Consider threads as well as nesting
- How do scopes interact with io! and stm
- Can we hydrate the fork/join tree where we need it?
- How to prevent mess?
- (when-scope :fails ...) et al are very declarative
- but are setting up non-local effects
- potentially leading to wtf when triggered
- is this the right external API?
- any way to ensure correctness
- will we be beset with requests for order control
- Is the nearest enclosing scope always the right one?
- what to do if not
- named scopes
- scopes as args
- what to do if not
Labels: