Repository: dask/dask

Issue:
dask.bag.map argument splatting should check for passed in **kwargs as well
There's I think an unwanted behaviour in the current implementation for checking if `bag.map` should "splat" the arguments or not, i.e. fall back on `starmap`.

It is not uncommon to have functions with `args` that have no default values that at call time are defined by name by the callee, as in

    def f(a, b, c=1):
        return a+b+c

    f(0, b=10, c=11)

Currently `dask.bag.map` checks if the mapped function takes `*args` and if `*args` are provided to `bag.map`, if not it throws a depracation warning and falls back on `starmap`. The `bag.map` function should instead check if the `*args` and `**kwargs` provided jointly satisfy everything except the first argument in the mapped function, and only then fall back on `starmap`. 

Falling back on `starmap` is problematic when the `bag` contains dictionaries, which is often the case when handling JSON files for instance. Calling `map(*args) for args in bag` passes in the keys of the dictionary, which ends up producing very unhelpful error messages.

Consider the following two examples that should produce the same results

```python
from dask import bag as db
dummy_bag = db.from_sequence([{'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {'a': 1, 'b': 2}])

def dummy_map(item, c):
    item['d'] = item['a'] + item['b'] + c
    return item

dummy_bag.map(dummy_map, 10).compute()    # works fine
dummy_bag.map(dummy_map, c=10).compute()  # TypeError: dummy_map() got multiple values for argument 'c'
```



Discussion:

Resolving patch:
diff --git a/dask/bag/core.py b/dask/bag/core.py
--- a/dask/bag/core.py
+++ b/dask/bag/core.py
@@ -425,13 +425,7 @@ def map(self, func, *args, **kwargs):
         >>> b.map(myadd, b.max()).compute()
         [4, 5, 6, 7, 8]
         """
-        if takes_multiple_arguments(func, varargs=False) and not args:
-            warn("Automatic 'splatting' of arguments in `Bag.map` is "
-                 "deprecated and will be removed in a future release. "
-                 "Please use `Bag.starmap` instead.")
-            return self.starmap(func, **kwargs)
-        else:
-            return bag_map(func, self, *args, **kwargs)
+        return bag_map(func, self, *args, **kwargs)
 
     def starmap(self, func, **kwargs):
         """Apply a function using argument tuples from the given bag.
@@ -1061,10 +1055,6 @@ def flatten(self):
                    for i in range(self.npartitions))
         return type(self)(merge(self.dask, dsk), name, self.npartitions)
 
-    def concat(self):
-        warn("Deprecated.  Use the .flatten method instead")
-        return self.flatten()
-
     def __iter__(self):
         return iter(self.compute())
 


Test patch:
diff --git a/dask/bag/tests/test_bag.py b/dask/bag/tests/test_bag.py
--- a/dask/bag/tests/test_bag.py
+++ b/dask/bag/tests/test_bag.py
@@ -48,71 +48,6 @@ def test_keys():
     assert sorted(b._keys()) == sorted(dsk.keys())
 
 
-# Deprecated test for old map behavior
-def test_map():
-    c = b.map(inc)
-    assert c.compute() == list(map(inc, b.compute()))
-    assert c.name == b.map(inc).name
-
-
-# Deprecated test for old map behavior
-def test_map_function_with_multiple_arguments():
-    b = db.from_sequence([(1, 10), (2, 20), (3, 30)], npartitions=3)
-    assert list(b.map(lambda x, y: x + y).compute(get=dask.get)) == [11, 22, 33]
-    assert list(b.map(list).compute()) == [[1, 10], [2, 20], [3, 30]]
-
-
-class A(object):
-    def __init__(self, a, b, c):
-        pass
-
-
-class B(object):
-    def __init__(self, a):
-        pass
-
-
-# Deprecated test for old map behavior
-def test_map_with_constructors():
-    assert db.from_sequence([[1, 2, 3]]).map(A).compute()
-    assert db.from_sequence([1, 2, 3]).map(B).compute()
-    assert db.from_sequence([[1, 2, 3]]).map(B).compute()
-
-    failed = False
-    try:
-        db.from_sequence([[1,]]).map(A).compute()
-    except TypeError:
-        failed = True
-    assert failed
-
-
-# Deprecated test for old map behavior
-def test_map_with_builtins():
-    b = db.from_sequence(range(3))
-    assert ' '.join(b.map(str)) == '0 1 2'
-    assert b.map(str).map(tuple).compute() == [('0',), ('1',), ('2',)]
-    assert b.map(str).map(tuple).map(any).compute() == [True, True, True]
-
-    b2 = b.map(lambda n: [(n, n + 1), (2 * (n - 1), -n)])
-    assert b2.map(dict).compute() == [{0: 1, -2: 0}, {1: 2, 0: -1}, {2: -2}]
-    assert b.map(lambda n: (n, n + 1)).map(pow).compute() == [0, 1, 8]
-    assert b.map(bool).compute() == [False, True, True]
-    assert db.from_sequence([(1, 'real'), ('1', 'real')]).map(hasattr).compute() == \
-        [True, False]
-
-
-# Deprecated test for old map behavior
-def test_map_with_kwargs():
-    b = db.from_sequence(range(100), npartitions=10)
-    assert b.map(lambda x, factor=0: x * factor,
-                 factor=2).sum().compute() == 9900.0
-    assert b.map(lambda x, total=0: x / total,
-                 total=b.sum()).sum().compute() == 1.0
-    assert b.map(lambda x, factor=0, total=0: x * factor / total,
-                 total=b.sum(),
-                 factor=2).sum().compute() == 2.0
-
-
 def test_bag_map():
     b = db.from_sequence(range(100), npartitions=10)
     b2 = db.from_sequence(range(100, 200), npartitions=10)
@@ -763,7 +698,6 @@ def test_concat():
 def test_flatten():
     b = db.from_sequence([[1], [2, 3]])
     assert list(b.flatten()) == [1, 2, 3]
-    assert list(b.flatten()) == list(b.concat())
     assert b.flatten().name == b.flatten().name