One way to do what you want:
M-: (replace-regexp "\\([0-9]\\{4\\}\\)/\\([0-9]\\{2\\}\\)/\\([0-9]\\{2\\}\\)" "<\\1-\\2-\\3>" nil (point-min) (point-max))
This looks more complicated than it is because of the escaped backslashes: you could do M-x replace-regexp and then type the regexp in, in which case you'd only type a single backslash.
The regexp matches four digits, followed by a slash, followed by two digits, followed by a slash, followed by two digits. The groups of digits (year, month, day) are remembered (because they are inside \( ... \) ) and numbered 1, 2 and 3 resp. The replacement text then uses them by referring to them as \1, \2 and \3 (again the backslashes have to be doubled in lisp code).
The function is applied to a region (by default, starting from the current position of the cursor (the point) and going to the end of the buffer). Here we specify the beginning of the buffer ((point-min)) and the end of the buffer ((point-max)) so that the whole buffer is scanned).
There are many other ways to do this (e.g. two applications of replace-regexp with suitably modified versions of your ed regexps is another possibility - which I now see you have added in a comment!).
Regexps can be daunting and are overused in many cases, but there is no question that they are useful. See the Emacs manual, chapter Searching and Replacement, for more info. And for even more, see the Emacs Lisp Reference manual, chapter Searching and Matching.
replace-regexpseems to me to be be the natural fit here.replace-regexp RET / RET -and assumes that there are no other slashes around, but indeed, that's a perfectly good way to proceed. You need to be at the beginning of the buffer.