dwt / fluent

Python wrapper for stdlib (and other) objects to give them a fluent interface.
ISC License
60 stars 2 forks source link

Possible solution to "call()" #4

Closed gamis closed 5 years ago

gamis commented 5 years ago

Howdy,

Thanks for a great library. For various reasons, I decided to write my own version of it, but it was heavily influenced by your work.

In my version, I was able to fix the "call()" issue you mention in your readme. Specifically, if I want to call the method foo on each element in my iterable, I can do from_(myit).map(each.foo()) rather than from_(myit).map(each.foo.call)

For example, here, I can get the backing numpy array from every axes in a figure and concatenate them:

from_(plt.gcf().axes)\
    .map(each.images[0].get_data())\
    .map(each.reshape(1,512,512))\
    .to(np.vstack)

My code is attached. Hope this helps! fluentutil.py.txt

dwt commented 5 years ago

Cool stuff! I'll certainly take a deep look at that. Thanks!

dwt commented 5 years ago

Thats very very interesting. I really like your idea that your expression generator object Each will always return another Each instance and thus allows for arbitrary chaining to build up complex expressions. Then in Iterable.map and other functions you resolve it, by calling Each.invoke() to 'reduce' it to a regular callable. (Also big cudos for the typ hints, that's certainly something I'm thinking about for fluentpy as well!)

I have thought about this in the past, and always thought that this wouldn't work for me, as it means that the generated expressions cannot be passed to arbitrary python code, because they are not regular callables.

However, thinking about it more, it seems to me that it would be quite natural for fluentpy to use the Each._ accessor to reduce the expression to a callable. This way _.each.attribute_access['item_access']('and calling')._ would work. For expressions that end in a non chaining way, e.g. _.each % 3 the natural way would be to rewrite it as (_.each % 3)._ but that looks awful to me. Maybe just define that operators terminate the chain implicitly? I don't like that that would be inconsistent, but it could work. Of course this would overload the .unwrap and ._ properties with something that behaves completely different, but at least it would be conceptually consistent.

What do you think? Am I getting your idea correctly?

gamis commented 5 years ago

Howdy,

Yeah, I type-hint almost all my python. Comes from many years in statically typed languages. Type hints only help so much here though. The Each object is inherently so dynamic, it would be impossible to keep track of types without being super-cumbersome for the user.

And yeah, operators are often quite a pain to deal with in a chain context. For example, in pandas, one frequently wants to combine a bunch of filter operations, but in python, you can't do my_data_frame[ my_data_frame.foo>3 & my_data_frame.bar <=2 ], you're forced to wrap the > and <= operations in parentheses, otherwise it tries to "&" the 3 and the my_data_frame.bar. That's why I also made a bunch of monkey patches to pandas so I can write something more like my_data_frame.gt(foo=3).leq(bar=2).

I like you're idea of forcing operators to terminate implicitly. Typically operators are producing a value at the end that isn't something you want to chain further.

g

On Tue, Jan 15, 2019 at 3:41 PM notifications@github.com wrote:

Thats very very interesting. I really like your idea that your expression generator object Each will always return another Each instance and thus allows for arbitrary chaining to build up complex expressions. Then in Iterable.map and other functions you resolve it, by calling Each.invoke() to 'reduce' it to a regular callable. (Also big cudos for the typ hints, that's certainly something I'm thinking about for fluentpy as well!)

I have thought about this in the past, and always thought that this wouldn't work for me, as it means, that the generated expressions cannot be passed to arbitrary python code, because they are not regular callables.

However, thinking about it more, it seems to me that it would be quite natural for fluentpy to use the Each. accessor to reduce the expression to a callable. This way .each.attribute_access'item_access'. would work. For expressions that end in a non chaining way, e.g. .each % 3 the natural way would be to rewrite it as (.each % 3). but that looks awful to me. Maybe just define that operators terminate the chain implicitly? I don't like that that would be inconsistent, but it could work. Of course this would overload the .unwrap and ._ properties with something that behaves completely different, but at least it would be conceptually consistent.

What do you think? Am I getting your idea correctly?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/dwt/fluent/issues/4#issuecomment-454543062, or mute the thread https://github.com/notifications/unsubscribe-auth/AHwjrFHEnw5x2EFOzXUApAO9JKfP49eqks5vDjzZgaJpZM4ZbIr7 .

dwt commented 5 years ago

I've reimplemented each according to your idea, and really like it so far. If you want, I'd love you to give it a whirl (currently master of the project).

It works like this:

What do you think of that?

gamis commented 5 years ago

Looks great!

On Mon, Feb 4, 2019 at 9:36 AM notifications@github.com wrote:

I've reimplemented each according to your idea, and really like it so far. If you want, I'd love you to give it a whirl (currently master of the project).

It works like this:

  • .each. is the identity function
  • .each.foo. creates a attrgetter
  • .each.foo'bar'. creates a chain that first executes an attrgetter('foo'), then an itemgetter('bar'), then chains a methodcaller('bar')
  • _.each.foo + 3 creates an attrgetter chained with an operator that adds 3 - and is auto-unwrapped.

What do you think of that?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/dwt/fluent/issues/4#issuecomment-460271425, or mute the thread https://github.com/notifications/unsubscribe-auth/AHwjrLhUvnukyTkucARwDbGfOcZct-kpks5vKEV4gaJpZM4ZbIr7 .

dwt commented 5 years ago

I've released version 2.0 now with the new _each - I'd love some more feedback if you have the time.

gamis commented 5 years ago

Howdy,

Sorry for the slow reply. Been busy. I don't really have any particular additional feedback. I think it looks great! I applied some of your changes to my version. One thing I found was helpful was to have some versions of commonly used each methods that short-cut straight to . E.g., I have a method on each called "apply" that will apply some function to each (rather than calling a method ON each). I have a version of apply called apply, such that .each.apply(foo) is equivalent to .each.apply(foo).. Just a little shorthand. Your version doesn't have many methods on each, though that's probably a good thing. Every method added potentially collides with the attributes on the elements you're trying to process.

g

On Tue, Feb 12, 2019 at 2:19 AM notifications@github.com wrote:

I've released version 2.0 now with the new _each - I'd love some more feedback if you have the time.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/dwt/fluent/issues/4#issuecomment-462645334, or mute the thread https://github.com/notifications/unsubscribe-auth/AHwjrC3hSHsy99wSWtlPlRkaVxxPuVdTks5vMmsIgaJpZM4ZbIr7 .

dwt commented 5 years ago

Yes, I did miss that ability, actually what I think I am missing is a way to turn each into a generic wrapper, so I can chain off of.

Also, I found that requiring the unwrap at the end of each expressions is surprisingly easy to miss and annoying to debug...

Not sure what the best way to make that better would be.

Sent with GitHawk

gamis commented 5 years ago

Use a statically typed language? :) Seriously, though, this whole library would be unnecessary in Scala or Kotlin.

Seriously, though, one option would be to make call result in an unwrap, and instead make a method call_() to actually indicate a method invocation.

On Tue, Mar 5, 2019 at 2:26 AM notifications@github.com wrote:

Yes, I did miss that ability, actually what I think I am missing is a way to turn each into a generic wrapper, so I can chain off of.

Also, I found that requiring the unwrap at the end of each expressions is surprisingly easy to miss and annoying to debug...

Not sure what the best way to make that better would be.

Sent with GitHawk http://githawk.com

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/dwt/fluent/issues/4#issuecomment-469569802, or mute the thread https://github.com/notifications/unsubscribe-auth/AHwjrNMj0E0Af50ijJ2L1IiYrw_kAdh4ks5vThwlgaJpZM4ZbIr7 .

dwt commented 5 years ago

:-) Well, in python 4 we may no longer have problems then... Having a .call. proxy is what I had before, and it sucked too. :-)