1. 12
  1.  

  2. 10

    I would argue Pony is completely unambiguous when it comes to references, mutation, etc in function signatures. Also Ada distinguishes between pipe-in pipe-out read-loan write-loan etc. Both of these are imperative languages. OCaml also has explicit references and explicit mutable flags and one can program imperatively within in.

    I think this is a really interesting point/problem and a great way of spelling it out! There are a bunch of languages that deal with it though.

    1. 8

      The closest thing we have currently is probably Rust, but it restricts the possible scenarios a lot for the sake of precisely tracking the ownership of a piece of data, which is necessary for its GC-less memory management system.

      I’m a bit confused. I can come up with relatively straight-forward implementations of the above scenarios with Rust. Obviously, they need lifetime annotations, but this is not really an issue of ownership. (Especially as one, “transfer”, explicitely asks for passing of exclusive ownership!)

      Ownership in Rust is far more then memory management. (For example, shared access, such as reference counting or even GC, is usually expressed through ownership of handles).

      I’d be interested in which scenarios aren’t possible with Rust (or very hard).

      IMHO, Rust provides a lot of these features through its signatures.

      1. 5

        I agree. I don’t buy the OP’s couple sentence dismissal of Rust here. It would be interesting, for example, to explore the idea the OP is discussing and how it interacts with interior mutability. On one hand, the presence of interior mutability might make the signatures found in Rust more ambiguous. On the other hand, I’ve found it interesting to design APIs where interior mutability cannot easily be observed by callers. That is, it is an implementation detail. Does that help the OP’s version of the ambiguity story or not?

        With respect to the OP’s dismissal, I too would like to see more examples. There are the obvious ones, such as self referential structs or graphs, but we have access to data structures (in std) that use, say, graphs internally but still expose unambiguous function signatures.

        1. 5

          On the other hand, I’ve found it interesting to design APIs where interior mutability cannot easily be observed by callers. That is, it is an implementation detail.

          Yes, that. The problem here in Rust is not its model, but its habit of exposing things.

          For those not so familiar with Rust: take Rc (Refcounter), which is internally mutable. If you take a new reference to an Rc, it increments the ref counter, which is obviously a mutation. This is not visible from the outside, you don’t need mutating access to the Rc or the contents to do this. This kind of violates the rule that you can only get mutating access to inner data if you have mutable access to outer data.

          Now, this might be one of the things that blog post author is talking about. A GC could hide this mutablity in some cases. But there’s many other structures that have nothing to do with memory management - such as a Mutex - with similar behaviour.

        2. 1

          You’re right that Rust solves these problems (partially at least, as @burntsushi mentions there is the issue of interior mutability). However, the cost of doing that is that it makes certain other patterns impossible without wrapping everything in refcounted boxes (I often find it useful to maintain global tables and registries for instance) – that is my impression anyway, but do tell if I’m missing something.

        3. 3

          The article poses some interesting questions. I suppose that chaining and proper composition of those effects could be accomplished with a type system. I was thrown, however, by ‘imperative’ being used as the opposite of ‘pure functional’ when the key issue is mutability.

          To explain further, we could have a function which evaluates several other functions internally that have side effects, but the side effects are localized: they don’t escape the top level function. The top level function has referential transparency, so the programmer using it would see not see the style as imperative. This is a minor nit. I only mention it because it affected my initial understanding.