Jam don't add like that

APL, 44 bytes

{1=≡⍺⍵:⍺+⍵⋄=/∆←|≡¨⍺⍵:⊃∇¨/↓↑⍺⍵⋄</∆:⍺∘∇¨⍵⋄⍵∇⍺}

APL's + distributes over arrays as well, but in a different enough manner that this can't really be used. However, there is a built-in depth function ().

Explanation:

  • 1=≡⍺⍵:⍺+⍵: if the depths of are both zero (and therefore the depth of ⍺ ⍵ is 1), add them.
  • ∆←|≡¨⍺⍵: take the absolute of the depth of both and and store them in . ( gives a negative value if not all elements have the same depth.)
  • =/∆: if they have the same depth:
    • ↓↑⍺⍵: pad the shortest array with zeroes to match the longer array
    • ⊃∇¨/: distribute the function over both arrays
  • </∆: if the depth of is less than that of :
    • ⍺∘∇¨⍵: bind and then map over
  • ⍵∇⍺: if nothing else (so is deeper than ), swap the arguments and try again.

Mathematica, 122 bytes

d=Depth
x_~f~y_/;d@x>d@y:=y~f~x
x_~f~y_/;d@x<d@y:=x~f~#&/@y
x_List~f~y_:=MapThread[f,{x,y}~PadRight~Automatic]
x_~f~y_=x+y

Defines a recursive function f which computes the sum. Making use of Mathematica's pattern matching, this function is made up of four separate definitions:

x_~f~y_/;d@x>d@y:=y~f~x

If the depth of x is greater than that of y, swap the arguments so that we only have to handle distribution in one direction (which we can do, since addition is commutative).

x_~f~y_/;d@x<d@y:=x~f~#&/@y

If the depth of x is less thann that of y, replace each value # in y with f[x,#], which takes care of the distribution for arguments of unequal depth.

x_List~f~y_:=MapThread[f,{x,y}~PadRight~Automatic]

Otherwise, if one argument is a list (which implies that the other also is a list, since we know they have the same depth), we put both arguments in a list, pad them to the same length with PadRight[..., Automatic] (which simply fills up a ragged array with zeros to make it rectangular), and then use MapThread to apply f to corresponding pairs from the two lists.

And finally, the base case:

x_~f~y_=x+y

If none of the other patterns match, we must be trying to add two numbers, so we just do that.


Haskell, 150 bytes

data L=S Int|V{v::[L]}
d(V z)=1+maximum(d<$>S 0:z);d _=0
S x!S y=S$x+y
x!y|d x<d y=V$(x!)<$>v y|d x>d y=y!x|1<2=V$v x#v y
(x:a)#(y:b)=x!y:a#b;a#b=a++b

Explanation

The first line defines an algebraic data type L, which is either a Scalar (containing an Int) or a Vector (containing a list of Ls, accessed using a record getter v, which is a partial function L → [L].)

The second line defines the depth function: the depth of a Vector is one plus its maximum depth. I prepend S 0 to the values in the vector, so that depth [] == 1 + maximum [depth (S 0)] == 1. The depth of “anything else” (a scalar) is 0.

The third line defines the base case for ! (the addition function): the sum of scalars is simply a scalar.

The fifth line defines a variant of zipWith (!) that just picks elements from the longest list when one of them is empty.

The fourth line is split in three cases:

x!y | d x<d y = V$(x!)<$>v y
    | d x>d y = y!x
    | True    = V$v x#v y
  • If the depth of x is strictly less than the depth of y, map (x!) over the elements of y. (The use of v is guaranteed to be valid, as d(y) ≥ 1.)

  • If the depth of x is strictly greater, flip the arguments and restart.

  • If their depths are equal, zip the arguments together with (!). (The use of v is guaranteed to be valid, as the case d(x) = d(y) = 0 was handled as a base case.)

Test cases

instance Show L where
  show (S x) = show x
  show (V x) = show x

lArg = V [S 1, V [S 2, V [S 3, V [S 4]]]]
rArg = V [S 10, V [S 20]]

Then show (lArg ! rArg) == "[[11,[21]],[[12,[22]],[13,[24]]]]".