On Tue, Dec 23, 2014 at 10:51 AM, Jonathan S. Shapiro <[email protected]> wrote:
> On Tue, Dec 23, 2014 at 9:20 AM, Geoffrey Irving <[email protected]> wrote:
>
>> Could you give a brief description of what the mutability inference or
>> lack of it would look like?
>
>
> Not really, no. As anybody here can attest, I probably can't give a brief
> description of the time of day. :-)

Long descriptions are even better. :)

> In BitC v0, "mutable" and "readonly" (might have been "const") could be
> prefixed at any type. So you'd get things like:
>
> let x : mutable T = initializer in ...
> let x : mutable 'a = initializer in ...
>
>
> If you fail to state explicitly whether a parameter or variable is readonly,
> as in
>
> let x : T = initializer in ...
>
>
> then the best way to think of it is that you wrote (I'm making up notation):
>
> let x : ?mutable?(T) = initializer in ...
>
>
> with the result that mutability will be inferred. Mainly from the presence
> of assignment or as a result of passing x at an explicitly mutable
> by-reference parameter position. Mutability is not strongly contagious.
> Given an assignment of the form:
>
> x : 'lhs := expr : 'rhs
>
>
> obviously requires a mutable type for 'lhs, but the type compatibility
> requirement is merely that 'lhs and 'rhs be "copy compatible". Which is to
> say that they have compatible types excluding considerations of shallow
> mutability.
>
> All of this is pretty much what happens in BitC v0 today. I don't like the
> verbosity of the syntax, so I suspect that a convenience syntax such as one
> of
>
> let !x = initializer in ...
> let x := initializer in ...  // Note := in binding
>
>
> might be worthwhile.

That seems good, and is actually what I meant by "ocaml's ref cell
without the boxing".  That is, the above places the mutability as part
of the type, not "part of the variable".  Scala makes variable
mutability part of the variable and not visible to the type system:
"var x = 1" makes a mutable named thing "x", but if you look at x you
see only the type "Int".

>> In languages where mutability is the default, but an annotation is
>> possible, I generally find myself adding const / final / similar in
>> front of every single variable declaration to catch errors.
>
> I agree that's a potential concern. For structure and union fields, the
> default is readonly, though there are some interesting issues with what
> "list 'a" should mean given that 'a can bind a mutable type.
>>
>> I think there may be a distinction between simple variable mutability
>> and deep data structure mutability, though.
>
> Actually, it's way worse than that. It's not as simple as shallow vs. deep.
> There are valid use cases for deep partial mutability, and genericity over
> that presents some interesting challenges. You very rapidly get into the
> const vs. non-const vs. generic-over-mutability methods sort of problem.

A lot of my C++ code looks like const Array<const ...>, so I'm very
aware of the pain of mixing different kinds of mutability. :)

>> In imperative languages I
>> often find myself wanting to write routines which are as generic over
>> mutability as possible (e.g., take three arrays, one of which has to
>> be mutable, one of which has to be deep immutable (because we store it
>> in a lazy closure, say), one of which can and should be either one).
>> Here mutability inference sounds lovely.
>
> That's not generic over mutability. That sounds like an example of a
> procedure whose parameters are copy-compatible and which therefore doesn't
> care whether they are mutable or not. You're stumbling blissfully into the
> heart of one of the problems in BitC v0, which is that we failed to
> distinguish immutable (variable or field does not change) with readonly
> (variable is not modified by this consumer).
>
> I don't see why lazy closures require deep immutability. They may or may not
> require shallow immutability. Actually, I'm not sure why we're getting into
> lazy closures at all.

They don't *require* deep immutability, but one runs into screwy bugs
without it.  The Scala error I was referring to looked like

    var error = ...
    ...
    val x = bad(error)
    error = null

It looked fine, but bad took its argument by name, so it was really
bad(() => error).  The correct code was

    var error = ...
    ...
    val e = error
    val x = bad(e)
    error = null

so that the implicit closure accessed an immutable value.  If you
don't have language support for ensuring deep immutability, tracking
down bugs in closures becomes significantly harder.

>> In contrast, I'd personally be fine without a notation of mutability
>> of simple variables, as long as the unboxing support is good enough
>> that a mutable cell is as fast as a mutable language variable would
>> be.
>
> Mutability and unboxing in BitC are completely orthogonal. We reject, as a
> strong design principal, any argument that starts with "well, it's okay to
> rely on the compiler to unbox this". The problem with it is that (a) the
> specification must now state an escape analysis algorithm so that we fully
> know the contract about what will/won't be unboxed, and (b) we lose the
> ability to obtain a diagnostic when unboxing does not proceed as we expect.
>
>> I.e., use ocaml's ref type, but unbox it.
>
> That's exactly the solution we seek to avoid at all costs. It's
> categorically non-viable in a systems language.

We're saying the same thing, except that I'm using the wrong words.
By "unboxing support", I mean the ability for the language to express
things that are by definition unboxed, not the ability of the
optimizer to notice that a boxed thing can be unboxed.

>> Mutability of simple variables is fragile in the presence of closures...
>
> I'm not sure why you say this. Fragile in the sense that the compiler can
> readily get the closure construction wrong, or fragile in the sense that the
> presence of a mutable thing in a closure leads to surprising programming
> consequences? From the compiler perspective there's no difficulty building a
> closure that contains a mutable variable. If that wasn't intended, the
> tip-off that this occurred is that the resulting procedure will not be typed
> as pure.

Fragile in the sense of surprising programming consequences.  Typing
the resulting procedure as not pure is sufficient.

>> Python just gets in
>> wrong, Java disallows it entirely, and I spent half an hour on Friday
>> tracking on a bug in some Scala code that tied a var into a closure.
>
> Can you describe what each language does before we decide that it's wrong?

In python,

    closures = []
    for i in range(10):
      closures.append(lambda x:x+i)

makes 10 copies of lambda x:x+9.  In Java

    closures = ...
    for (int i=0;i<10;i++)
       closures.add(x -> x+i);

is an error because i isn't final.  The code has to be fixed to

    closures = ...
    for (int i=0;i<10;i++) {
       int i2 = i;
       closures.add(x -> x+i2);
    }

In Scala, you can reference vars in closures without complaint (see
above), but there's no support for warning you if the resulting
closure isn't pure.

Doing correct purity analysis seems like the best solution, unless the
purity analysis is too conservative.

Geoffrey
_______________________________________________
bitc-dev mailing list
[email protected]
http://www.coyotos.org/mailman/listinfo/bitc-dev

Reply via email to