tag:blogger.com,1999:blog-8068466035875589791.post6576036560263449221..comments2024-03-18T21:47:40.045-07:00Comments on Semantic Domain: HOPE 2016 Talk SummariesNeel Krishnaswamihttp://www.blogger.com/profile/06853898957395028131noreply@blogger.comBlogger2125tag:blogger.com,1999:blog-8068466035875589791.post-405710676316121892016-09-18T09:42:51.390-07:002016-09-18T09:42:51.390-07:00Oh it seems I forgot to mention the other closure ...Oh it seems I forgot to mention the other closure types at all. `FnMut` and `FnOnce` closures (with unique but temporary access to their environment, and ownership of their environment, respectively) would be able to copy/clone (and in the case of `FnOnce`, move) the `IO` capability, and therefore call functions which do I/O.glaebhoerlhttps://www.blogger.com/profile/16820759981849420314noreply@blogger.comtag:blogger.com,1999:blog-8068466035875589791.post-56642437798253435952016-09-18T08:39:38.067-07:002016-09-18T08:39:38.067-07:00I had wanted to propose a similar scheme to "...I had wanted to propose a similar scheme to "Effects via capabilities" for Rust at some point before 1.0. <br />(I'm not sure how familiar you are with Rust's type system?)<br /><br />The basic idea was the same: an `IO` capability (zero-sized type) that needs to be passed into any function that wants to do IO. The next question was likewise "what about closures capturing it", here the idea was to exploit the different closure types Rust already has (or had, by that point): `IO` would be affine (non-`Copy`), so `&Fn` closures (with a shared, immutable environment) could not move it out from their environment, so all `&Fn` and `fn` (functions without an environment) could be known to be pure except if they took an `IO` capability explicitly as an argument. Then one option would be to just require threading the `IO` capability through linearly everywhere (like the IO monad, or Clean), but another idea I had was to allowing cloning/copying the `IO` capability through (unique) `&mut` references (but not `&`) -- so if an IO-doing function wants to call another IO-doing function, it can make a copy of the IO cap to call it with, instead of having to expect getting it back in the return value, while still preserving the purity of `fn`s and `&Fn`s. In fact you'd probably want this to happen implicitly, at least in the "obvious" cases, to not regress (too much) on ergonomics.<br /><br />This also would've allowed neat things like, for instance, the `File` type could store an `IO` capability internally (but not allow retrieving it externally) and only use it for accessing that particular file, so if an otherwise-pure function has an `&mut File` parameter, you can infer that the function may do I/O on that file, but not any other I/O. So the `File` itself would be a kind of capability, in effect.<br /><br />(Another interesting thought that's occurred to me since then. My original idea was that the `Cell` and `RefCell` types, which allow mutation through `&` references (that is, shared mutability -- analogous to `IORef` in Haskell) would simply have to require an `IO` capability for their methods (much like how `IORef`'s methods are in the `IO` monad), again to preserve the purity of `fn` and `&Fn`. But this is slightly unsatisfying: if a function creates a `Cell` or `RefCell` locally, on the stack, it and its callees can mutate it all they want, and the caller will be none the wiser (this is like the `ST` monad). So requiring the global `IO` capability to mutate a local `Cell` seems too coarse-grained. Maybe the `IO` capability could have a lifetime parameter? And then `IO<'static>` would correspond to the earlier "plain" `IO` type, being the capability for *globally* observable shared mutable state (which includes any "external" I/O like files or whatever), and you could call the methods of a local `Cell` with any `IO<'a>` where `'a` encompasses its lifetime. But I haven't really thought through the details here.)<br /><br />glaebhoerlhttps://www.blogger.com/profile/16820759981849420314noreply@blogger.com