Hacker News new | past | comments | ask | show | jobs | submit login

The following example is kinda funny:

    // This is obviously not too right
    ",".splitBy("1,2,3,4,5")

    // This should be right, because it reads out more naturally
    "1,2,3,4,5".splitBy(",")
Seeing as Python uses the first version for .join()



Exactly what I came here to write about:

  Python 3.8.2 (default, Jul 16 2020, 14:00:26) 
  >>> " ".join(["a", "b"])
  'a b'
vs Ruby

  2.6.5 :001 > ["a", "b"].join(" ")
  => "a b" 
I don't know which one is more natural but I prefer the Ruby version because it's consistent with

  "a b".split(" ")
which works in both languages. One less think to remember.


In Python:

["a", "b"].join(" ")

This would mean the type list has a method join, how would it work with the following ?

[1.5, "hello", None].join(" ")


Principle of least surprise: make it work like

  >>> f"{1.5} {'hello'} {None}"
  '1.5 hello None'
Ruby does that. From [1] "[Array#join] Returns a string created by converting each element of the array to a string, separated by the given separator."

The difference is that nil (Ruby's None) disappears

  2.6.5 :001 > [1.5, "hello", nil].join(" ") 
  => "1.5 hello " 
That's totally expected because in Ruby the conversion of nil into a string is the empty string. Python converts it into the string "None".

[1] https://ruby-doc.org/core-2.6.5/Array.html#method-i-join

Edit: an example with a type that normally wouldn't meaningfully cast to string

  2.6.5 :001 > class Example
  2.6.5 :002?>   attr_accessor :name
  2.6.5 :003?> end
   => nil 
  2.6.5 :004 > e = Example.new
   => #<Example:0x000055a321fa2bb0> 
  2.6.5 :005 > e.name = "example"
   => "example" 
  2.6.5 :006 > e.to_s
   => "#<Example:0x000055a321fa2bb0>" 
  2.6.5 :007 > [1, e].join(" ")
   => "1 #<Example:0x000055a321fa2bb0>" 
  2.6.5 :008 > class Example
  2.6.5 :009?>   attr_accessor :name
  2.6.5 :010?>   def to_s
  2.6.5 :011?>     "#{name}"
  2.6.5 :012?>   end
  2.6.5 :013?> end
   => :to_s 
  2.6.5 :014 > e = Example.new
   => #<Example:0x000055a321f8a4c0> 
  2.6.5 :015 > e.name = "example"
   => "example" 
  2.6.5 :016 > e.to_s
   => "example" 
  2.6.5 :017 > [1, e].join(" ")
   => "1 example"


  >>> " ".join([1.5, "hello", None])
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: sequence item 0: expected string, float found
Why would `[1.5, "hello", None].join(" ")` be any different?


Because "list of string" is not a type in Python.

Why would you put a method on a class if only a small subset of it can use it?


Either way it's a partial function that is not defined for most of its domain. The only difference is how its arguments (including self) get arranged at call sites, right?


That happens all the time, since Python is dynamically typed. For example a[10] gives an IndexError if a has less than 11 elements. So with static typing, we should restrict indexing to ensure that the number of elements needed are present, and remove indexing from general lists. But you wouldn't remove indexing from lists, that's one of the main functions...


the key difference is that in ruby, `join` is implemented in the `Enumerable` mixin (which provides a whole suite of functionality to any object with an `each` method). if your own class wants to support `join`, it has to both implement `each` and explicitly mix in `Enumerable`.

in python, `join` is implemented in the `string` class, and the argument is an iterable. therefore, if your class wants to support `join`, it needs to implement the `__iter__` method (python's equivalent of ruby's `each`), but it does not need to also mix in an implementation of `join`.

it's mostly a cultural difference between ruby and python - the ruby ecosystem leans more heavily towards mixins and implementing generic functionality by attaching methods to objects, whereas the python ecosystem leans more heavily towards implementing generic functionality by providing functions (or methods on an external class) that accept generic objects.


Actually join is a method of Array https://ruby-doc.org/core-2.6.5/Array.html

A class that wants to support join has to implement to_s. The definition of join is

> join(separator=$,) → str

> Returns a string created by converting each element of the array to a string, separated by the given separator. If the separator is nil, it uses current $,. If both the separator and $, are nil, it uses an empty string.

If the class doesn't implement to_s Object.to_s kicks in and displays something like "#<SomeClass:0x000055a321fa2bb0>"

Enumerable is really a mixin https://ruby-doc.org/core-2.6.5/Enumerable.html but join is not listed in its methods.

There is another reply of mine with an example of a class can be used in join and doesn't mix in Enumerable.


oops, right you are, it's not defined on Enumerable (though i do wonder why! `each` does define an iteration order after all)


If I were designing a functional language, I'd be thinking about whether data.splitBy or delimiter.split was useful even when not immediately invoked.

Consider this crude csv parser:

using splitBy:

    csv.splitBy("\n").map(csv_row=>csv_row.splitBy(","))
using split:

    "\n".split(csv).map(",".split)
split was the clear winner here. Given splitBy, I basically recreated split as a lambda.

I tried to create an analog to that where splitBy would come out looking better. I figured that if we didn't know the dimensionality of our data, then delimiters becomes an array of arbitrary length and we could pass data.!splitBy into something like delimiters.reduce. When actually writing that, however, I wound up recreating split again:

using splitBy:

    delimiters.reduce((accum,delim)=>accum.deepMap(data=>data.splitBy(delim)),[data])
using split:

    delimiters.reduce((accum,delim)=>accum.deepMap(delim.split),[data])


Yeah Python (and I think JavaScript?) pretty clearly have `join` backwards.


JS uses the second example


It is because in Javascript everything can be translated to a string:

1 + "hello" == "1hello"

Therefore, the type Array can have a join method that have predictable results (unlike Python).


I don't know what that has to do with `join`'s call order.

As mentioned above, it's because Python supports `join` on any sequence, not just the array class. Personally, I find JavaScript's solution neater:

    [..."hello"].join(" ") === "h e l l o"


Because when the Javascript engine will try to concatenate all the strings in the Array (the expected behavior of join), it will first translate every object in the array to a string.

Javascript always falls back to string, that is also why we have === and == operators.

Python's type system is a bit more strict because it will not attempt to serialize your objet if you haven't explicitly done it yourself.

My point is: Python doesn't use this notation because it would not make sense in Python.


This is also the cause of this lovely bit of JS behaviour:

    >>> [1,2,10].sort()
    Array(3) [ 1, 10, 2 ]




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: