Потому что в ЯП вроде Python и Ruby списки не типизированы, т.е. самого общего назначения. А метод join() строго заточен под строки. Во-первых, зачем код работы с форматированием вклеен в контейнер о бщего назначения? Он должен быть в соответствующей библиотеке форматирования. Во-вторых, что делать если нужно сделать join для нестандартных строк или других объектов? Конкретно в ruby метод join() особо омерзителен...['1', '2', '3'].join(' ') -> "1 2 3" # норм
[1, 2, 3].join(5) -> ошибка # хочет String вместо 5
[1, '2', '3'].join(' ') -> "1 2 3" # какого хрена?
[1, [ '2', '3' ]].join(' ') -> "1 2 3" # это уже просто пц
Также сюда забралась мерзость из перла в виде скрытых неявных переменных
$,='~'; ['1', '2', '3'].join(' ') -> "1~ ~2~ ~3"
В 'правильной' библиотеки мухи и котлеты должны быть отдельно, например, на некотором условном языке
Join_Fn(array, sep) # Отдельная функция
Join(sep)(array) # Функтор Join(...) с явными настройками далее форматирует
join = Join(sep); join(array) # .. массив через оператор вызова ()
Str_Ops(sep).Join(array) # Аналогично, только уже модуль Str_Ops с набором функий
Здесь под array могут прятаться массивы, итераторы, генераторы и слайсы, а не только списки. Т.е. такой подход ещё и обладает большей универсальностью .. потому что мухи отдельно и котлеты отдельно. Теперь посмотрим как сделано в Python
' '.join(['1', '2', '3']) -> '1 2 3' # норм
' '.join([1, '2', '3']) -> ошибка типа # норм
Здесь используется вариация метода Str_Ops(sep).Join(array) выше (тут ' ' = str(' ')). И в данном случае это только склейка строк, а не форматирование. И должно быть только так. Также здесь в качестве аргумента array может быть что угодно похожее на итератор.
В rust метод join() это прокся к трейту std::slice::Join, т.е. фактически сахар для вызова форматирования по типу отдельной функции Join_Fn(array, sep). И в rust массивы типизированные из-за чего отсутствует неявное форматирование и метод можно расширять для других типов. Но с логической и практичкской точек зрения такой подход всё равно плохой. С практической, например, сложнее кастомизировать join т.к. придётся придумывать как прокидывать дополнительные настройки через скахар в конечную функцию. Потому в rust есть другие методы сделать join
Итого, Ruby с точки зрения логичности, прозрачности и безопасности кода просто хлам.