`Pull`'s public api is unsound
Because of Translate we may only G ~> F, not F ~> G. However GetScope and any operator that uses GetScope is subject to a soundness hole since:
-
Scopeis allocated in the initialF. -
Scopecannot havemapKsince it must be invariant inF(acquisition of child resources).
Here is an example that throws a class cast exception.
I have been looking at it for a while and I can't see any other road to a sound structure than to require F ~> G if any interaction with the open resources in necessary.
The good news is that either more stream programs can be expressed with F ~> G or at least the current semantics can be retained explicitly.
new FunctionK[F, G] {
def apply[A](fa: F[A]): G[A] = throw new ClassCastException("Oh no, couldn't go from F to G")
}
Assuming the api is used without throwing of exceptions, a bi-directional Translate allows Scope to be exposed (or at-least a subset of Scope's operators).
As of now, the only part of the api which is exposed and subject to this issue is lease. Fortunately lease doesn't invoke any other effects than ones in Scope, so any lossy F ~> G is completely fine, say EitherT[F, String, *] ~> F. Maybe a typeclass to represent non-structure preserving translations to make the G ~> F derivable for most cases?
Another direction could be to ensure that any Pull that is built on GetScope is non-translatable. Although this might require an extra type parameter to Pull and consequently Stream unfortunately.
@mpilquist could you assign me for this issue