"Immutable Builder" Pattern and Operator

I've been thinking of an Immutable Builder pattern and an operator to go with it. Since the builder would be immutable, this wouldn't work: long_name = mkbuilder() long_name.seta(a) long_name.setb(b) y = long_name.build() Instead, you'd need something more like this: long_name = mkbuilder() long_name = long_name.seta(a) long_name = long_name.setb(b) y = long_name.build() Or we could add an operator to simplify it: long_name = mkbuilder() long_name .= seta(a) long_name .= setb(b) y = long_name.build() (Yes, I'm aware you can x = mkbuilder().seta(a).setb(b), then y = x.build(). But that doesn't work if you wanna "fork" the builder. Some builders, like a builder for network connections of some sort, would work best if they were immutable/forkable.)

This is easy to do in Python, and we already have NamedTuples and other things. If you need such a builder anyway, this snippet can work - no need for special syntax: https://gist.github.com/jsbueno/b2b5f5c06caa915c451253bb4f171ee9 On 22 January 2017 at 20:54, Serhiy Storchaka <storchaka@gmail.com> wrote:

On 22/01/17 08:54 PM, Serhiy Storchaka wrote:
I guess you could do something like this, for an IRC bot builder: fnircbotbuilder = mkircbotbuilder(network="irc.freenode.net", port=6697, ssl=true) mainbot = mkircbotbuilder(parent=fnircbotbuilder, # ??? channels=["#bots"]).build() fndccbotbuilder = mkircbotbuilder(parent=fnircbotbuilder, dcc=true, channeldcc=true) dccbot = mkircbotbuilder(parent=fndccbotbuilder, channels=["#ctcp-s"]).build() otherircbotbuilder = mkircbotbuilder(parent=fndccbotbuilder, network="irc.subluminal.net") # because we want this whole network otherbot = mkircbotbuilder(parent=otherircbotbuilder, channels=["#programming"]).build() # to use DCC and channel DCC But this would be cleaner: botbuilder = mkircbotbuilder().network("irc.freenode.net").port(6697).ssl(true) mainbot = botbuilder.channels(["#bots"]).build() botbuilder .= dcc(true).channeldcc(true) dccbot = botbuilder.channels(["#ctcp-s"]).build() botbuilder .= network("irc.subluminal.net") otherbot = botbuilder.channels(["#programming"]).build() (I mean, "channels" could/should be a per-bot property instead of a per-builder property... but that doesn't affect the examples much.)

On 01/22/2017 03:30 PM, Soni L. wrote:
On 22/01/17 08:54 PM, Serhiy Storchaka wrote:
On 23.01.17 00:45, Soni L. wrote:
The following is just fine: fnircbotbuilder = mkircbotbuilder( network="irc.freenode.net", port=6697, ssl=true, ) mainbot = fnircbotbuilder(channels=["#bots"]).build() fndccbotbuilder = fnircbotbuilder(dcc=true, channeldcc=true) dccbot = fndccbotbuilder(channels=["#ctcp-s"]).build() otherircbotbuilder = fndccbotbuilder(network="irc.subluminal.net") otherbot = otherircbotbuilder(channels=["#programming"]).build() -- ~Ethan~

On 23.01.17 01:30, Soni L. wrote:
In Python you can save common options in a dict and pass them as var-keyword argument. Or use functools.partial. In any case you don't need a builder class with the build method and a number of configuring methods. It can be just a function with optional keyword parameters. A Builder pattern is often used in languages that don't support passing arguments by keyword and partial functions. Python rarely needs the purposed class implementing a Builder pattern. Actually a Builder pattern is built-in in the language as a part of syntax.

On 23.01.2017 14:05, Soni L. wrote:
I don't see this an being a particular intuitive way of writing such rather uncommon constructs. The syntax is not clear (what if you have an expression on the RHS) and it doesn't save you much in writing (if long_name is too long simply rebind it under a shorter name for the purpose of the code block). Also note that rebinding different objects to the same name in the same block is often poor style and can easily lead to hard to track bugs. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, Jan 23 2017)
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/

On 23 January 2017 at 13:05, Soni L. <fakedme+py@gmail.com> wrote:
Those don't seem particularly clear to me.
And it also looks neat.
Well, we have to agree to differ on that one. Also, the semantics of the proposed operation are very odd. If I understand your proposal a .= b(c) doesn't evaluate b(c) (It can't, as b is a method of a and doesn't make sense on its own), but rather combines the LHS and RHS with a dot - so it's defined in terms of rewriting the input rather than as an operation on the subexpressions. There's no other operator in Python that I'm aware of that works like this. What grammar would you allow for the RHS? So far you've shown LHS .= METHOD(ARGS) LHS .= ATTRIBUTE Clearly, LHS .= EXPR makes no sense in general (consider a .= 1+1). On the other hand, what about LHS .= ATTRIBUTE[INDEX] ? I'm guessing you'd want that allowed? Frankly, I don't think the benefits are even close to justifying the complexity. Paul

Have you looked at pyrsistent for immutable/functional/persistent/copy-on-write data structures in Python? https://github.com/tobgu/pyrsistent/ (freeze() / thaw()) ... e.g. List and Dict NamedTuple values are not immutable (because append() and update() still work) On Sunday, January 22, 2017, Soni L. <fakedme+py@gmail.com> wrote:

On Sunday, January 22, 2017, Wes Turner <wes.turner@gmail.com> wrote:
fn.py also has immutables: https://github.com/kachayev/fn.py/blob/master/README.rst#persistent-data-str...

On Sunday, January 22, 2017, Wes Turner <wes.turner@gmail.com> wrote:
From http://pyrsistent.readthedocs.io/en/latest/api.html#pyrsistent.PClass.set : "Set a field in the instance. Returns a new instance with the updated value. The original instance remains unmodified. Accepts key-value pairs or single string representing the field name and a value."

On Mon, Jan 23, 2017 at 12:49:19AM -0200, Soni L. wrote: [...]
You seem to be thinking of "immutable object builder". Not "the builder itself is immutable and operations on it create new builders".
Why would you make a builder class immutable? That's not a rhetorical question -- I'm genuinely surprised that you're not only using the builder design pattern (there are usually better solutions in Python) but more so that you're making the builder itself immutable. In any case, it seems that this is such a narrow and unusual use-case that it wouldn't be wise to give it special syntax. Especially when there are other, potentially far more useful, uses for the .= syntax, e.g. Julia's syntactic loop fusion: http://julialang.org/blog/2017/01/moredots -- Steve

On 23/01/17 11:54 AM, Steven D'Aprano wrote:
Builders for network connections where you don't wanna start with a fresh builder every time.
It is far more useful only because it's not just a syntax sugar. It's more like a completely new, standalone operator. Which, IMO, makes it more confusing. I propose `x .= y` -> `x = x . y`, for any `y`. You propose `x .= y` -> `operate_on(x).with(lambda: y)` This feels like you're arguing for loops are more useful than multiplication, and thus we shouldn't have multiplication.

On Mon, Jan 23, 2017, at 09:12, Soni L. wrote:
Builders for network connections where you don't wanna start with a fresh builder every time.
Maybe you need a builder builder. Or, more seriously, a way to differentiate the things in the 'builder' from the things that are going to be different with each connection, and pass them separately in the connection's constructor. Or a clone method.
I think you're underestimating the extent to which the fact that "y" isn't a real expression will cause confusion.

On 22 Jan 2017, at 22:45, Soni L. <fakedme+py@gmail.com> wrote:
This pattern is present in the cryptography module already with things like their x509.CertificateBuilder: https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Certific... <https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Certific...>. My 2c, but I find that code perfectly readable and legible. I don’t think a dot-equals operator would be needed. Cory

On 22 January 2017 at 22:45, Soni L. <fakedme+py@gmail.com> wrote:
I don't think the .= operator adds enough to be worth it. If the problem you see is the duplication of long_name in those lines (it's difficult to be sure without a real example) then you can use a temporary: b = mkbuilder() b = b.seta(a) b = b.setb(b) long_name = b y = long_name.build() For your real example: On 22 January 2017 at 23:30, Soni L. <fakedme+py@gmail.com> wrote:
I don't find the second example appreciably cleaner than the first. But a bit of reformatting looks better to me: # First create builders for the bots fnircbotbuilder = mkircbotbuilder( network="irc.freenode.net", port=6697, ssl=true) fndccbotbuilder = mkircbotbuilder( parent=fnircbotbuilder, dcc=true, channeldcc=true) otherircbotbuilder = mkircbotbuilder( parent=fndccbotbuilder, network="irc.subluminal.net") # Now create the actual bots mainbot = mkircbotbuilder( parent=fnircbotbuilder, channels=["#bots"]).build() dccbot = mkircbotbuilder( parent=fndccbotbuilder, channels=["#ctcp-s"]).build() otherbot = mkircbotbuilder( parent=otherircbotbuilder, channels=["#programming"]).build() And some API redesign (make the builders classes, and the parent relationship becomes subclassing, and maybe make channels an argument to build() so that you don't need fresh builders for each of the actual bots, and you don't even need the "builder" in the name at this point) makes the whole thing look far cleaner (to me, at least): class FNIRCBot(IRCBot): network="irc.freenode.net" port=6697 ssl=True class FNDCCBot(FNIRCBot): dcc=True channeldcc=True class OtherIRCBot(IRCBot): network="irc.subluminal.net" mainbot = FNIRCBot(channels=["#bots"]) dccbot = FNDCCBot(channels=["#ctcp-s"]) otherbot = OtherIRCBot(channels=["#programming"]) Paul

This is easy to do in Python, and we already have NamedTuples and other things. If you need such a builder anyway, this snippet can work - no need for special syntax: https://gist.github.com/jsbueno/b2b5f5c06caa915c451253bb4f171ee9 On 22 January 2017 at 20:54, Serhiy Storchaka <storchaka@gmail.com> wrote:

On 22/01/17 08:54 PM, Serhiy Storchaka wrote:
I guess you could do something like this, for an IRC bot builder: fnircbotbuilder = mkircbotbuilder(network="irc.freenode.net", port=6697, ssl=true) mainbot = mkircbotbuilder(parent=fnircbotbuilder, # ??? channels=["#bots"]).build() fndccbotbuilder = mkircbotbuilder(parent=fnircbotbuilder, dcc=true, channeldcc=true) dccbot = mkircbotbuilder(parent=fndccbotbuilder, channels=["#ctcp-s"]).build() otherircbotbuilder = mkircbotbuilder(parent=fndccbotbuilder, network="irc.subluminal.net") # because we want this whole network otherbot = mkircbotbuilder(parent=otherircbotbuilder, channels=["#programming"]).build() # to use DCC and channel DCC But this would be cleaner: botbuilder = mkircbotbuilder().network("irc.freenode.net").port(6697).ssl(true) mainbot = botbuilder.channels(["#bots"]).build() botbuilder .= dcc(true).channeldcc(true) dccbot = botbuilder.channels(["#ctcp-s"]).build() botbuilder .= network("irc.subluminal.net") otherbot = botbuilder.channels(["#programming"]).build() (I mean, "channels" could/should be a per-bot property instead of a per-builder property... but that doesn't affect the examples much.)

On 01/22/2017 03:30 PM, Soni L. wrote:
On 22/01/17 08:54 PM, Serhiy Storchaka wrote:
On 23.01.17 00:45, Soni L. wrote:
The following is just fine: fnircbotbuilder = mkircbotbuilder( network="irc.freenode.net", port=6697, ssl=true, ) mainbot = fnircbotbuilder(channels=["#bots"]).build() fndccbotbuilder = fnircbotbuilder(dcc=true, channeldcc=true) dccbot = fndccbotbuilder(channels=["#ctcp-s"]).build() otherircbotbuilder = fndccbotbuilder(network="irc.subluminal.net") otherbot = otherircbotbuilder(channels=["#programming"]).build() -- ~Ethan~

On 23.01.17 01:30, Soni L. wrote:
In Python you can save common options in a dict and pass them as var-keyword argument. Or use functools.partial. In any case you don't need a builder class with the build method and a number of configuring methods. It can be just a function with optional keyword parameters. A Builder pattern is often used in languages that don't support passing arguments by keyword and partial functions. Python rarely needs the purposed class implementing a Builder pattern. Actually a Builder pattern is built-in in the language as a part of syntax.

On 23.01.2017 14:05, Soni L. wrote:
I don't see this an being a particular intuitive way of writing such rather uncommon constructs. The syntax is not clear (what if you have an expression on the RHS) and it doesn't save you much in writing (if long_name is too long simply rebind it under a shorter name for the purpose of the code block). Also note that rebinding different objects to the same name in the same block is often poor style and can easily lead to hard to track bugs. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, Jan 23 2017)
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/

On 23 January 2017 at 13:05, Soni L. <fakedme+py@gmail.com> wrote:
Those don't seem particularly clear to me.
And it also looks neat.
Well, we have to agree to differ on that one. Also, the semantics of the proposed operation are very odd. If I understand your proposal a .= b(c) doesn't evaluate b(c) (It can't, as b is a method of a and doesn't make sense on its own), but rather combines the LHS and RHS with a dot - so it's defined in terms of rewriting the input rather than as an operation on the subexpressions. There's no other operator in Python that I'm aware of that works like this. What grammar would you allow for the RHS? So far you've shown LHS .= METHOD(ARGS) LHS .= ATTRIBUTE Clearly, LHS .= EXPR makes no sense in general (consider a .= 1+1). On the other hand, what about LHS .= ATTRIBUTE[INDEX] ? I'm guessing you'd want that allowed? Frankly, I don't think the benefits are even close to justifying the complexity. Paul

Have you looked at pyrsistent for immutable/functional/persistent/copy-on-write data structures in Python? https://github.com/tobgu/pyrsistent/ (freeze() / thaw()) ... e.g. List and Dict NamedTuple values are not immutable (because append() and update() still work) On Sunday, January 22, 2017, Soni L. <fakedme+py@gmail.com> wrote:

On Sunday, January 22, 2017, Wes Turner <wes.turner@gmail.com> wrote:
fn.py also has immutables: https://github.com/kachayev/fn.py/blob/master/README.rst#persistent-data-str...

On Sunday, January 22, 2017, Wes Turner <wes.turner@gmail.com> wrote:
From http://pyrsistent.readthedocs.io/en/latest/api.html#pyrsistent.PClass.set : "Set a field in the instance. Returns a new instance with the updated value. The original instance remains unmodified. Accepts key-value pairs or single string representing the field name and a value."

On Mon, Jan 23, 2017 at 12:49:19AM -0200, Soni L. wrote: [...]
You seem to be thinking of "immutable object builder". Not "the builder itself is immutable and operations on it create new builders".
Why would you make a builder class immutable? That's not a rhetorical question -- I'm genuinely surprised that you're not only using the builder design pattern (there are usually better solutions in Python) but more so that you're making the builder itself immutable. In any case, it seems that this is such a narrow and unusual use-case that it wouldn't be wise to give it special syntax. Especially when there are other, potentially far more useful, uses for the .= syntax, e.g. Julia's syntactic loop fusion: http://julialang.org/blog/2017/01/moredots -- Steve

On 23/01/17 11:54 AM, Steven D'Aprano wrote:
Builders for network connections where you don't wanna start with a fresh builder every time.
It is far more useful only because it's not just a syntax sugar. It's more like a completely new, standalone operator. Which, IMO, makes it more confusing. I propose `x .= y` -> `x = x . y`, for any `y`. You propose `x .= y` -> `operate_on(x).with(lambda: y)` This feels like you're arguing for loops are more useful than multiplication, and thus we shouldn't have multiplication.

On Mon, Jan 23, 2017, at 09:12, Soni L. wrote:
Builders for network connections where you don't wanna start with a fresh builder every time.
Maybe you need a builder builder. Or, more seriously, a way to differentiate the things in the 'builder' from the things that are going to be different with each connection, and pass them separately in the connection's constructor. Or a clone method.
I think you're underestimating the extent to which the fact that "y" isn't a real expression will cause confusion.

On 22 Jan 2017, at 22:45, Soni L. <fakedme+py@gmail.com> wrote:
This pattern is present in the cryptography module already with things like their x509.CertificateBuilder: https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Certific... <https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Certific...>. My 2c, but I find that code perfectly readable and legible. I don’t think a dot-equals operator would be needed. Cory

On 22 January 2017 at 22:45, Soni L. <fakedme+py@gmail.com> wrote:
I don't think the .= operator adds enough to be worth it. If the problem you see is the duplication of long_name in those lines (it's difficult to be sure without a real example) then you can use a temporary: b = mkbuilder() b = b.seta(a) b = b.setb(b) long_name = b y = long_name.build() For your real example: On 22 January 2017 at 23:30, Soni L. <fakedme+py@gmail.com> wrote:
I don't find the second example appreciably cleaner than the first. But a bit of reformatting looks better to me: # First create builders for the bots fnircbotbuilder = mkircbotbuilder( network="irc.freenode.net", port=6697, ssl=true) fndccbotbuilder = mkircbotbuilder( parent=fnircbotbuilder, dcc=true, channeldcc=true) otherircbotbuilder = mkircbotbuilder( parent=fndccbotbuilder, network="irc.subluminal.net") # Now create the actual bots mainbot = mkircbotbuilder( parent=fnircbotbuilder, channels=["#bots"]).build() dccbot = mkircbotbuilder( parent=fndccbotbuilder, channels=["#ctcp-s"]).build() otherbot = mkircbotbuilder( parent=otherircbotbuilder, channels=["#programming"]).build() And some API redesign (make the builders classes, and the parent relationship becomes subclassing, and maybe make channels an argument to build() so that you don't need fresh builders for each of the actual bots, and you don't even need the "builder" in the name at this point) makes the whole thing look far cleaner (to me, at least): class FNIRCBot(IRCBot): network="irc.freenode.net" port=6697 ssl=True class FNDCCBot(FNIRCBot): dcc=True channeldcc=True class OtherIRCBot(IRCBot): network="irc.subluminal.net" mainbot = FNIRCBot(channels=["#bots"]) dccbot = FNDCCBot(channels=["#ctcp-s"]) otherbot = OtherIRCBot(channels=["#programming"]) Paul
participants (10)
-
Cory Benfield
-
Ethan Furman
-
Joao S. O. Bueno
-
M.-A. Lemburg
-
Paul Moore
-
Random832
-
Serhiy Storchaka
-
Soni L.
-
Steven D'Aprano
-
Wes Turner