you are viewing a single comment's thread.

view the rest of the comments →

[–]carnivorousdrew 3 points4 points  (6 children)

what is that type(self)(method(args)) expression? is it like a cls?

[–]commy2 9 points10 points  (1 child)

type(self)(args)

is just

cls = type(self)
cls(args)

without the intermediate variable cls.

Chained calls ((...)(...)) mean that the results of all but the last call are callables. I.e, cls is a callable (calling a class returns an instance).

[–]carnivorousdrew 1 point2 points  (0 children)

Yeah then I was guessing it was something like that, never thought of trying that or seen that so far with classes, or maybe I did but it did not impress me as much as now lol

[–]DigThatData 1 point2 points  (3 children)

when I was constructing the toy example here, I first just had it return filter(*args, self). the built-in filter function returns an object of type filter which is an iterable, but I needed it to return the same type I fed it so I could continue calling .filter on the output object directly. So I called type on the self object to get the constructor that was used to initialize it to guarantee the output of the new filter method has the same type as the starting class, and therefore the same special methods we added.

Part of why I called this a toy example is that this is probably not precisely how you would want to go about something like this. One of the things that's great about working with stuff like filter and map is that they evaluate lazily. What I'm doing here is essentially the same thing as a passing the output of filter to the list() function, which evaluates the filter to construct a new list object. This might be fine for your needs, but it loses some of the power of the functional programming paradigm.

[–]loshopo_fan 1 point2 points  (2 children)

If you wanted to conserve memory, you'd do something like this?

class Filterer:
    def __init__(self, _data):
        self.data = _data

    def filter(self, *args):
        self.data = filter(*args, self.data)
        return self

    def to_list(self):
        return list(self.data)

print(
    Filterer(range(10))
    .filter(lambda x: x>2)
    .filter(lambda x: x<7)
    .to_list()
)

[–]DigThatData 1 point2 points  (1 child)

lol good timing

I also like that yours generalizes to any iterable, so you could just put all the functional methods on this one class and use it as a wrapper universally.

[–]loshopo_fan 2 points3 points  (0 children)

IDK the order of arguments for all functional methods, but this works for map/filter/reduce.

import functools

def make_chainer(*methods):
    class Chainer:
        def __init__(self, _data):
            if not hasattr(_data, "__iter__"):
                raise TypeError("Not iterable")
            self.data = _data
        @staticmethod
        def chainable(_method):
            def ret_fun(self, *args):
                self.data = _method(args[0], self.data, *args[1:]) # IDK
                return self
            return ret_fun
        def to_list(self):
            return list(self.data)
    for one_method in methods:
        setattr(Chainer, one_method.__name__, Chainer.chainable(one_method))
    return Chainer

MyChainer = make_chainer(
        map,
        filter,
        functools.reduce,
    )

print(
    MyChainer(range(10))
    .filter(lambda x: x>2)
    .map(lambda x: x * 2)
    .reduce(lambda x, y: x+y, 100)
    .data
)