Jump to page: 1 2
Thread overview
Creating immutable arrays in @safe code
Jul 16, 2021
Dennis
Jul 16, 2021
Ali Çehreli
Jul 16, 2021
Dennis
Jul 17, 2021
Ali Çehreli
Jul 16, 2021
H. S. Teoh
Jul 16, 2021
Dennis
Jul 16, 2021
H. S. Teoh
Jul 17, 2021
ag0aep6g
Jul 17, 2021
Dennis
Jul 17, 2021
ag0aep6g
Jul 17, 2021
Dennis
Jul 17, 2021
rikki cattermole
Jul 17, 2021
ag0aep6g
Jul 18, 2021
ag0aep6g
Jul 18, 2021
zjh
July 16, 2021

I like passing around immutable data, but I don't like creating it. Here are some toy examples to illustrate:

immutable(int)[] positive(int[] input) @safe
{
    return input.filter!(x => x > 0).array;
}
>

Error: cannot implicitly convert expression array(filter(input)) of type int[] to immutable(int)[]

So we need to tell the compiler the data is unique, but that's not @safe:

immutable(int)[] positive(int[] input) @safe
{
    return input.filter!(x => x > 0).array.assumeUnique();
}
>

Error: @safe function positive cannot call @system function assumeUnique

I can use .idup instead of assumeUnique() in @safe code, but that makes a redundant copy. We could mark the function @trusted, but I don't want @trusted functions all over my code, so I thought I'd make a little helper function:

auto iarray(R)(R range)
{
    auto result = range.array;
    return (() @trusted => result.assumeUnique)();
}

And I got plenty of use out of it so far. Maybe something like that already exists in Phobos, but I couldn't find it. It has its limits however:

immutable(int)[] sortedPositive(int[] input) @safe
{
    return input.filter!(x => x > 0).iarray.sort.release;
}

That doesn't work because you can't sort an immutable array, so we're back to:

immutable(int)[] sortedPositive(int[] input) @trusted
{
    return input.filter!(x => x > 0).array.sort.release.assumeUnique();
}

I could make another primitive (iarraySort), but I wonder if there are more convenient ways to create immutable data in general?

July 16, 2021
On 7/16/21 1:19 PM, Dennis wrote:

> I like passing around immutable data

I think the D community needs to talk more about guidelines around 'immutable'. We don't... And I lack a complete understanding myself. :)

To me, 'immutable' is a demand of the user from the provider that the data will not mutate. So, I don't agree with the sentiment "I like passing around immutable data" because 'immutable' limits usability without a user to demand it to begin with. To me, 'immutable' must be used when really needed.

> , but I don't like creating it.

I think making newly-created data 'immutable' renders it less useful. (The caller cannot mutate it.) So to me, newly created data should be mutable for the most usability. 'immutable' should be decided by the caller. Luckily, we have 'pure' allows the caller to make it 'immutable' effortlessly:

import std;

pure
int[] positive(int[] input) @safe
{
    return input.filter!(x => x > 0).array;
}

void main() {
  immutable p = positive([1, 2]);
}

Now the function is 'pure' and the caller's data is 'immutable' because the caller decided it had to be immutable. On the other hand, the function is happier because it is useful to callers that may mutate the data. :)

Ali

July 16, 2021
On Fri, Jul 16, 2021 at 08:19:32PM +0000, Dennis via Digitalmars-d-learn wrote: [...]
> ```D
> immutable(int)[] positive(int[] input) @safe
> {
>     return input.filter!(x => x > 0).array;
> }
> ```
[...]
> I could make another primitive (`iarraySort`), but I wonder if there are more convenient ways to create immutable data in general?

Have you tried `pure`?


T

-- 
They say that "guns don't kill people, people kill people." Well I think the gun helps. If you just stood there and yelled BANG, I don't think you'd kill too many people. -- Eddie Izzard, Dressed to Kill
July 16, 2021

On Friday, 16 July 2021 at 20:39:41 UTC, Ali Çehreli wrote:

>

So to me, newly created data should be mutable for the most usability.

It's clear that I stripped away too much context with the toy examples, so let me try to add some back. I don't like forcing the use of immutable in general, but it's useful for transforming reference types into value types (inspired by invariant strings and std.bigint). The actual data structure I'm working with is a tree that looks like:

struct Expression
{
    immutable(Expression)[] children;
    int value;
}
>

Now the function is 'pure' and the caller's data is 'immutable' because the caller decided it had to be immutable.

I've encountered the use of pure for creating immutable data before, e.g: Function Purity and Immutable Data Structure Construction. But pure is no silver bullet:

import std;

pure: @safe:

struct Expression
{
    immutable(Expression)[] children;
    int value;
}

Expression withSortedChildren(Expression exp)
{
    return Expression(expr.children.dup.sort!((a, b) => a.value < b.value).release);
}
>

Error: cannot implicitly convert expression sort(dup(cast(const(Expression)[])expr.children)).release() of type Expression[] to immutable(Expression)[]

I've tried structuring it in a way that makes the compiler allow the conversion to immutable, but had no luck so far.

July 16, 2021

On Friday, 16 July 2021 at 20:45:11 UTC, H. S. Teoh wrote:

>

Have you tried pure?

The code in question is all @safe pure nothrow.

July 16, 2021
On Fri, Jul 16, 2021 at 10:23:31PM +0000, Dennis via Digitalmars-d-learn wrote:
> On Friday, 16 July 2021 at 20:45:11 UTC, H. S. Teoh wrote:
> > Have you tried `pure`?
> 
> The code in question is all `@safe pure nothrow`.

Hmm, OK. Not sure why .array isn't being inferred as unique... but yeah, you probably have to resort to using @trusted with .assumeUnique.


T

-- 
Тише едешь, дальше будешь.
July 16, 2021
On 7/16/21 3:21 PM, Dennis wrote:

> But `pure` is no silver bullet:

Agreed. I occasionally struggle with these issues as well. Here is a related one:

import std;

void foo(const int[] a) {
  auto b = a.array;
  b.front = 42;
  // Error: cannot modify `const` expression `front(b)`
}

I think that is a Phobos usability issue with array() because the freshly *copied* int elements are const. Really? So just because the programmer promised not to modify the parameter, now he/she is penalized to be *safe* with own data. I am not sure whether array() is the only culprit with this problem. (I think array() can be improved to pick mutable type for element types that have no indirections.)

> ```D
> import std;
>
> pure: @safe:
>
> struct Expression
> {
>      immutable(Expression)[] children;
>      int value;
> }
>
> Expression withSortedChildren(Expression exp)
> {
>      return Expression(expr.children.dup.sort!((a, b) => a.value <
> b.value).release);
> }
> ```
>
>> Error: cannot implicitly convert expression
>> `sort(dup(cast(const(Expression)[])expr.children)).release()` of type
>> `Expression[]` to `immutable(Expression)[]`
>
> I've tried structuring it in a way that makes the compiler allow the
> conversion to immutable, but had no luck so far.

I don't have a solution and you have more experience with this. :/

Ali

July 17, 2021
On 17.07.21 00:27, H. S. Teoh wrote:
> Hmm, OK. Not sure why .array isn't being inferred as unique... but yeah,
> you probably have to resort to using @trusted with .assumeUnique.

In addition to `pure`, you also need a const/immutable input and a mutable output, so that the output cannot be a slice of the input.

For std.array.array it might be possible to carefully apply `Unqual` to the element type.

I tried doing that, but `-preview=dip1000` causes trouble. This fails:

----
int[] array(const int[] input) pure nothrow @safe
{
    int[] output;
    foreach (element; input) output ~= element;
    return output;
}
void main() pure nothrow @safe
{
    const int[] c = [1, 2, 3];
    immutable int[] i = array(c);
    /* Without `-preview=dip1000`: works, because the result is unique.
    With `-preview=dip1000`: "Error: cannot implicitly convert". */
}
----

I'm not sure what's going on. `pure` being involved makes me think of issue 20150. But it also fails with my fix for that issue. So maybe it's another bug.
July 17, 2021

On Saturday, 17 July 2021 at 05:44:24 UTC, ag0aep6g wrote:

>

I tried doing that, but -preview=dip1000 causes trouble. This fails:
(...)
I'm not sure what's going on.

I'm not completely caught up, but from what I see, pure and immutable have a history of issues:

Issue 11503 - Type system breaking caused by implicit conversion for the value returned from pure function
Issue 11909 - Struct members and static arrays break pure function escape analysis (immutability violation)
Issue 15660 - break immutable with pure function and mutable reference params

There used to be a complex isReturnIsolated check, but the fix for issue 15660 reduced it to a check 'is the function strongly pure' which means 'parameters are values or immutable'. To reduce code breakage, the 'strong pure' requirement is only needed with -dip1000, which is why your example doesn't work with it.

July 17, 2021
On 17.07.21 13:05, Dennis wrote:
> There used to be a complex `isReturnIsolated` check, but the [fix for issue 15660](https://github.com/dlang/dmd/pull/8048) reduced it to a check 'is the function strongly `pure`' which means 'parameters are values or immutable'. To reduce code breakage, the 'strong pure' requirement is only needed with -dip1000, which is why your example doesn't work with it.

Hm, as far as I understand, "strongly pure" doesn't require `immutable` parameters. `const` should be enough. The spec says: "A strongly pure function has no parameters with mutable indirections" [1]. Seems to me that the fix is buggy.

Also, conflating other issues with DIP1000 is such an obviously terrible idea.


[1] https://dlang.org/spec/function.html#pure-functions
« First   ‹ Prev
1 2