July 10, 2012
On 07/11/2012 01:10 AM, Walter Bright wrote:
> On 7/10/2012 3:49 PM, Timon Gehr wrote:
>>> I understand, but the downside of not making these functions const is it
>>> will torpedo the use of functional style programming in D.
>>>
>>
>> Making these functions const will torpedo the use of OO style
>> programming in D.
>
> No, it won't, there are simple workarounds.
>

In @safe code there are none. There are also simple workarounds for the
functional style programming case: just do not annotate.

>
>> OO is all about data encapsulation, aliasing and
>> mutation.
>>
>> Also consider that:
>>
>> - Functional style programs use structs and delegates, not class
>> objects. Classes are an OO abstraction.
>>
>> - The most well-known functional programming language, Haskell, mutates
>> existing state a great deal at runtime, since almost _everything_ is
>> lazily initialized. I have written some Haskell-style D code, and
>> could not use the 'const', 'immutable' or 'pure' qualifiers.
>>
>> - what actually harms functional style programming in D the most is the
>> lack of tail calls. (I have some other gripes, but this is the most
>> important one.)
>>
>> - D is multi-paradigm. A program can be functional style in many ways,
>> and still mutate state. (Functional programming languages have to
>> invent idioms and syntactic sugar to provide an abstraction for
>> mutation. The compiler has to detect those and optimize them into
>> mutation.)
>>

I consider those points important.

>>> A straightforward workaround is to use PIMPL to encapsulate the logical
>>> const stuff, and then cast the reference to const to use inside the
>>> opEquals.
>>
>> I didn't get that.
>
> PIMPL means pointer to implementation.  Your const struct is just a
> wrapper around a pointer, and that pointer can be cast to mutable before
> calling member functions on it.
>
> Or:
>
> bool opEquals(const Object p) const
> {
>     Object q = cast() p;
>     Object r = cast() this;
>     return r.non_const_equals(q);
> }
>
> Of course, you'd have to make this @trusted.

It is not safe to do that. Marking it @trusted would be a lie. We
cannot build a type system on the premise that any OO code will break
it and at the same time claim that it provides actual guarantees.
July 10, 2012
On 7/10/2012 4:16 PM, Timon Gehr wrote:
> Const is stronger than what is required to bridge the gap between
> mutable and immutable. It guarantees that a reference cannot be used to
> mutate the receiver regardless of whether or not the receiver is
> immutable underneath. That is unnecessary as far as immutable is
> concerned. It only needs to guarantee that the receiver does not change
> if it is immutable underneath.

If you have a const function that accepts both mutable and immutable args, then that function *by definition* cannot tell if it received mutable or immutable args.

Furthermore, a const function is saying it will not change, even if mutable data is passed to it.

Everything falls apart once you allow "logical const" in. You'll be in the same boat as C++ const, which is faith-based programming rather than checkable programming.
July 10, 2012
On 7/10/2012 4:19 PM, H. S. Teoh wrote:
> On Tue, Jul 10, 2012 at 04:05:51PM -0700, Walter Bright wrote:
>> On 7/10/2012 4:05 PM, H. S. Teoh wrote:
> [...]
>>> What about the "partial const" idea I had?
>>>
>>> Given a class C which derives from a base class B, we may designate
>>> the base class B as const, which makes every inherited field const,
>>> while C itself may have mutable members. Then we can pass instances
>>> of C around as B references (it's OK to implicitly convert C to
>>> const(B), because the B part of C is actually const). As far as the
>>> user of the B references can tell, the object is const, even though
>>> the methods of C may be mutating C-specific members.
>>>
>>> So C could be a caching class, or whatever it is that needs mutation
>>> to work, and B is its "const part" which is guaranteed by the type
>>> system never to mutate.
>>
>> Logical const is still mutating const members.
> [...]
>
> Not in this case. The const(B) reference does not permit any of B's
> methods to mutate the members of B -- you cannot downcast a const(B)
> reference to a C reference. As far as the methods of B are concerned,
> the object is immutable.
>
> The interesting part is when C's methods override B's methods: those
> methods _can_ mutate the object, but not the members inherited from B.
> And this does not break immutability; you cannot cast const(B) to C, so
> if you start with immutable(B), it will remain immutable no matter what.
> You can't call C's methods from the const(B) reference, and you can't
> override B's methods with C's mutating methods without actually having a
> mutable C to begin with.
>
>
> T
>

If you've found a way to mutate const and have it stay const, then there's a hole in the typing system.

July 11, 2012
On 7/10/2012 4:29 PM, Timon Gehr wrote:
> On 07/11/2012 01:10 AM, Walter Bright wrote:
>> On 7/10/2012 3:49 PM, Timon Gehr wrote:
>>>> I understand, but the downside of not making these functions const is it
>>>> will torpedo the use of functional style programming in D.
>>>>
>>>
>>> Making these functions const will torpedo the use of OO style
>>> programming in D.
>>
>> No, it won't, there are simple workarounds.
>>
>
> In @safe code there are none.

Right. That's what I meant when you have to add @trusted.


> There are also simple workarounds for the functional style programming case: just do not annotate.

Functional means a guarantee against mutability. Faith-based functional programming is not functional programming.


> I consider those points important.

So do I. But all language design involves tradeoffs. I believe the advantages of const toHash etc. outweigh the disadvantage of less straightforward memoization.


>> Of course, you'd have to make this @trusted.
>
> It is not safe to do that. Marking it @trusted would be a lie. We
> cannot build a type system on the premise that any OO code will break
> it and at the same time claim that it provides actual guarantees.

Consider that in the light of what logical const actually is - it offers no guarantees whatsoever. At least with @trusted the code inspector knows that there's something unusual going on there that needs to be manually checked.
July 11, 2012
On 07/11/2012 01:57 AM, Walter Bright wrote:
> On 7/10/2012 4:16 PM, Timon Gehr wrote:
>> Const is stronger than what is required to bridge the gap between
>> mutable and immutable. It guarantees that a reference cannot be used to
>> mutate the receiver regardless of whether or not the receiver is
>> immutable underneath. That is unnecessary as far as immutable is
>> concerned. It only needs to guarantee that the receiver does not change
>> if it is immutable underneath.
>
> If you have a const function that accepts both mutable and immutable
> args, then that function *by definition* cannot tell if it received
> mutable or immutable args.
>

I understand. The trick is to disallow creating immutable instances of a class which is allowed to mutate the receiver in const methods.
This is essentially your proposal with the casts, but it is type safe.

This removes the 'const' guarantees, but 'immutable' stays
unaffected. Furthermore, functions with closures are type checked at
their creation site and may violate const-transitivity without affecting
'immutable'.

> Furthermore, a const function is saying it will not change, even if
> mutable data is passed to it.
>

A const function is saying nothing at all. Only const pure functions
provide any guarantees.

> Everything falls apart once you allow "logical const" in. You'll be in
> the same boat as C++ const, which is faith-based programming rather than
> checkable programming.

Casting away const because the object one is implementing cannot
support it is faith-based programming.


I for one would be satisfied if inheriting from object became optional:

// object.di
class RawObject /+ this is the root of the class hierarchy +/{ }
class SynchronizableObject : RawObject { void* monitor; }
class Object : SynchronizableObject {
     const { stuff }
}

// user code
class NoCruft : RawObject {
    // ...
}

And if AAs would stop being overly zealous about the opEquals, toHash,
opCmp signatures (why would it need opCmp? I have implemented my own
hash table for now, because I cannot support opCmp.)

The AA should just propagate the signatures to its methods. It will be
@safe to index an AA if the required members are @safe etc.
July 11, 2012
On 07/11/2012 02:03 AM, Walter Bright wrote:
> On 7/10/2012 4:29 PM, Timon Gehr wrote:
>> On 07/11/2012 01:10 AM, Walter Bright wrote:
>>> On 7/10/2012 3:49 PM, Timon Gehr wrote:
>>>>> I understand, but the downside of not making these functions const
>>>>> is it
>>>>> will torpedo the use of functional style programming in D.
>>>>>
>>>>
>>>> Making these functions const will torpedo the use of OO style
>>>> programming in D.
>>>
>>> No, it won't, there are simple workarounds.
>>>
>>
>> In @safe code there are none.
>
> Right. That's what I meant when you have to add @trusted.
>
>
>> There are also simple workarounds for the functional style programming
>> case: just do not annotate.
>
> Functional means a guarantee against mutability.

Only guaranteed referential transparency is necessary. Eg, 'fun' in the
following code snippet has this property. Even though it mutates global
state and calls a function that does not have the property.

int x = 2;
int fun(){
    auto y = x;
    x = 3;
    auto r = gun();
    x = y;
    return r;
}

auto gun(){ return arbitrary_pure_computation(x); }


> Faith-based functional programming is not functional programming.
>

This program is functional-style:
http://pastebin.com/Vx4hXvaT

With some template/static assert/information hiding magic, it could be
adapted so that any pure-annotated code using the Lazy types for all
arguments&returns provides referential transparency guarantees.

>
>> I consider those points important.
>
> So do I. But all language design involves tradeoffs. I believe the
> advantages of const toHash etc. outweigh the disadvantage of less
> straightforward memoization.
>

I use unbounded data structures. Those have to be initialized lazily
and therefore the non-constness of their methods invades the entire
code base which is written in OO and functional styles (and a good
portion of Metaprogramming to remove the boilerplate). Most methods
that would be considered const cannot be, because they may trigger
extensions of the unbounded data structures somewhere down the road.

>
>>> Of course, you'd have to make this @trusted.
>>
>> It is not safe to do that. Marking it @trusted would be a lie. We
>> cannot build a type system on the premise that any OO code will break
>> it and at the same time claim that it provides actual guarantees.
>
> Consider that in the light of what logical const actually is - it offers
> no guarantees whatsoever.

The issue is that the guarantees need to apply to objects to which they
cannot apply if they are forced to implement functions that provide the
guarantees.

> At least with @trusted the code inspector
> knows that there's something unusual going on there that needs to be
> manually checked.

There will be non-trivial bugs that the code inspector won't see.
Most bugs are caught by testing or trying to formally prove correctness.
July 11, 2012
On 07/11/2012 03:04 AM, Timon Gehr wrote:
> On 07/11/2012 02:03 AM, Walter Bright wrote:
>> ...
>> So do I. But all language design involves tradeoffs. I believe the
>> advantages of const toHash etc. outweigh the disadvantage of less
>> straightforward memoization.
>>
>
> I use unbounded data structures. Those have to be initialized lazily
> and therefore the non-constness of their methods invades the entire
> code base which is written in OO and functional styles (and a good
> portion of Metaprogramming to remove the boilerplate). Most methods
> that would be considered const cannot be, because they may trigger
> extensions of the unbounded data structures somewhere down the road.
>

(conceptually, the structures are immutable and infinite.)
July 11, 2012
On 7/10/2012 6:04 PM, Timon Gehr wrote:
>> Functional means a guarantee against mutability.
>
> Only guaranteed referential transparency is necessary. Eg, 'fun' in the
> following code snippet has this property. Even though it mutates global
> state and calls a function that does not have the property.
>
> int x = 2;
> int fun(){
>      auto y = x;
>      x = 3;
>      auto r = gun();
>      x = y;
>      return r;
> }
>
> auto gun(){ return arbitrary_pure_computation(x); }

Purity also means not reading mutable global state. Also, it is not practical for a compiler to ensure that x has the same value upon exit as entry if it is being assigned to.



> I use unbounded data structures. Those have to be initialized lazily
> and therefore the non-constness of their methods invades the entire
> code base which is written in OO and functional styles (and a good
> portion of Metaprogramming to remove the boilerplate). Most methods
> that would be considered const cannot be, because they may trigger
> extensions of the unbounded data structures somewhere down the road.

You do have another option - don't use toHash, opEquals, etc., with those structures.


>> At least with @trusted the code inspector
>> knows that there's something unusual going on there that needs to be
>> manually checked.
>
> There will be non-trivial bugs that the code inspector won't see.
> Most bugs are caught by testing or trying to formally prove correctness.

I cannot reconcile that with a desire for logical constness, which is completely uncheckable.

Anyhow, the point of @trusted is to notify the maintainer that "here be dragons". Logical const doesn't even go that far - it's purely ornamental.
July 11, 2012
On 7/10/2012 6:07 PM, Timon Gehr wrote:
> (conceptually, the structures are immutable and infinite.)

I understand, and those are useful constructs. Something has to give somewhere, and I believe that the future lies with provable constructs, not with faith based programming.
July 11, 2012
On 7/10/12 6:05 PM, Walter Bright wrote:
> A straightforward workaround is to use PIMPL to encapsulate the logical
> const stuff, and then cast the reference to const to use inside the
> opEquals.

s/straightforward/awful/

Andrei