I don't understand polynomials as frozensets. What's the point of
representing them that way? Particularly if you're converting to and
from dicts all the time, why not represent them as dicts? Or as some
custom mapping type, if you need it to be hashable?

Sorry for deviating here, but this kind of argumentation is one
that is sensitive for me -   but here it is:
I personally do not think the  comment above adds anything to the discussion at hand.
We've been presented to a real-world use case of frozensets that would
benefit in readability from having a dedicated literal. How good it is to 
question the way it is coded without even checking the project out? 
(and even so, do that publicly in a non related discussion?)

I had this happen to me in an email here, when I tried an early 
version of match/case in a code in a project of mine. Despite 
being a more or less internal API, the code was bashed in 
a way, in an unasked for code review,
 it took out the fun I had in coding the project for months.

So, please, take care when deviating from the discussion at hand.

Back on topic: 
It looks like this thing of "prefixes are valid for strigns and no good
for anything else" is, as yoiu put it, Chris, a personal thing.

Do we have anyone else in this thread commenting (or even
"+1ing") on that side? As I've mentioned a couple
of times before: is there any other arguments against
"f{}" other than "prefixes should be for strings only" 
(and the"bug magnet" perceived by many as a plain
 incorrect statement )?

If there is not, then we are not at "there is no viable syntax",
as prefixable braces are perfectly viable. It is whether it should be
done or not, despite some people finding it ugly, which is subjective.   

At that point, I argue that despite adding still more things to 
the syntax, it is one that will spare time in average than the other
way around, due to the time people, needing frozensets for
the first time in any project, waste looking for a literal syntax for them
only to find out there is not any.

On Fri, Jan 21, 2022 at 9:16 AM Chris Angelico <rosuav@gmail.com> wrote:
On Fri, 21 Jan 2022 at 22:52, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
>
> On Thu, 20 Jan 2022 at 10:19, Ricky Teachey <ricky@teachey.org> wrote:
>>
>> On Thu, Jan 20, 2022 at 3:35 AM Stephen J. Turnbull <stephenjturnbull@gmail.com> wrote:
>>>
>>> Christopher Barker writes:
>>>
>>>  > If this does all come to pass, then:
>>>  >
>>>  > s = {3,8,2}.frozen()
>>>  > will be slightly faster, in some case, than
>>>  > s = frozenset({3,8,2}
>>>  >
>>>  > but the result would be the same.
>>>  >
>>>  > There are plenty of tricks to in python to get a touch more performance,
>>>  > this would just be one more
>>>  > and frankly pretty rare that it would make an noticable difference at all.
>>>  >
>>>  > +1 on this
>>>  > +0 on f{}
>>>  > -1 on making frozenset a keyword
>>>
>>> Stated better than I could, expresses my feelings exactly.  Sticking
>>> to integers (in floats I'd be less than -0 on f{}), I'll go with
>>> Chris's ratings, too.
>>>
>>> Steve
>>
>>
>> Another agreement with Chris' ratings:
>>
>> +1 for .frozen()
>> +0 on f{}
>> -1 on keyword for frozenset
>
>
> I really don't understand (having read everything above) why anyone prefers {1,2,3}.frozen() over f{1,2,3}. Yes, some people coming from some other languages might get confused (e.g. in Mathematica this is function call syntax) but that's true of anything: you have to learn Python syntax to use Python. The fact that {1,2,3} is a set and f{1,2,3} is a frozenset is not difficult to explain or to understand, especially in a language that already uses single letter prefixes for other things.
>
> The .frozen() method is a strangely indirect way to achieve a minor optimisation. Outside of attempting to achieve that optimisation it's basically useless because any time you would have written obj.frozen() you could have simply written frozenset(obj) so it does nothing to improve code that uses frozensets.
>

If set.frozen() is optimized, then str.upper() can be optimized the
same way, which means there's a lot of places where constant folding
can be used. We commonly write code like "7*24*60*60" to mean the
number of seconds in a week, confident that it'll be exactly as fast
as writing "604800", and there's no particular reason that method
calls can't get the same optimization, other than that it hasn't been
done yet.

While dedicated syntax might be as good, it also wouldn't help with
string methods (or int methods - I don't see it a lot currently, but
maybe (1234).to_bytes() could become more popular), and it would also
be completely backward incompatible - you can't feature-test for
syntax without a lot of hassle with imports and alternates. In
contrast, code that wants to use set.frozen() can at least test for
that with a simple try/except in the same module.

Not one of the proposed syntaxes has seen any sort of strong support.
This isn't the first time people have proposed a syntactic form for
frozensets, and it never achieves sufficient consensus to move
forward.

> With f{...} you have a nice syntax that clearly creates a frozenset directly and that can be used for repr. This is some actual code that I recently wrote using frozensets to represent monomials in a sparse representation of a multivariate polynomial:
>

"Clearly" is subjective. Any syntax could be used for repr, including
{1,2,3}.frozen(), so f{1,2,3} doesn't have any particular edge there.
Personally, I think that string literals are not the same thing as
tuple/list/dict/set displays, and letter prefixes are not as useful on
the latter.

>   >>> poly = {frozenset([(1,2), (3,4)]): 2, frozenset([(0,1)]): 3}
>   >>> poly
>   {frozenset({(1, 2), (3, 4)}): 2, frozenset({(0, 1)}): 3}
>
> With the f{...} proposal you have actual syntax for this:
>
>   >>> poly = {f{(1,2), (3,4)}: 2, f{(0,1)}): 3}
>   >>> poly
>   {f{(1, 2), (3, 4)}: 2, f{(0, 1)}): 3}
>
> With .frozen() it's
>
>   >>> poly = {{(1,2), (3,4)}.frozen(): 2, f{(0,1)}.frozen()): 3}
>   >>> poly
>   ??? does the repr change?

Yes, it most certainly would change the repr. I don't see why that's an issue.

> That difference in code/repr may or may not seem like an improvement to different people but that should be the real point of discussion if talking about a frozenset literal. The performance impact of frozenset literals is not going to be noticeable in any real application.
>
> My polynomial class makes extensive use of frozensets and is something that I do need to be as fast as possible. I just looked through the code I have for that class and none of the performance sensitive routines could benefit from this because they all actually need to build their elements in a dict before converting to a frozenset anyway e.g.:
>

I don't understand polynomials as frozensets. What's the point of
representing them that way? Particularly if you're converting to and
from dicts all the time, why not represent them as dicts? Or as some
custom mapping type, if you need it to be hashable?

>     def mul(self, other):
>         """multiply two (frozenset) monomials"""
>         powermap = dict(self)
>         for g, n in other:
>             other_n = powermap.get(g)
>             if other_n is None:
>                 powermap[g] = n
>             else:
>                 powermap_n = other_n + n
>                 if powermap_n:
>                     powermap[g] = powermap_n
>                 else:
>                     powermap.pop(g)
>         return frozenset(powermap.items())
>
> I've just profiled this and the call to frozenset is always dwarfed by the time taken in the preceding loop which shows how cheap converting between builtins is compared to pretty much any other code.
>

Well, yes. This sort of code isn't what's being optimized here.
Massaging data between different formats won't be enhanced by a
literal form.

> If you're using literals then of necessity you are talking about small sets. Even just using a small set over a small tuple is a hardly noticeable difference in speed in most situations:
>
> In [12]: s = {1,2,3}
>
> In [13]: t = (1,2,3)
>
> In [14]: timeit 2 in s
> 44.9 ns ± 0.17 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>
> In [15]: timeit 2 in t
> 59.9 ns ± 5.67 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>

Again, not the point of the literal form. There's no significant
difference between a set and a frozenset when testing for inclusion,
so you're testing something meaningless here.

The point of a literal form is that it is guaranteed to mean what you
intend it to mean. That's why we (usually) use {"a":1, "b":2} rather
than dict(a=1, b=2) - not because it's faster (it is, but not by that
big a margin), but because it doesn't depend on the name dict. Maybe
that's not important to your code. That's fine. Not every feature has
to benefit every programmer. A frozenset display syntax would only
benefit me in a few places. But the benefits aren't where you're
looking for them, so naturally you're not going to find them there. :)

ChrisA
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/Y45UCQZ2GW62SVRNGKD2WR57W54LKCY2/
Code of Conduct: http://python.org/psf/codeofconduct/