[swift-evolution] Basic element-wise operator set for Arrays, Arrays of Arrays, etc.

Abe Schneider abe.schneider at gmail.com
Fri Feb 17 12:38:13 CST 2017


If I read Nicolas's post correctly, I think he's more arguing for the
ability to create syntax that allows Swift to behave in a similar way
to Numpy/Matlab. While Swift already does allow you to define your own
operators, the main complaint is that he can't define the specific
operators he would like.

I've been working on a Tensor library that would also benefit from
this. I ended up creating unicode operators for inner product etc. and
then used the standard operators for elementwise operations. However,
I think there is some virtue in not having to use the unicode
characters (many people don't want to have to remap their keyboard),
so providing alternatives might be nice.

While I've never been a fan of Matlab's notation, other people might
be familiar with it, so there's some virtue in making it available.


On Fri, Feb 17, 2017 at 1:01 PM, Xiaodi Wu via swift-evolution
<swift-evolution at swift.org> wrote:
> If you're simply looking for elementwise multiply without performance
> requirements, map(*) is a very succinct spelling.
>
> Performant implementations for these operations like you have in Matlab rely
> on special math libraries. Apple platforms have Accelerate that makes this
> possible, and other implementations of BLAS/LAPACK do the same for Linux and
> Windows platforms.
>
> There has been talk on this list of writing Swifty wrappers for such
> libraries. The core team has said that the way to get such facilities into
> Swift corelibs is to write your own library, get broad adoption, then
> propose its acceptance here. Currently, several libraries like Surge and
> Upsurge offer vectorized wrappers in Swifty syntax for Apple platforms; it
> would be interesting to explore whether the same can be done in a
> cross-platform way.
>
> But simply adding sugar to the standard library will not give you the
> results you're looking for (by which I mean, the performance will be
> unacceptable), and there's no point in providing sugar for something that
> doesn't work like the operator implies (Matlab's elementwise operators offer
> _great_ performance).
>
>
>
>
> On Fri, Feb 17, 2017 at 11:46 Nicolas Fezans via swift-evolution
> <swift-evolution at swift.org> wrote:
>>
>> Dear all,
>>
>> In swift (just as in many other languages) I have been terribly
>> missing the operators like  .*  ./  .^  as I know them from
>> MATLAB/Scilab. These operators are very handy and do element-wise
>> operations on vectors or matrices of the same size.
>>
>> So for instance A*B is a matrix multiplication (and the number of
>> columns for A must correspond to the number of rows in B), whereas A*B
>> (with A and B of same size) returns the matrix of that size whose
>> elements are obtained by making the product of each pair of elements
>> at the same location in A and B.
>>
>> So just a small example:
>> [1.0 , 2.5 , 3.0] .* [2.0 , 5.0 , -1.0] -> [2.0 , 12.5 , -3.0]
>>
>> The same exists for the division (./) or for instance for the power
>> function (.^). Here another example with *, .* , ^ , and .^ to show
>> the difference in behaviour in MATLAB/Scilab
>>
>> >> A = [1 2 3 ; 4 5 6 ; 7 8 9];
>> >> A*A
>>
>> ans =
>>
>>     30    36    42
>>     66    81    96
>>    102   126   150
>>
>> >> A.*A
>>
>> ans =
>>
>>      1     4     9
>>     16    25    36
>>     49    64    81
>>
>> >> A^2
>>
>> ans =
>>
>>     30    36    42
>>     66    81    96
>>    102   126   150
>>
>> >> A.^3
>>
>> ans =
>>
>>      1     8    27
>>     64   125   216
>>    343   512   729
>>
>> For addition and subtraction the regular operator (+ and -) and their
>> counterparts (.+ and .-) are actually doing the same. However note
>> that since the + operator on arrays is defined differently (it does an
>> append operation), there is a clear use for a .+ operation in swift.
>>
>> Version 1:
>> In principle, we can define it recursively, for instance ...+ would be
>> the element-wise application of the ..+ operator, which is itself the
>> element-wise application of the .+ operator, which is also the
>> element-wise application of the + operator.
>>
>> Version 2:
>> Alternatively we could have a concept where .+ is the element-wise
>> application of the .+ operator and finally when reaching the basic
>> type (e.g. Double when starting from [[[[Double]]]]) the .+ operator
>> needs to be defined as identical to the + operator. I do prefer this
>> version since it does not need to define various operators depending
>> on the "level" (i.e. Double -> level 0, [Double] -> level 1,
>> [[Double]] -> level 2, etc.). I could make this option work without
>> generics, but as I tried it with generics it generated a runtime error
>> as the call stack grew indefinitely (which does not seem as something
>> that should actually happen since at each call the level gets lower
>> and when reaching 0 it all solvable).
>>
>>
>> Anyway, I would like to discuss first the basic idea of defining these
>> element-wise operators for Arrays, before seeing how far it would be
>> interesting to go on this and how the implementation should exactly
>> look like. As a support for the discussion, you will find hereunder a
>> first shot for a generics-based solution for the aforementioned
>> Version 1 and going up to level 3 and for the 4 basic operators + - *
>> /
>> (BTW you can see that I have twice the same code, once with the
>> protocol conformance to my own protocols and once for FloatingPoint:
>> is there a way to specific the protocol conformance to protocol A or
>> to protocol B at once?)
>>
>> I personally think that these operators are very practical and helping
>> programmers to directly "vectorize" the way they write their
>> operations. Often these element-wise operations replace loops and I
>> think that the required syntax analysis (on compiler's side) to
>> vectorize the code is much simpler then. In swift, I have been using
>> map, flatMap, zip + map with a closure to make these type of
>> operations, but I think that the proposed operators would be a much
>> clearer and expressive way of coding this for most basic operations.
>>
>> Note that I mention and consider only Arrays here, but the idea might
>> be extended to other collections/containers.
>>
>> I am very curious to see the feedback of the community on this!
>>
>>
>> Nicolas
>>
>>
>>
>>
>> infix operator .+
>> infix operator ..+
>> infix operator ...+
>> infix operator .-
>> infix operator ..-
>> infix operator ...-
>> infix operator .*
>> infix operator ..*
>> infix operator ...*
>> infix operator ./
>> infix operator ../
>> infix operator .../
>>
>> protocol ImplementsInnerAddition       { static func + (_: Self,_:
>> Self)->Self }
>> protocol ImplementsInnerSubtraction    { static func - (_: Self,_:
>> Self)->Self }
>> protocol ImplementsInnerMultiplication { static func * (_: Self,_:
>> Self)->Self }
>> protocol ImplementsInnerDivision       { static func / (_: Self,_:
>> Self)->Self }
>>
>> func .+<T> (lhs: [T], rhs: [T]) -> [T] where T:ImplementsInnerAddition {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a + b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] + b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a + rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .+<T> (lhs: [T], rhs:  T ) -> [T] where T:ImplementsInnerAddition
>> { return lhs.map({(a: T)->T  in return  a  + rhs }) }
>> func .+<T> (lhs:  T , rhs: [T]) -> [T] where T:ImplementsInnerAddition
>> { return rhs.map({(b: T)->T  in return lhs +  b  }) }
>> func ..+<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerAddition {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a .+ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T] in return lhs[0] .+ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T] in return a .+ rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ..+<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where
>> T:ImplementsInnerAddition { return lhs.map({(a: [T])->[T]  in return
>> a  .+ rhs }) }
>> func ..+<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerAddition { return rhs.map({(b: [T])->[T]  in return
>> lhs .+  b  }) }
>> func ...+<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerAddition {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ..+ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ..+ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ..+ rhs[0] })
>>     }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ...+<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:ImplementsInnerAddition { return lhs.map({(a: [[T]])->[[T]]  in
>> return  a  ..+ rhs }) }
>> func ...+<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerAddition { return rhs.map({(b: [[T]])->[[T]]  in
>> return lhs ..+  b  }) }
>> func .-<T> (lhs: [T], rhs: [T]) -> [T] where T:ImplementsInnerSubtraction
>> {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a - b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] - b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a - rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .-<T> (lhs: [T], rhs:  T ) -> [T] where
>> T:ImplementsInnerSubtraction { return lhs.map({(a: T)->T  in return  a
>>  - rhs }) }
>> func .-<T> (lhs:  T , rhs: [T]) -> [T] where
>> T:ImplementsInnerSubtraction { return rhs.map({(b: T)->T  in return
>> lhs -  b  }) }
>> func ..-<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerSubtraction {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a .- b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T]                 in return lhs[0] .- b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T]                 in return a .- rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ..-<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where
>> T:ImplementsInnerSubtraction { return lhs.map({(a: [T])->[T]  in
>> return  a  .- rhs }) }
>> func ..-<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerSubtraction { return rhs.map({(b: [T])->[T]  in
>> return lhs .-  b  }) }
>> func ...-<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerSubtraction {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ..- b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ..- b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ..- rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ...-<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:ImplementsInnerSubtraction { return lhs.map({(a: [[T]])->[[T]]  in
>> return  a  ..- rhs }) }
>> func ...-<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerSubtraction { return rhs.map({(b: [[T]])->[[T]]  in
>> return lhs ..-  b  }) }
>> func .*<T> (lhs: [T], rhs: [T]) -> [T] where
>> T:ImplementsInnerMultiplication {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a * b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] * b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a * rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .*<T> (lhs: [T], rhs:  T ) -> [T] where
>> T:ImplementsInnerMultiplication { return lhs.map({(a: T)->T  in return
>>  a  * rhs }) }
>> func .*<T> (lhs:  T , rhs: [T]) -> [T] where
>> T:ImplementsInnerMultiplication { return rhs.map({(b: T)->T  in return
>> lhs *  b  }) }
>> func ..*<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerMultiplication {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a .* b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T]                 in return lhs[0] .* b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T]                 in return a .* rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ..*<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where
>> T:ImplementsInnerMultiplication { return lhs.map({(a: [T])->[T]  in
>> return  a  .* rhs }) }
>> func ..*<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerMultiplication { return rhs.map({(b: [T])->[T]  in
>> return lhs .*  b  }) }
>> func ...*<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerMultiplication {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ..* b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ..* b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ..* rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ...*<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:ImplementsInnerMultiplication { return lhs.map({(a: [[T]])->[[T]]
>> in return  a  ..* rhs }) }
>> func ...*<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerMultiplication { return rhs.map({(b: [[T]])->[[T]]
>> in return lhs ..*  b  }) }
>> func ./<T> (lhs: [T], rhs: [T]) -> [T] where T:ImplementsInnerDivision {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a / b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] / b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a / rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ./<T> (lhs: [T], rhs:  T ) -> [T] where T:ImplementsInnerDivision
>> { return lhs.map({(a: T)->T  in return  a  / rhs }) }
>> func ./<T> (lhs:  T , rhs: [T]) -> [T] where T:ImplementsInnerDivision
>> { return rhs.map({(b: T)->T  in return lhs /  b  }) }
>> func ../<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerDivision {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a ./ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T]                 in return lhs[0] ./ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T]                 in return a ./ rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ../<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where
>> T:ImplementsInnerDivision { return lhs.map({(a: [T])->[T]  in return
>> a  ./ rhs }) }
>> func ../<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where
>> T:ImplementsInnerDivision { return rhs.map({(b: [T])->[T]  in return
>> lhs ./  b  }) }
>> func .../<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerDivision {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ../ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ../ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ../ rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .../<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:ImplementsInnerDivision { return lhs.map({(a: [[T]])->[[T]]  in
>> return  a  ../ rhs }) }
>> func .../<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:ImplementsInnerDivision { return rhs.map({(b: [[T]])->[[T]]  in
>> return lhs ../  b  }) }
>> func .+<T> (lhs: [T], rhs: [T]) -> [T] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a + b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] + b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a + rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .+<T> (lhs: [T], rhs:  T ) -> [T] where T:FloatingPoint { return
>> lhs.map({(a: T)->T  in return  a  + rhs }) }
>> func .+<T> (lhs:  T , rhs: [T]) -> [T] where T:FloatingPoint { return
>> rhs.map({(b: T)->T  in return lhs +  b  }) }
>> func ..+<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a .+ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T] in return lhs[0] .+ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T] in return a .+ rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ..+<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where T:FloatingPoint {
>> return lhs.map({(a: [T])->[T]  in return  a  .+ rhs }) }
>> func ..+<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>> return rhs.map({(b: [T])->[T]  in return lhs .+  b  }) }
>> func ...+<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where T:FloatingPoint
>> {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ..+ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ..+ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ..+ rhs[0] })
>>     }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ...+<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:FloatingPoint { return lhs.map({(a: [[T]])->[[T]]  in return  a  ..+
>> rhs }) }
>> func ...+<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:FloatingPoint { return rhs.map({(b: [[T]])->[[T]]  in return lhs ..+
>>  b  }) }
>> func .-<T> (lhs: [T], rhs: [T]) -> [T] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a - b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] - b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a - rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .-<T> (lhs: [T], rhs:  T ) -> [T] where T:FloatingPoint { return
>> lhs.map({(a: T)->T  in return  a  - rhs }) }
>> func .-<T> (lhs:  T , rhs: [T]) -> [T] where T:FloatingPoint { return
>> rhs.map({(b: T)->T  in return lhs -  b  }) }
>> func ..-<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a .- b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T]                 in return lhs[0] .- b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T]                 in return a .- rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ..-<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where T:FloatingPoint {
>> return lhs.map({(a: [T])->[T]  in return  a  .- rhs }) }
>> func ..-<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>> return rhs.map({(b: [T])->[T]  in return lhs .-  b  }) }
>> func ...-<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where T:FloatingPoint
>> {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ..- b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ..- b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ..- rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ...-<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:FloatingPoint { return lhs.map({(a: [[T]])->[[T]]  in return  a  ..-
>> rhs }) }
>> func ...-<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:FloatingPoint { return rhs.map({(b: [[T]])->[[T]]  in return lhs ..-
>>  b  }) }
>> func .*<T> (lhs: [T], rhs: [T]) -> [T] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a * b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] * b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a * rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .*<T> (lhs: [T], rhs:  T ) -> [T] where T:FloatingPoint { return
>> lhs.map({(a: T)->T  in return  a  * rhs }) }
>> func .*<T> (lhs:  T , rhs: [T]) -> [T] where T:FloatingPoint { return
>> rhs.map({(b: T)->T  in return lhs *  b  }) }
>> func ..*<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a .* b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T]                 in return lhs[0] .* b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T]                 in return a .* rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ..*<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where T:FloatingPoint {
>> return lhs.map({(a: [T])->[T]  in return  a  .* rhs }) }
>> func ..*<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>> return rhs.map({(b: [T])->[T]  in return lhs .*  b  }) }
>> func ...*<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where T:FloatingPoint
>> {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ..* b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ..* b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ..* rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ...*<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:FloatingPoint { return lhs.map({(a: [[T]])->[[T]]  in return  a  ..*
>> rhs }) }
>> func ...*<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:FloatingPoint { return rhs.map({(b: [[T]])->[[T]]  in return lhs ..*
>>  b  }) }
>> func ./<T> (lhs: [T], rhs: [T]) -> [T] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> T,b: T)->T in return a / b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b: T)->T
>>            in return lhs[0] / b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
>>            in return a / rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ./<T> (lhs: [T], rhs:  T ) -> [T] where T:FloatingPoint { return
>> lhs.map({(a: T)->T  in return  a  / rhs }) }
>> func ./<T> (lhs:  T , rhs: [T]) -> [T] where T:FloatingPoint { return
>> rhs.map({(b: T)->T  in return lhs /  b  }) }
>> func ../<T> (lhs: [[T]], rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [T],b: [T])->[T] in return a ./ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [T])->[T]                 in return lhs[0] ./ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [T])->[T]                 in return a ./ rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func ../<T> (lhs: [[T]], rhs:  [T] ) -> [[T]] where T:FloatingPoint {
>> return lhs.map({(a: [T])->[T]  in return  a  ./ rhs }) }
>> func ../<T> (lhs:  [T] , rhs: [[T]]) -> [[T]] where T:FloatingPoint {
>> return rhs.map({(b: [T])->[T]  in return lhs ./  b  }) }
>> func .../<T> (lhs: [[[T]]], rhs: [[[T]]]) -> [[[T]]] where T:FloatingPoint
>> {
>>     guard (lhs.count != rhs.count) else { return zip(lhs,rhs).map({(a:
>> [[T]],b: [[T]])->[[T]] in return a ../ b }) }
>>     guard (lhs.count != 1)         else { return rhs.map({(b:
>> [[T]])->[[T]]                   in return lhs[0] ../ b }) }
>>     guard (rhs.count != 1)         else { return lhs.map({(a:
>> [[T]])->[[T]]                   in return a ../ rhs[0] }) }
>>     assert(false,"Element-wise operation can only be applied to arrays
>> of same size or alternatively if one of the array is of size
>> 1",file:#file,line:#line)
>> }
>> func .../<T> (lhs: [[[T]]], rhs:  [[T]] ) -> [[[T]]] where
>> T:FloatingPoint { return lhs.map({(a: [[T]])->[[T]]  in return  a  ../
>> rhs }) }
>> func .../<T> (lhs:  [[T]] , rhs: [[[T]]]) -> [[[T]]] where
>> T:FloatingPoint { return rhs.map({(b: [[T]])->[[T]]  in return lhs ../
>>  b  }) }
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>


More information about the swift-evolution mailing list