4 ms·
> And the namespace is completely riddled with bugs. union returns duplicates if some of the inputs are lists instead of sets depending on their length. to be
by blain_the_train 10y ago
> And the namespace is completely riddled with bugs. union returns duplicates if some of the inputs are lists instead of sets depending on their length.
to be clear I assume he is taking about this:
```
try-spec.core=> (clojure.set/union [1,2] [1,2])
[1 2 1 2]
```
Though im not sure what "depending on their length means".
> for the above bugs there are two possible fixes: raise an IllegalArgumentException if anything other than sets are provided
Isn't this an argument for types? Wont this type of problem be prolific in any dynamic language? What is the union of anything besides sets?
> coerce lists and vectors to sets before continuing
This only works for very similar data. How do you coerce a map into a set? Is it based on keys or values? What about a string? is it based on the whole string or the characters. I loaded up python3 to see how it was handled...
```
In [7]: set(["hello"]).union(["hello"])
Out[7]: {'hello'}```
is that what you would expect? Maybe. But honestly it should have thrown an error imo. Python is just skirting the issue. A set needs a hashable type. A list isn't one because its mutable. So python knew you "meant" a set of the items in the list... It throws up its hands if you force the issue further:
```
In [8]: x = [1,2,3]
In [9]: set(x)
Out[9]: {1, 2, 3}
In [10]: set([x])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-0b079714f02c> in <module>()
----> 1 set([x])
TypeError: unhashable type: 'list'```
The problem is that we have handed the union function the wrong type of arguments. That it fails in further down stream is the trade off we make with dynamic languages. The community seems to admit the trade off and alleviate it with things like clojure.spec. Im not mental equipped to argue type theory here. But i suppose i feel this is an _ancient_ argument and saying its a "bug" seems simplistic.
> How you define getting the wrong type with nonsense values counts as “working” is beyond me. Is it just because it doesn’t throw an Exception? Anyone here prefer bad data instead of exceptions when dealing with functions like this? I doubt it.
This is maybe the deeper problem. You would expect union to fail given the wrong types. Sure the failure wont be a type error but it will be contained inside the function. Leaking out the wrong data means its harder to debug. I'm generally curious how and why set would work on lists at all.
As to the rest of the arguments made here. I don't know enough to hazard a guess. But i would appreciate any feedback on my interoperation of the above situation.
- todd8 10y agoI'm a bit confused by your example. Python has different design goals than Clojure. Clojure competes with rather high performance languages, Java and Scala, on the JVM and it appears from the OP that its implementors have decided that it can't afford to spend too much time at run-time checking. Python on the other hand at least attempts to adhere to a principle of least surprise and enforces strong type requirements at run-time. It does this because consistency is generally more important than performance considerations in Python, one of the reasons that Python is much slower than Clojure. The API for the set constructor in Python accepts any iterable as an argument. Furthermore the elements of a set must be objects that are hashable. Consequentially, Python sets rule out mutable members, such as Lists, Maps, and other Sets. Sets of Set Objects aren't allowed since Set Objects can be modified. Strings are immutable so Sets of strings are allowed and even frozensets (an immutable kind of set object) and tuples (which happen to be immutable in Python) can be members of sets in Python. I believe that this is all spelled out in the documentation pretty clearly, and the language's built ins and standard library enforce this simple model at the cost of run-time type checking. Thus, set([1,2,3,2]) == {1,2,3} # the tuple (10,20) is immutable and hashable set([1,2,"abc",(10,20)]) == {1,2,"abc",(10,20)} # strings are iterables set("abc") == {"a", "b", "c"} # lists are iterable and strings immutable and hashable set(["abc"]) == {"abc"} # comprehensions and generators are iterable set(i*2 for i in range(5)) == {0,2,4,6,8} # sets are iterable set({2,4,6}) == {2,4,6} # int 14 is not an iterable set(14) <--- error # and [2,3] is a list and so mutable and not hashable set([1,[2,3]]) <--- error The interface to the union method for sets also takes an interable so, set([1,2,3]).union(["a", "bc"]) == {1,2,3,"bc","a"} set({1,2,3}).union({3,4,5}) == {1,2,3,4,5}
- blain_the_train 10y agoI'm not questioning that python api isn't clear. Neither is clojure one. Both require the user to read them in order to properly apply them. I think pythons resolutes only seem less surprising because someone would be used to them? Maybe that coercion is easier to reason about. Im honestly not sure. Like this example: In [19]: s = set([1, 2, 3]) In [20]: s.union({"x", "y"}) Out[20]: {1, 2, 3, 'y', 'x'} Is that more clear then explicitly requiring only sets be unioned? Why the key and the value? The meaning of the key and value have been drastically change now. Maybe this is what all languages do? anyway, I think i gained a lot thinking this through. Thanks everyone :) *One error Is I mistakenly said that strings weren't hashable.