docs: function concat(a, b) {
  	var aLen = a.size;
  	var bLen = b.size;
  	var newLen = aLen + bLen;
  
  	if (aLen === 0) return b;
  	if (bLen === 0) return a;
  
  	if (!a.root || !b.root) {
  		if (aLen + bLen <= _const.M) {
  			let newTail = a.tail.concat(b.tail);
  			newTail.height = 0;
  			if (!a.editable) return new Tree(newLen, null, newTail);
  			a.size = newLen;
  			a.root = null;
  			a.tail = newTail;
  			return a;
  		}
  		if (!a.root && !b.root) {
  			// newTail will overflow, but newRoot can't be over M
  			let newRoot = a.tail.concat(b.tail.slice(0, _const.M - aLen));
  			newRoot.height = 0;
  			let newTail = b.tail.slice(_const.M - aLen);
  			newTail.height = 0;
  			if (!a.editable) return new Tree(newLen, newRoot, newTail);
  			a.size = newLen;
  			a.root = newRoot;
  			a.tail = newTail;
  			return a;
  		}
  		// else either a has a root or b does
  		if (!b.root) {
  			// b has no root
  			let aTailLen = a.tail.length;
  			let bTailLen = b.tail.length;
  			// size left over in last root node in a
  			let rightCut = _const.M - aTailLen;
  			// create a new tail by concatting b until cut
  			let newTail = a.tail.concat(b.tail.slice(0, rightCut));
  			newTail.height = 0;
  			let newRoot;
  			// if tail would overflow, sink it and make leftover newTail
  			if (aTailLen + bTailLen > _const.M) {
  				newRoot = (0, _util.sinkTailIfSpace)(newTail, a.root, a.editable);
  				newTail = b.tail.slice(rightCut);
  				newTail.height = 0;
  			} else {
  				newRoot = a.root.slice(0);
  				if(a.root.height) newRoot.sizes = a.root.sizes.slice(0);
  				newRoot.height = a.root.height;
  			}
  			if (!a.editable) return new Tree(newLen, newRoot, newTail);
  			a.size = newLen;
  			a.root = newRoot;
  			a.tail = newTail;
  			return a;
  		}
  		// else a has no root
  		// make a.tail a.root and concat b.root
  		let newRoot = (0, _concat.concatRoot)((0, _util.parentise)(a.tail, 1), b.root, a.editable);
  		let newTail = (0, _util.createLeafFrom)(b.tail, a.editable);
  		if (!a.editable) return new Tree(newLen, newRoot, newTail);
  		a.size = newLen;
  		a.root = newRoot;
  		a.tail = newTail;
  		return a;
  	} else {
  		// both a and b have roots
  		// if have a.tail, just sink a.tail and make b.tail new tail...
  		let aRoot = a.tail.length === 0 ? a.root : (0, _util.sinkTailIfSpace)(a.tail, a.root, a.editable) || (0, _util.siblise)(a.root, (0, _util.parentise)(a.tail, a.root.height));
  		let newRoot = (0, _concat.concatRoot)(aRoot, b.root, a.editable);
  		let newTail = (0, _util.createLeafFrom)(b.tail, a.editable);
  		if (!a.editable) return new Tree(newLen, newRoot, newTail);
  		a.size = newLen;
  		a.root = newRoot;
  		a.tail = newTail;
  		return a;
  	}
  }