[HN Gopher] Multiple assignment and tuple unpacking improve Pyth...
       ___________________________________________________________________
        
       Multiple assignment and tuple unpacking improve Python code
       readability (2018)
        
       Author : bjourne
       Score  : 142 points
       Date   : 2022-05-08 12:34 UTC (10 hours ago)
        
 (HTM) web link (treyhunner.com)
 (TXT) w3m dump (treyhunner.com)
        
       | a_c wrote:
       | While I agree with the article, readability is rarely precisely
       | defined. What appeared to a veteran programmer be readable can be
       | confusing to novice programmer. It then begs the question aren't
       | veterans meant to understand a program faster. I have seen teams
       | spend countless hours arguing what makes code readable
        
         | 0x0000000 wrote:
         | I share your perspective. When I saw this line below in the
         | article, I thought it was neat and might use it when called
         | for, but I can already see the comments on code review from
         | some team members I've worked with who would find it less
         | readable than the hard coded indices alternative.
         | head, *middle, tail = numbers
        
       | shadedtriangle wrote:
       | This is probably my most used C++17 feature too:
       | for (auto& [key, value] : persons)
       | 
       | The nested bindings are neat. Don't think you can do that in c++
       | yet.
        
         | quibono wrote:
         | For me it's these little things that make working with newer
         | C++ standards so much easier. Always felt like iterating over
         | anything was cumbersome. No longer the case.
        
       | [deleted]
        
       | carapace wrote:
       | I didn't see it mentioned (forgive me if I missed it) but you can
       | also assign to slices:                   >>> n = list(range(10))
       | >>> n[::3] = 'fizz'         >>> n         ['f', 1, 2, 'i', 4, 5,
       | 'z', 7, 8, 'z']
       | 
       | It's weird but occasionally useful.
        
       | BiteCode_dev wrote:
       | As a python veteran, I'm quite envious of the advanced
       | destructuring you get in some languages.
       | 
       | Unpacking doesn't let you specify default values if cardinality
       | doesn't match, nor can you extract dictionaries by keys.
       | 
       | Since operators.itemgetter and itertools.slice are limited, I end
       | up either adding a dependency or reinventing the wheel for every
       | project.
        
         | d0mine wrote:
         | there is a pattern matching in Python 3.10 (match/case) which
         | can match dictionaries.
         | https://peps.python.org/pep-0636/#going-to-the-cloud-mapping...
        
           | BiteCode_dev wrote:
           | Yes, but since it's an expression it is very verbose for a
           | simple assignment.
           | 
           | Python really needs something like:                   {foo:
           | "default", mapping} = mapping
        
         | [deleted]
        
         | z3c0 wrote:
         | > Unpacking doesn't let you specify default values if
         | cardinality doesn't match, nor can you extract dictionaries by
         | keys.
         | 
         | I'm a bit confused by this one, but maybe I'm misunderstanding.
         | Does this not accomplish what you're defining?
         | def parse_record(first=0, second=1, **all_the_others) -> dict:
         | return dict(first=first, second=second, **all_the_others)
         | dictionary = dict(first=99, third=2)
         | parse_record(**dictionary)            > {"first": 99, "second":
         | 1, "third": 2}
        
           | bjourne wrote:
           | That's packing. Unpacking would be something like this:
           | d = {'a' : 10, 'b' : 20, 'c' : 30}         {'a' : v1, 'b' :
           | v2, **others} = d         print(v1, v2, others) # prints 10,
           | 20, {'c' : 30}
        
             | BiteCode_dev wrote:
             | Exactly.
             | 
             | The working code in Python, as a function, would be:
             | def extract(mapping, *args, **kwargs):             for a in
             | args:                 yield mapping[a]             for key,
             | default in kwargs.items():                 yield
             | mapping.get(key, default)
        
         | lysecret wrote:
         | This is what i liked most working a bit in JS. How you can
         | deconstruct, even in the function definition!!! Using {}. Why
         | did they have to use that for sets in python ahhh.
         | 
         | Also using ? For attributes that might be there and being able
         | to chain that is sooo awesome.
         | 
         | These two things make for example working with nested Json in
         | JS so much better than in Python.
        
           | BiteCode_dev wrote:
           | The ? attribute would indeed be a welcome addition, but not
           | to work in the same way as in JS. In JS, it ignore null
           | values. That wouldn't work in python.
           | 
           | We would need a ? to says "ignore AttributeError, IndexError
           | and KeyError on that access".
           | 
           | However, the use of {} for set is not a problem.
           | 
           | First, remember than Python is much, much older than JS.
           | Insight is 20/20. Second, you could always use a different
           | syntax.
        
         | [deleted]
        
         | bmitc wrote:
         | > Unpacking doesn't let you specify default values if
         | cardinality doesn't match
         | 
         | Functional languages that have pattern matching have wildcard
         | matches that allow you to explicitly handle things that don't
         | match as expected and also have tail matching for lists.
         | 
         | > nor can you extract dictionaries by keys
         | 
         | I'm not exactly sure what you mean here, but you can definitely
         | do that for records in F# and maps/structs in Elixir. It's done
         | all the time.
        
           | bobbylarrybobby wrote:
           | I think the parent comment is referring to how you can do
           | e.g., `let {x, y, z} = point` (JS, object/dictionary) or `let
           | Point {x, y, z} = point} (Rust, struct) but in Python your
           | only option is `x = point.x; y = point["y"]` etc
        
             | bmitc wrote:
             | I misread their first sentence. I see now that they're
             | lamenting what I mentioned not being in Python. I was
             | thinking they meant in general and not just in Python.
        
             | BiteCode_dev wrote:
             | Indeed.
             | 
             | Now you can do:                   import operator
             | foo, bar, baz = operator.itemgetter('foo', 'bar',
             | 'baz')(mapping)
             | 
             | But it's verbose, inelegant, and doesn't handle missing
             | values.
        
       | dominotw wrote:
       | this list is awesome. made my morning.
       | 
       | anyone know why this works in python                 lst=[]
       | lst+=2, # note , in the end       print(lst) #[2]
        
         | carapace wrote:
         | The comma operator makes tuples.                   lst += (2,)
        
           | raymondh wrote:
           | Roughly, that is true. However, the actual pattern matching
           | rules are a bit more complex:                  ()          #
           | Makes a zero-tuple. The parens are required.             2,
           | # Makes a one-tuple.        (2,)        # Makes a one-tuple.
           | The parens are optional.             [2]         # Makes a
           | list of size one.        [(2)]       # Also makes a list of
           | size one.        [2,]        # Also makes a list of size one.
           | f(x, y)     # Call a function with two arguments.
           | f((x, y))   # Make a 2-tuple and call a function with one
           | argument.
        
             | carapace wrote:
             | Yeah, you're right, of course. I should have mentioned the
             | concept of "comma operator" is a heuristic, not something
             | from the grammar or semantics.
             | 
             | BTW, kind of a tangent, I've always assumed (but not
             | checked) that every "()" literal evaluates to one singleton
             | zero-tuple, do you know if that's true?
        
         | HelloNurse wrote:
         | The tuple is sequence-like enough to be concatenated with a
         | list: []+(2,) differs from []+[2] only by the type of the
         | temporary object that contains 2.
        
           | klyrs wrote:
           | The truly weird thing here is that                  [] + (2,)
           | 
           | is a                  TypeError: can only concatenate list
           | (not "tuple") to list
           | 
           | but                  L = []        L += (2,)
           | 
           | is totally cool.
           | 
           | In the end, it makes sense because I can't tell you if []+()
           | should be a tuple or a list, and even if I did, I might have
           | a different answer for ()+[]; whereas L+=bla looks like the
           | type of L should not change.
        
             | maleldil wrote:
             | They're different operations. The first is concatenation;
             | the second is extension. Concatenation only works with
             | other lists, but extension works with any iterable.
        
       | sitkack wrote:
       | This is an excellent article!
       | 
       | In functional languages this is called "destructuring
       | assignment".
       | 
       | This article could be a great jumping off point into
       | namedtuple[1], the walrus operator [2] and the Pattern
       | Matching[3] (Python 3.10), which assist into getting back into
       | the symbolic domain from the structural domain, though you will
       | have to come up with names! :)
       | 
       | Remember that namedtuple has the following additional methods
       | _make               Class method that makes a new instance from
       | an existing sequence or iterable         _asdict
       | Return a new dict which maps field names to their corresponding
       | values         _replace            Return a new instance of the
       | named tuple replacing specified fields with new values
       | _fields             Tuple of strings listing the field names
       | _field_defaults     Dictionary mapping field names to default
       | values.
       | 
       | [1]
       | https://docs.python.org/3/library/collections.html#collectio...
       | 
       | [2] https://peps.python.org/pep-0572/
       | 
       | [3] https://peps.python.org/pep-0636/
        
         | agumonkey wrote:
         | one step at a time people will realize mainstream imperative
         | programming and its derivative has been a long waste of time
        
           | nerdponx wrote:
           | I don't think that's a valid conclusion here at all.
        
             | agumonkey wrote:
             | the amount of code that was                   - point to a
             | structure / object         - declare a variable         -
             | access the object / structure field and assign it to var
             | - repeat for every information you want to extract from the
             | struct / object
             | 
             | is large and now replaced by something transparent
        
               | Espressosaurus wrote:
               | In what way is multiple assignment and tuple unpacking
               | _not_ imperative code?
        
               | sitkack wrote:
               | Assignment is not necessarily mutation. Every language,
               | including functional ones can assign values to names. You
               | have unlocked +1 reductionist pedantry.
        
         | bmitc wrote:
         | > In functional languages this is called "destructuring
         | assignment".
         | 
         | I thought it's just called pattern matching. Most functional
         | languages don't have assignment and have bindings instead.
        
           | greggyb wrote:
           | "Pattern matching", in the context of functional programming
           | languages, most often refers to the complex combination of
           | destructuring bind _and_ conditional control flow based on
           | which binding succeeded.
           | 
           | This article describing the Python feature of multiple
           | assignment demonstrates only the destructuring assignment
           | (assignment, because it is Python and these variables can be
           | reassigned). There is no control flow tied to this operation,
           | except for the exceptions that can be thrown when the LHS and
           | RHS have incompatible structure.
           | 
           | Clojure is another example of a language with destructuring
           | binding that is not tied to a control flow construct. Clojure
           | functions and `let` bindings can use a destructuring syntax
           | to bind to elements in a data structure. There is no tightly-
           | linked control flow structure that goes along with this
           | binding mechanism. It is my understanding that there are
           | several libraries available that implement pattern matching,
           | but I do not use these.
           | 
           | Most often, when I have seen pattern matching described to an
           | audience with a background in imperative languages, it is
           | compared to a super-powered switch statement. This gives the
           | impression that the important part is the control flow.
           | Languages such as Python and Clojure show that there is
           | inherent value in the destructuring assignment/bind, even out
           | of the context of control flow.
        
           | nerdponx wrote:
           | I would argue that destructuring is a special case of pattern
           | matching.
        
             | raymondh wrote:
             | IOW, the match/case tool can pattern match and destructure
             | at the same time. Som uninterestingly, it is possible to do
             | only one of those things:
             | 
             | Iterator unpacking:                   first, *middle, last
             | = t
             | 
             | is similar to a match/case with a sequence pattern:
             | match t:             case first, *middle, last:
             | ...
             | 
             | The former works for any iterable and the latter requires a
             | sequence. The former raises an error for a non-iterable and
             | the latter just skips the case for a non-sequence.
             | Otherwise, they are similar.
             | 
             | However, arguing that one is a special case of the other
             | misses the entire point of structural pattern matching. We
             | would almost never substitute the latter for the former.
             | They mostly live in different worlds because we use them
             | and think about them much differently:                   #
             | Something that would work but they we don't do         for
             | item in somedict.items():                 match item:
             | case [key, value]:                      print(key.upper())
             | print(value)                      print()
             | case _:                      raise RuntimeError('unexpected
             | kind of item')              # What we actually do
             | for key, value in somedict.items():
             | print(key.upper())             print(value)
             | print()
             | 
             | Sorry for the small rant, but I think this pedantic point
             | isn't a useful line of thinking. That concepts and
             | implementation have some overlap but should occupy a
             | different concept maps in our brains -- very much like the
             | idea that dicts can do set-like things but that we're
             | better off thinking about dicts as doing lookups and sets
             | as doing uniquification, membership-testing, and set-to-set
             | operations.
        
       | bobbyi wrote:
       | I definitely expected "multiple assignment" to refer to this
       | syntax (which Python also has)                 x = y = None
        
       | anyaya wrote:
       | I had no idea about the '*'!!
        
       | z3c0 wrote:
       | Great article. I use unpacking regularly for a lot of the reasons
       | given, as it helps ensure data is in its expected shape. However,
       | I never knew of "star assignment" eg                 first,
       | second, *others = some_tuple
       | 
       | I've opted for less-readable positional indexing several times
       | for not realizing this. Looks like I have some updates to do.
        
         | JadeNB wrote:
         | > However, I never knew of "star assignment" eg
         | first, second, *others = some_tuple
         | 
         | For what it's worth, I think that's often called "splat".
        
           | delaaxe wrote:
           | You can also do:                   first, second, *_ =
           | some_tuple
           | 
           | If you don't care about the others
        
         | jwarden wrote:
         | This reminds me of an idea I wrote about years ago called Pass-
         | Through Lists, where every assignment is implicitly a
         | destructuring assignment:
         | https://jonathanwarden.com/2014/06/19/pass-through-lists/
        
       | [deleted]
        
       | divs1210 wrote:
       | Destructuring is one of the things i love about Clojure!
       | 
       | I'm happy that js and python have also adopted it.
        
         | yawaramin wrote:
         | I'm fairly sure Python had destructuring syntax before Clojure
         | existed. I remember using it in early 2000s.
        
       | Ambolia wrote:
       | A bit sad that this stopped working in lambdas in Python 3, it
       | was nice to have in Python 2:                   (lambda (x, y):
       | x+y)((2, 3),)
        
         | raymondh wrote:
         | I also miss this capability. It was super helpful for working
         | with 2-D or 3-D point vectors:                   lambda (x1,
         | y1), (x2, y2):  return abs(x2 - x1) + abs(y2 - y1)
        
         | anfractuosity wrote:
         | That's pretty cool, shame it doesn't work in 3 too.
         | 
         | Not as nice, but:                   (lambda x, y: x+y)(*(2, 3))
         | 
         | Seems to work
        
           | klyrs wrote:
           | Now do                 lambda i,(x,y): x[i]+y[j]
           | 
           | I never really understood the rationale here. Pep8 hates
           | lambdas, which is also baffling. It seems as though tptb
           | don't like lambdas, but there wasn't enough political will to
           | remove them entirely, so they kneecapped them instead.
           | Because... more lines = more readable??
           | 
           | But we've got a walrus operator now, because... less lines =
           | more readable?
        
             | sitkack wrote:
             | You golf a lambda and then deride walrus in the same
             | comment? Please explain yourself.
             | 
             | And if you really want to make the perfect lambda, you can
             | do that with code generation and frame walking/patching.
             | 
             | http://farmdev.com/src/secrets/framehack/index.html
        
               | klyrs wrote:
               | That wasn't golf. Are you going to tell me that this is
               | more legible?                  lambda i, t: t[0][i] +
               | t[1][i]
               | 
               | edit: and... that frame hack thing is fun, but what does
               | it have to do with anything?
               | 
               | Also, note that I don't "deride" the walrus operator. But
               | it does smack of inconsistency. Are we aiming for clean
               | notation or not?
        
               | sitkack wrote:
               | You want tuple params back into an inlined function
               | declaration? You can do that with a framehack. One of the
               | examples from the link is Ruby style string
               | interpolation, f-strings in Python, before f-string
               | support.
               | 
               | You can fight Python on lambda support, or you can build
               | the language you want. It gives you the tools.
               | 
               | I am not arguing about lambda legibility, but tilting at
               | windmills rather than just walking around them. They
               | can't move.
        
               | abecedarius wrote:
               | Do you really use framehacks in production for the sake
               | of syntactic sugar like this? It's fair that Python _can_
               | do this sort of thing[0], but it seems like begging for
               | trouble to make it load-bearing without a very strong
               | reason.
               | 
               | [0] That level of access was essential when I was working
               | on https://codewords.recurse.com/issues/seven/dragon-
               | taming-wit...
        
               | sitkack wrote:
               | If the local sugar leads to a lower incidence of
               | diabetes, then yes.
               | 
               | I have never had a framehack spontaneously break. Self
               | proclaimed Pythonistas of course make a sour face, like
               | their God was offended, but that isn't a logical argument
               | against.
               | 
               | I really enjoy your compiler art, please keep it up. Are
               | you doing anything with Wasm these days?
        
               | wnoise wrote:
               | The example lambda isn't "golfing". It's just plain more
               | readable than naming the function.
               | 
               | Introducing side-effects in the middle of expressions
               | with the walrus operator is weird semantics, though
               | admittedly sometimes very convenient.
        
               | xigoi wrote:
               | I like the walrus operator for working with the regex
               | module.                   if match := re.search(r"Score =
               | (\d+)", data):             print(match[1])         else:
               | print("Not found")
        
             | tln wrote:
             | FYI here's the thread where tuple parameters were removed.
             | 
             | https://mail.python.org/archives/list/python-
             | dev@python.org/...
        
               | klyrs wrote:
               | > So the short answer is: yes, we are attached to it, we
               | will miss it. No guarantees about throwing fits, though
               | <wink>.
               | 
               | Observe: me, throwing a fit.
        
               | carapace wrote:
               | I cursed and swore for twenty full minutes when they
               | removed tuple unpacking from parameters. God that pissed
               | me off. Sure, I was one of only a handful of people who
               | ever used it, and it had it's warts, but it was so
               | elegant.
        
               | klyrs wrote:
               | >>> Hey, we don't like this feature anymore, is anybody
               | using it?"
               | 
               | >> Yes, we're using it"
               | 
               | > Okay, it's gone"
               | 
               | [rage]
               | 
               | The one thing I agree with in the thread is the confusion
               | of                 foo = lambda (x,y): x+y       foo(1,2)
               | #boom
               | 
               | I think a better approach would be to raise warnings when
               | sole parameters are parenthesized, unless written as
               | foo = lambda (x,y),: x+y
               | 
               | which is kinda gross, but cogent with the weirdness of
               | singletons:                 a, = [1]       b = (1,)
               | c = 1,
               | 
               | Alas, I've never had Guido's ear, so I shout into the
               | void.
        
               | raymondh wrote:
               | People who respect the law and love sausage should never
               | watch either of them being made ;-)
               | 
               | The referenced thread isn't Python's finest hour. A:
               | "Hey, this feature is slightly inconvenient to implement
               | in AST, let's kill it." B: "Many users haven't heard of
               | this, so let's kill it." C: "I find it useful and there's
               | not real advantage in killing it". A&B: "Okay, it's
               | gone".
               | 
               | Really, you're better-off just enjoying a language that
               | is mostly nice and mostly well-designed. Don't read the
               | development threads. It will spoil the illusion.
        
         | macintux wrote:
         | As an Erlang devotee, when I switched jobs and started writing
         | Python I was happy to find that destructuring worked for
         | function arguments, only to be disappointed when I realized
         | Python 2 was about dead and Python 3 had dropped it because "no
         | one uses it".
        
           | [deleted]
        
       | moelf wrote:
       | that's all cool and fine, but can you unpack named tuple or
       | struct (data class)? In Julia one of my favorite quality of life
       | improvement in the 1.7:
       | https://github.com/JuliaLang/julia/pull/39285
       | 
       | Example:                   julia> X = (this = 3, that = 5)
       | (this = 3, that = 5)              julia> this         ERROR:
       | UndefVarError: this not defined              julia> (;that, this)
       | = X;              julia> this         3              julia> that
       | 5
       | 
       | this works with structs as well (in fact anything with
       | getproperty() defined, so like dataframe column too)
        
         | kzrdude wrote:
         | namedtuple yes, it's a tuple so can be unpacked (by position)
         | and dataclasses can not be unpacked. But with the new pattern
         | matching, you can "unpack" dataclass objects that way!
         | 
         | https://peps.python.org/pep-0636/
        
           | moelf wrote:
           | but I'm showing unpacking by name, not by position.
        
             | kzrdude wrote:
             | I answered the question you put a question mark behind :)
        
         | girzel wrote:
         | I've enjoyed using types.SimpleNamespace:                   >>>
         | from types import SimpleNamespace         >>> d = {"one":1,
         | "two":2}         >>> ns = SimpleNamespace(**d)         >>>
         | ns.two         2
         | 
         | I consider it a bonus that the dict keys are grouped as
         | attributes on an object :)
        
           | raymondh wrote:
           | As I teacher, types.SimpleNamespace is wonderful for helping
           | people bridge between their understanding of dicts (a
           | fundamental) to classes/instances (a little less
           | fundamental).
           | 
           | However, I almost never use SimpleNamespace in real code.
           | Dicts offer extra capabilities: get, pop, popitem, clear,
           | copy, keys, values, items). Classes/instances offer extra
           | capabilities: unique and shared keys in distinct namespaces,
           | instances knowing their own type, and an elegant
           | transformation of method calls ``a.m(b, c)`` -->
           | ``type(a).m(a, b, c)``. Dataclasses and named tuples have
           | their advantages as well. In practice, it is almost never
           | that case that SimpleNamespace beats one of the alternatives.
        
         | jwilk wrote:
         | If you don't mind questionable hacks, you can implement it in
         | Python by abusing the import system:
         | 
         | https://stackoverflow.com/a/69860087
        
         | est wrote:
         | looks can be done with some weird locals().__setitem__ hack .
        
         | jwilk wrote:
         | JavaScript has it too; it's called "object destructuring":
         | 
         | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
        
         | rsfern wrote:
         | That's cool that this is in Julia Base! Do you know how it
         | compares to Unpack.jl? I love this feature because you can do
         | partial unpacking by name
         | 
         | https://github.com/mauro3/Unpack.jl
        
           | moelf wrote:
           | Unpack.jl happened before this got added to Julia Base.
           | 
           | You can always partially unpack, just only put what you need
           | on the LHS
        
       | woodruffw wrote:
       | I've long wanted some kind of "null-filling" destructuring
       | operator in Python, to avoid cases like this:                   #
       | oops, ValueError on `item = "noequals"`         key, value =
       | item.split("=", 1)
       | 
       | That alone makes destructuring somewhat limited, unless I'm
       | either willing to validate my lengths ahead of time or do
       | exception-driven control flow. Something like `?=` perhaps, to
       | complement the walrus operator.
        
         | tech2 wrote:
         | Is empty string acceptable in place of None? If so, consider
         | partition:                 key, sep, value =
         | item.partition("=")
         | 
         | If you need to know whether there was a split/match that
         | occurred you could test sep's truthiness.
        
         | klyrs wrote:
         | Does this work for you?                 key, *value =
         | item.split("=", 1)       if value:           value = value[0]
         | else:           value = None
        
           | woodruffw wrote:
           | Yep, that's a pattern I've applied before. It's not the
           | worst, but I don't think it has the clarity that this would
           | have:                   key, value ?= item.split("=", 1)
           | 
           | Modulo bike shedding over the actual syntax, of course.
           | 
           | (Edit: the semantics of this could be even more generic, like
           | Ruby's `[...] rescue value` syntax for turning any raised
           | exception into a default value instead.)
        
             | xtreak29 wrote:
             | There was a similar PEP https://peps.python.org/pep-0505/
        
           | srfwx wrote:
           | value = value[0] if value else None
        
         | [deleted]
        
       | ttyprintk wrote:
       | I suppose I'd like the smoke-filled rooms deciding readability to
       | mandate square brackets on the left-hand side, or permit ,=
       | without a space when a tool like black has an opinion.
        
         | raymondh wrote:
         | Wouldn't it have been better for the language itself to have
         | made a decision rather than another group of people who are in
         | the business of having strong, enforced decisions about what is
         | allowed?
         | 
         | The language specifies that simple statements can be separated
         | by semicolons. Black eliminates this part of the language,
         | essentially overriding Guido's decision.
         | 
         | The language specifies that four different quoting characters
         | are available. Black eliminates this part of the language,
         | mandating double quotes in code and single quotes in a repr
         | output, essentially overriding Guido's decision.
         | 
         | The language specifies that in-line comments can start anywhere
         | on a line. Black eliminates the writer's option to line-up
         | their comments vertically, essentially overriding Guido's
         | language decision.
         | 
         | The language allows and PEP 8 encourages using whitespace to
         | aid readers in reading mathematical expressions just like sympy
         | and LaTex do: ``3 _x*2 - 2_ x +5``. Black eliminates this
         | possibility and mandates equal spacing regardless of operator
         | precedence: ``3 * x * 2 - 2 * x + 5``. Once again, this
         | overrides Guido's judgment.
         | 
         | Wouldn't be better for Guido to decide what the language allows
         | rather than have a downstream tool narrow the range of what is
         | possible?
        
           | digisign wrote:
           | Yes, and I'm looking forward to blue, and have used nero as
           | well before I knew blue existed. Like the idea of black, and
           | implementation, but its "taste" unfortunately leaves a bit to
           | be desired.
           | 
           | Pushing extra spaces into slices is another one I don't care
           | for.
        
       ___________________________________________________________________
       (page generated 2022-05-08 23:00 UTC)