HN user

akubera

488 karma

[ my public key: https://keybase.io/andrewkubera; my proof: https://keybase.io/andrewkubera/sigs/lrFejFgqfBa8JC8yOc6yJjqVHcHqMiR8eoM2e9DWzgU ]

[ my other public key: https://keyoxide.org/DF564BCA14402C4D550E10FF9D3BC88718FB4D14 $2a$11$hK.oSfvqEJGRylzOeN6Pmu7kQzaCSE4rrLHKK3WBU.Iv50T4oqCF2 ]

Posts17
Comments99
View on HN
Fstrings.wtf 1 year ago

The PEP: https://peps.python.org/pep-0736/

The discussion: https://discuss.python.org/t/pep-736-keyword-argument-shorth...

The rejection: https://discuss.python.org/t/pep-736-shorthand-syntax-for-ke...

Grammar changes, in particular things used everywhere like function invocations, have to be worth paying the price for changing/adding new rules. The benefits of fewer characters and more explicit intention weren't enough to outweigh the costs.

There were other considerations: Do linters prefer one syntax to another? Does the name refer to the parameter or the argument in tooling? Should users feel pressure to name local variables the same as the function's parameters? What about more shorthand for common cases like func(x=self.x, y=self.y)?

I personally did not like the func(x=, y=) syntax. I think their example of Ruby's func(x:, y:) would actually make more sense, since it's syntax that would read less like "x equals nothing", and more "this is special syntax for passing arguments".

I'm reminded of the SMBC comic https://www.smbc-comics.com/?id=2722 which resonated with me. If it takes ~7 years to master something, you should dedicate yourself to becoming good at it. Or at least you don't have to tie your identity to what you do you right now; you can reinvent yourself and experience more from of life, but you have to give yourself the time to do so.

It's been almost 14 years since that was published, so maybe some self-reflection is due.

As others have said, Rust's ownership model prevents data races, as it can prove that references to mutable data can only be created if there are no other references to that data. Safe Rust also prevents use-after-free, double-free, and use-uninitialized errors. Does not prevent memory leaks or deadlocks.

It's easy to write code that deadlocks; make two shared pointers to the same mutex, then lock them both. This compiles without warnings:

    let mutex_a = Arc::new(Mutex::new(0_u32));
    let mutex_b = mutex_a.clone();
    
    let a = mutex_a.lock().unwrap();
    let b = mutex_b.lock().unwrap();
    println!("{}", *a + *b);

In terms of the math: the table legs are assumed to be equal length, and the wobble is caused by variations of the surface. Specifically the feet of the table are in the same plane. So you could rotate your mathematical table until all feet are secure on the plane, then cut the legs to make the top flat again (legs will not be same length, but top and bottom remain planes).

As for @Cerium's real-life usage, you have possibility of uneven legs and uneven floor (and discontinuities, like a raised floorboard) so it's obviously not guaranteed, but if the floor is warped and smooth enough, you can try.

[EDIT]: Changed wording

Looks good. One note about the video: you mention you recently watched a good video on 1-bit sound, and suggest it's worth a watch, but it doesn't appear to be linked-to in the description.

What makes you think it's not compressed? (or that the data is stored as XML?)

There's very sophisticated compression systems throughout each experiment's data acquisition pipelines. For example this paper[1] describes the ALICE experiment's system for Run3, involving FPGAs and GPUs to be able to handle 3.5TB/s from all the detectors. This one [2] outlines how HL-LHC & CMS use neural networks to fine tune compression algorithms on a per-detector basis.

Not to mention your standard data files are ROOT TFiles with TTrees which store arrays of compressed objects.

It's all pretty neat.

[1] https://arxiv.org/pdf/2106.03636

[2] https://arxiv.org/pdf/2105.01683

For the Rust crate, there is already an arbitrary limit (defaults to 100 digits) for "unbounded operations" like square_root, inverting, division. That's a compile time constant. And there's a Context object for runtime-configuration you can set with a precision (stop after `prec` digits).

But for addition, the idea is to give the complete number if you do `a + b`, otherwise you could use the context to keep the numbers within your `ctx.add(a, b)`. But after the discussions here, maybe this is too unsafe... and it should use the default precision (or a slightly larger one) in the name of safety? With a compile time flag to disable it? hmm...

I think that's the use-case for the rust_decimal crate, which is a 96-bit floating number (~28 decimal digits) which is safer and faster than the bigdecimal crate (which at its heart is a Vec<u64>, unbounded, and geared more for things like calculating sqrt(2) to 10000 places, that kind of thing). Still, people are using it for serialization, and I try to oblige.

Having user-set generic limits would be cool, and something I considered when const generics came out, but there's a lot more work to do on the basics, and I'm worried about making the interface too complicated. (And I don't want to reimplement everything.) D

I also would like a customizable parser struct, with things like localization, allowing grouping-delimiters and such (1_000_000 or 1'000'000 or 10,00,000). That could also return some kind of OutOfRange parsing error to disallow "suspicious" values, out of range. I'm not sure how that to make that generic with the serde parser, but I may some safe limits to the auto serialization code.

Especially with JSON, I'd expect there's only two kinds of numbers: normal "human" numbers, and exploit attempts.

This isn't about parsing so much as letting the users do "dangerous" math operations. The obvious one is diving by zero, but when the library offers arbitrary precision, addition becomes dangerous with regard to allocating all the digits between a small and large value

  1e10 + 1e-10 = 10000000000.0000000001
  1e10000000000000000000 + 1e-10000000000000000000 = ...
It's tough to know where to draw the lines between "safety", "speed", and "functionality" for the user.

[EDIT]: Oh I see, fix the parser to disallow such large numbers from entering the system in the first place, then you don't have to worry about adding them together. Yeah that could be a good first step towards safety. Though, I don't know how to parametrize the serde call.

I was attempting to solve this very problem in the Rust BigDecimal crate this weekend. Is it better to just let it crash with an out of memory error, or have a compile-time constant limit (I was thinking ~8 billion digits) and panic if any operation would exceed that limit with a more specific error-message (does that mean it's no longer arbitrary-precision?). Or keep some kind of overflow-state/nan, but then the complexity is shifted into checking for NaNs, which I've been trying to avoid.

Sounds like Haskell made the right call: put warnings in the docs and steer the user in the right direction. Keeps implementation simple and users in control.

To the point of the article, serde_json support is improving in the next version of BigDecimal, so you'll be able to decorate your BigDecimal fields and it'll parse numeric fields from the JSON source, rather than json -> f64 -> BigDecimal.

    #[derive(Serialize, Deserialize)]
    pub struct MyStruct {
      #[serde(with = "bigdecimal::serde::json_num")]
      value: BigDecimal,
    }
Whether or not this is a good idea is debatable[^], but it's certainly something people have been asking for.

[^] Is every part of your system, or your users' systems, going to parse with full precision?

I'd bet they have very similar performance-metrics, but the yield syntax is more extensible (i.e. you're not limited to one expression) and debug-able (you can put breakpoints within the function).

Also the name and the generator is nicer (for some definition of nice):

   >>> def square_vals(x : list):
   ...    return (v * v for v in x)
   ... 
   >>> square_vals([1,2,3])
   <generator object square_vals.<locals>.<genexpr> at 0x786b8511f5e0>

   >>> def square_vals_yields(x: list):
   ...     for v in x:
   ...         yield v * v
   ... 
   >>> square_vals_yields([1,2,3])
   <generator object square_vals_yields at 0x786b851f5ff0>


I think it's more idiomatic to pass generator-comprehensions into functions rather than return them from functions
    >>> sum((v*v for v in x))

I've had a similarly frustrating time trying to understand and wrangle the pyproject.toml builder system, (egg-layer? wheel-roller? cheese-monger?)

One thing the author might want to try is writing their own "build-backend". You can specify your own script (even use setup.py) and that will be the target of python -m build or pip wheel or presumably whatever build-frontend you use.

    # pyproject.toml
    [build-system]
    requires = ["setuptools"]
    build-backend = "setup"  # import setup.py as the build-module
    backend-path = ["."]

Then in setup.py you should write two functions:
    def build_sdist(sdist_directory, config_settings):
        ...

    def build_wheel(wheel_directory, config_settings, metadata_directory):
        ...

Where config_settings is a dictionary of the command line "--config-settings" options passed to the builder. (sys.argv does not have access to the actual invocation, I suppose to ensure frontend standardization)

example:

    $ python -m build --config-setting=foo=bar --config-setting=can-spam

    # will call 
    >>> build_sdist("the/dist/dir", {"foo": "bar", "can": "spam"})

Of course, you can extend the default setuptools build meta so you only have to do the pre-compilation or whatever your custom build step requires:
    from setuptools.build_meta import build_sdist as setuptools_build_sdist

    def build_sdist(sdist_directory, config_settings):
        # ... code-gen and copy files to source  ...

        # this will call setup.py::setup, to make things extra confusing
        return setuptools_build_sdist(sdist_directory, config_settings)
I had to create a temporary MANIFEST.in file to make sure that the setuptools build_sdist saw the generated files. Maybe there's a better way? I think the wheel "just" packages whatever the sdist produces, though that might be more difficult if you're compiling .so files or whatnot.

Still overall pretty fiddly/under-documented and a shame there seems to be a push for more dependencies rather than encouraging users to build their own solutions.

More info in PEP 517: https://peps.python.org/pep-0517/

https://addons.mozilla.org/en-US/firefox/addon/tree-style-ta...

Makes a vertical list of your tabs in the side panel, and opening links from a page makes a node visually indented directly under that tab. Very nice for grouping tabs in a hierarchy as you descend through links on a topic.

Useful for general browsing and for sites like Hacker News: you can middle click links to interesting comments on the home page, then you can go through them one by one, opening the article (which is another tab one layer deeper), and any other links from there, all "contained" in that tree. You can collapse the tree to save space and resume at a later time. After you're done you pop the stack back to the comments and read, right click and close the whole tree when done and move on to the next article you "bookmarked".

For some workflows it's very nice.

There's also the shelve[0] module which allows storing any pickleable object in a persistent key-value store, not just string/bytes. I've found it's very handy for caching while developing scripts which query remote resources, and not have to worry about serialization.

[0] https://docs.python.org/3.10/library/shelve.html

Obligatory pickle note: one should be aware of pickle security implications and should not open a "Shelf" provided by untrusted sources, or rather should treat opening a shelf (or any pickle deserialization operation for that matter) as running an arbitrary Python script (which cannot be read).

Isn't that what they ended up doing in Python3?

  $ python2
  >>> "abc" + b"123"
  'abc123'
  >>> "abc" + u"123"
  u'abc123'


  $ python3
  >>> "abc" + b"123" 
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: can only concatenate str (not "bytes") to str
  >>> "abc" + u"123"
  'abc123'
  >>> type(u"123")
  <class 'str'>

P.S. Your use of the word "simply" there is a reminder of how easy it is to underestimate the complexity of handling encoding properly, since it took years (a decade?) to port libraries from Py2 to Py3, since it doesn't just affect string literals, but general IO when reading from files and sockets.

cmd-tab to the minimized window then hold option(alt) before removing your finger from cmd will restore a window (or create a new window if none are available). I don't know if it's documented somewhere, which is a real shame because it's not obvious, but I guess that's been the behavior since Leopard (10.5).

I'd like to nominate "cryptoc", which I think is simple and just as easy to say, but I'm unsure to which internet-language standards board I should submit this proposal.

Yes, z-axis is the direction of the tape's "thickness" so electricity passes only from the thing stuck to the top to the thing stuck to the bottom (directly through the tape) without shorting the rest of the contacts.

I misread the question to be "what makes this 'z-axis' tape" rather than "which axis is the z-axis of the tape".

Summary: Explicit 'async' functions avoid implicit messiness and allow control over which sections of code are running at one time.

From what I understand, in JavaScript at least, putting `await foo()` inside an async function, splits the calling function in two, with the 2nd half being converted to a callback

I wouldn't say that's wrong, but it may be a slight over-simplification if your mental-model isn't complete. For example you can await from within a loop which would make function-splitting kind of a strange thing to think about. But yes, every `await` is a suspension point that will be resumed and the function carries on, so effectively the rest of the function becomes a callback, but let's call it a "continuation", which is the callback that continues from where we left off.

Of course it's implementation specific, but at a high level these continuations are basically normal functions which may return either the "incomplete" pair (new_awaitable, next_continuation) for `await new_awaitable` statements, or the "complete" return-value for `return` statements. (Maybe other things like exceptions, but let's not go there)

It's the event-loop/task-scheduler which keeps track of these callback/continuations: when one returns with a complete-value, the scheduler will go find the continuation that was awaiting it, and call that with the new value. If it returns a new (awaitable, continuation) object, the loop will start the awaitable and repeat until it returns a value, then resume the continuation with the value.

So you can think of await as "return to the event loop with awaitable and keep calling the callbacks until a final value is returned".

This is where the function coloring comes from: async functions need access to that event loop to keep returning to and continuing from, they know they're being called in a special way and return these special values for await/return that the event loop knows how to deal with; normal functions don't have access and just return values in the "normal" way.

Of course, to reap any benefit from this you have to have multiple tasks running "at the same time". To just await one thing which awaits one thing is not much different than a normal function call/stack. But when you're listening on sockets and fetching HTTP resources and making database queries and waiting for user responses, each executing simultaneously but on one thread (so no thread contention), that's when async shines. But note: they all must be talking to the same event loop which manages all these coroutines, running one at a time.

But what happens if "normal" functions could await?

Put another way: effectively de-color functions by making them all "async" and thus able to await. (Unless I'm misinterpreting the initial question)

From what I said above, all we have to do is change the implementation such that when a function returns, internally it actually this special "complete" variable type (with the real returned value inside). Then, with an event loop you get normal behavior and forward to whatever function had called it. Then you just add the awaits which return "incomplete" variables and you get waiting behavior.

This would work, BUT it introduces an overhead of going to the event loop checking if the return is "complete", finding the caller, and forwarding it for EVERY function call. Rather than the usual stack popping/register storing of standard languages. Optimizable? yeah probably, but it's not zero-cost, especially true in languages like Python which doesn't have an implicit event loop like JavaScript, so you're making huge changes to the implementation to begin with.

We could approach from the other side: returns stay the same, but the act of calling await creates an event loop or some object which does the "callback-the-callbacks" routine. I guess this would be the stop-the-world approach? This one function would keep looping through the callbacks until a final value reached. On second thought, this isn't different than a standard function call, because any internal `awaits` would setup their own loops and you always end up with a single returned value (or infinite loop). There's no way for multiple loops to running the continuations.

If you really don't like the keyword "async" before your function definition you could implement implicit async functions: just make any function in your language which includes an `await` implicitly become "async". But this only kicks the can down the road, as if you use await, that means you're returning continuations, and any callers need to await your value, and therefore your function is now implicitly colored.

And how do you implement the equivalent of

    async function() { return 42; }
maybe?
    function() { return 42; await; }

Implicit event loop.

Maybe this is the core of the question: for a language like JavaScript which already has an event loop, why can't we do

    function foo() { let x = await whatever(); return x; }
    console.log(foo());
The await in a non-async function could be implemented such that it goes out to the global event loop, "registers" itself as dependent on the Promise returned from "whatever()" then sit and wait for the event loop to return the value. This should sound familiar to you as the behavior of the standard async function / continuation business, but now we're trying to say that foo doesn't return a stream of continuations, (it just takes a long time to run).

While this may be doable, a problem arises if foo was called from within an async function. Part of "contract" of async programming is your function is run as usual between calls to await; at those points you return to the event loop and let other continuations run, but until the next await, shared/global variables will remain untouched because nobody else is running. If you called foo() which then starts running `whatever` on the event loop again any guarantees about ordering of events or state of variables may change unexpectedly.

I'm pretty sure stakkur means mermaidjs is a bad option for markdown, as its source form is not "useful" as a diagram, and that it's actually GitHub which is saying every "markdown parser (/renderer) should support building diagrams as well" since they're encouraging its use by supporting the syntax.

The real criticism of the original comment is that markdown has never really been "portable"; yes there's now a hard dependency (for some definition of "hard") on mermaidjs for GitHub-flavored-markdown, but that actually brings it in line with other implementations. So has portability been obviated or just slightly changed?

Nim 1.6 5 years ago

The bugs being referred to are from optional brackets in C:

    void foo() {
        ...
        if (expr)
            doA();
            doB();
        ...
    }
which is unambiguous to the compiler, but could (and I expect would, especially when glancing through a lot of code) cause misinterpretation by a human reader. We can more easily hallucinate the brackets than the indentation.

I disagree with the idea that indentation spaces are invisible; rather they are much more visible than a pair of brackets. This actually becomes a problem when you embed/indent too much and 70% of the line is spaces. It's all you see!