www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Language Idea #6892: in array ops, enable mixing slices and random

reply Guillaume Piolat <notthat email.com> writes:
General idea
============

Currently arrays ops express loops over slices.

     a[] = b[] * 2 + c[]

It would be nice if one could mix a random access range into such 
an expression.
The compiler would have builtin support for random access range.


Example
=======

------------------------------>3---------------------------------------

import std.algorithm;
import std.array;
import std.range;

void main()
{
	int[] A = [1, 2, 3];
	
	// arrays ops only work with slices
	A[] += iota(3).array[];
	
	
	// Check that iota is a random access range
	auto myRange = iota(3);
	static assert( isRandomAccessRange!(typeof(myRange)) );
	
	
	// Doesn't work, array ops can't mix random access ranges and 
slices
         // NEW
	A[] += myRange[]; // whatever syntax could help the compiler
}

------------------------------>3---------------------------------------


How it could work
=================

     A[] += myRange[]; // or another syntax for "myRange as an 
array op operand"

would be rewritten to:

     foreach(i; 0..A.length)
         A[i] += myRange[i];


myRange should not be a range without "length".


Why?
====

Bridges a gap between lazy generation and array ops, now that 
array ops are reliable.
Allow arrays ops to take slice-like objects.


What do you think?
Feb 05 2018
parent reply Meta <jared771 gmail.com> writes:
On Monday, 5 February 2018 at 17:35:45 UTC, Guillaume Piolat 
wrote:
 General idea
 ============

 Currently arrays ops express loops over slices.

     a[] = b[] * 2 + c[]

 It would be nice if one could mix a random access range into 
 such an expression.
 The compiler would have builtin support for random access range.


 Example
 =======

 ------------------------------>3---------------------------------------

 import std.algorithm;
 import std.array;
 import std.range;

 void main()
 {
 	int[] A = [1, 2, 3];
 	
 	// arrays ops only work with slices
 	A[] += iota(3).array[];
 	
 	
 	// Check that iota is a random access range
 	auto myRange = iota(3);
 	static assert( isRandomAccessRange!(typeof(myRange)) );
 	
 	
 	// Doesn't work, array ops can't mix random access ranges and 
 slices
         // NEW
 	A[] += myRange[]; // whatever syntax could help the compiler
 }

 ------------------------------>3---------------------------------------


 How it could work
 =================

     A[] += myRange[]; // or another syntax for "myRange as an 
 array op operand"

 would be rewritten to:

     foreach(i; 0..A.length)
         A[i] += myRange[i];


 myRange should not be a range without "length".


 Why?
 ====

 Bridges a gap between lazy generation and array ops, now that 
 array ops are reliable.
 Allow arrays ops to take slice-like objects.


 What do you think?
It's already possible, with only very slightly worse aesthetics: struct VecOp(T) { T[] arr; pragma(inline, true) T[] opOpAssign(string op: "+", Range)(Range r) { int i; foreach (e; r) { arr[i] += e; i++; } return arr; } } pragma(inline, true) VecOp!E vecOp(E)(return E[] arr) { return typeof(return)(arr); } void main() { import std.range: iota; int[] a = [1, 2, 3]; a.vecOp += iota(3); assert(a == [1, 3, 5]); } I'm not very good at reading assembly, so I have no idea whether it's comparable to doing `a[] += [0, 1, 2]`.
Feb 05 2018
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 6 February 2018 at 02:14:35 UTC, Meta wrote:
 What do you think?
It's already possible, with only very slightly worse aesthetics:
[Good stuff] We can do better than that, though: import std.range, std.array, std.algorithm; struct Vec(Range) if (isInputRange!Range) { Range rng; this(Range value) { rng = value; } static if (!isInfinite!Range) { auto get() { return rng.array; } alias get this; } auto getRange(R2)(R2 other) { static if (isInputRange!R2) return other; else static if (isVec!R2) return other.rng; else return repeat(other); } auto opBinary(string op, R2)(R2 rhs) { return vec(rng.zip(getRange(rhs)).map!("a[0] "~op~" a[1]")); } auto opBinaryRight(string op, R2)(R2 lhs) { return vec(getRange(lhs).zip(rng).map!("a[0] "~op~" a[1]")); } auto opOpAssign(string op, R2)(R2 rhs) if (isForwardRange!Range) { auto rhsR = getRange(rhs); auto r = rng.save; foreach (ref e; r) { if (rhsR.empty) break; mixin("e "~op~"= rhsR.front;"); rhsR.popFront(); } return this; } } auto vec(Range)(Range r) if (isInputRange!Range) { return Vec!Range(r); } enum isVec(T) = is(T == Vec!U, U); unittest { import std.stdio; auto a = [1,2,3,4,5,6,7,8]; auto b = a.length.iota; auto c = recurrence!("a[n-1] + a[n-2]")(1, 1); uint[] result = a + b * c.vec; writeln(result); result.vec += result; } -- Simen
Feb 06 2018
parent reply Guillaume Piolat <notthat email.com> writes:
On Tuesday, 6 February 2018 at 09:02:46 UTC, Simen Kjærås wrote:
 On Tuesday, 6 February 2018 at 02:14:35 UTC, Meta wrote:
 What do you think?
It's already possible, with only very slightly worse aesthetics:
[Good stuff] We can do better than that, though: [More good stuff]
The problem in your sample is that you turn a range into a new slice with .array (which is "alias this" also), and this allocates. Meta's solution brings slices into the range world instead (rather than ranges to array ops like originally proposed).
Feb 06 2018
parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 6 February 2018 at 12:13:22 UTC, Guillaume Piolat 
wrote:
 On Tuesday, 6 February 2018 at 09:02:46 UTC, Simen Kjærås wrote:
 On Tuesday, 6 February 2018 at 02:14:35 UTC, Meta wrote:
 What do you think?
It's already possible, with only very slightly worse aesthetics:
[Good stuff] We can do better than that, though: [More good stuff]
The problem in your sample is that you turn a range into a new slice with .array (which is "alias this" also), and this allocates.
Only if you assign it to an array. While it's not very well indicated in the above example, this also works, and does not allocate: unittest { auto a = [1,2,3,4,5,6,7,8]; auto b = a.length.iota; a.vec += b; } -- Simen
Feb 06 2018
prev sibling parent Guillaume Piolat <notthat email.com> writes:
On Tuesday, 6 February 2018 at 02:14:35 UTC, Meta wrote:
 It's already possible, with only very slightly worse aesthetics:

 struct VecOp(T)
 {
     T[] arr;

     pragma(inline, true)
     T[] opOpAssign(string op: "+", Range)(Range r)
     {
         int i;
         foreach (e; r)
         {
             arr[i] += e;
             i++;
         }

         return arr;
     }
 }

 pragma(inline, true)
 VecOp!E vecOp(E)(return E[] arr)
 {
     return typeof(return)(arr);
 }
I'm not sure if this is equivalent to what I asked, but in the event it is, here is a thought. A fully featured VecOp could be part of druntime, and slices as array ops operands could be replaced by slice.vecOp by the compiler. If this vectorize well, this allow to remove array ops from the language. (However it seems to me array ops are recognized at the operator level but well)
Feb 06 2018