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

Nicolas Fezans nicolas.fezans at gmail.com
Fri Feb 17 11:44:59 CST 2017

```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 + b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a + rhs }) }
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 .+ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T] in return a .+ rhs }) }
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
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 ..+ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ..+ rhs })
}
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 - b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a - rhs }) }
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 .- b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T]                 in return a .- rhs }) }
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 ..- b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ..- rhs }) }
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 * b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a * rhs }) }
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 .* b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T]                 in return a .* rhs }) }
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 ..* b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ..* rhs }) }
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 / b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a / rhs }) }
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 ./ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T]                 in return a ./ rhs }) }
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 ../ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ../ rhs }) }
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 + b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a + rhs }) }
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 .+ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T] in return a .+ rhs }) }
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 ..+ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ..+ rhs })
}
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 - b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a - rhs }) }
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 .- b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T]                 in return a .- rhs }) }
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 ..- b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ..- rhs }) }
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 * b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a * rhs }) }
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 .* b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T]                 in return a .* rhs }) }
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 ..* b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ..* rhs }) }
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 / b }) }
guard (rhs.count != 1)         else { return lhs.map({(a: T)->T
in return a / rhs }) }
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 ./ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[T])->[T]                 in return a ./ rhs }) }
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 ../ b }) }
guard (rhs.count != 1)         else { return lhs.map({(a:
[[T]])->[[T]]                   in return a ../ rhs }) }
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  }) }
```