Embrace the tuple
DeviousStoat opened this issue · comments
Would you be open to PR that switches zip_
, zip_with
and to_pairs
to return list of tuples instead?
Using a list in python is very similar to using a tuple but in static typing having lists is a bit annoying.
For example:
import pydash
d: dict[str, int] = {"key1": 1, "key2": 2}
pairs = pydash.to_pairs(d) # this is list[list[str | int]]
first_key, first_value = pairs[0] # first_key is `str | int` and first_value is `str | int`
This doesn't make much sense, we know first_key
should be str
and first_value
should be int
. If to_pairs
returned list of tuples instead we could type it better and not have this problem.
Same with zip_
:
import pydash
zipped = pydash.zip_([1, 2, 3], ["hello", "hello", "hello"])
zipped
is a list of lists of str | int
, we lost the order on the type level, int and str are blend in the lists.
And I think the hardest to work with might be zip_with
import pydash
def more_hello(s: str, time: int) -> str:
return s.upper() * time
zipped = pydash.zip_with(
["hello", "hello", "hello"],
[1, 2, 3],
iteratee=more_hello # type error here: cannot assign `str | int` to `str` and `str | int` to `int`
)
while this is valid at runtime, the type checker doesn't know that the first argument is str
and second is int
because it is all blend into the list[str | int]
This makes them really hard to use in a type checked context. Chaining functions with the result of these functions is very impractical. I think it would make a lot more sense for these functions to return tuples.
Makes sense to me! I'm all in favor.
Are there any other places where a type change like this would also make sense/make things easier?
Not really type changes but improvement ideas I had:
There is the sort_by
min_by
max_by
functions that don't support accessing the key of a mapping in the iteratee. i think it would be cool if we could. I had this use case recently and I had to go out of the chain to use builtins min
, max
instead.
Also one thing that would be cool to have in the library is a maybe apply function kinda thing. pydash.get
is really cool but I am a bit sad that it is not typable in the current python type system. One very common use case of it I believe is to get data from an optional value:
import pydash as _
class SomeClass:
attribute: int = 5
@classmethod
def build(cls) -> "SomeClass | None":
...
some_class = SomeClass.build()
attr = _.get(some_class, "attribute") # `attr` is `Any`, we cannot type `get` properly
But we could have a maybe_apply
function thing that would take a callable:
attr = _.maybe_apply(some_class, lambda x: x.attribute) # `attr` is `int | None`
this is typable.
And it is not restricted to attribute or key getting, we can just apply anything to an optional value, it abstracts this pattern:
def add1(x: int) -> int:
return x + 1
some_int: Optional[int]
if some_int is not None:
some_int = add1(some_int)
# instead just do
some_int = _.maybe_apply(some_int, add1)
And with the chaining interface I think it would look really cool, eg:
import pydash as _
from dataclasses import dataclass
@dataclass
class SomeAddress:
city: str | None
@dataclass
class SomeUser:
addr: SomeAddress | None
@dataclass
class SomeClass:
user: SomeUser | None
some_class: SomeClass
maybe_upper_city: str | None = (
_.chain(some_class)
.maybe_apply(lambda x: x.user)
.maybe_apply(lambda x: x.addr)
.maybe_apply(lambda x: x.city)
.maybe_apply(lambda x: x.upper())
)