1. 11

    The real question is why browsers don’t build this in as a default feature. Slide it into the developer tools, we can ALREADY change css in there we just can’t save it for next time.

    1. 7

      You can do this in Firefox by creating chrome/userContent.css in your profile. http://kb.mozillazine.org/UserContent.css

      1. 5

        Safari supports this too (and has for at least a decade). Just pick the style sheet you want in Preferences… → Advanced.

        1. 3

          I always found that not to be user friendly though. Step 1: google where that file is stored. Step 2: Hunt for it. Then, all the features Stylish has like importing/exporting for different sites, toggle the custom styles with a couple clicks, etc, is missing.

          1. 1

            Thank you for posting this! I was a longtime Stylish/Stylus user but now I’m just going to use the built-in thing.

            Two stumbling blocks I ran into when I was trying to get this set up:

            1. I had to visit about:config and set layout.css.moz-document.content.enabled to true.
            2. Apparently userContent.css cannot be a symlink. (This is weird, since the userChrome.css file in the same directory is allowed to be a symlink.)
            1. 1

              I always found that not to be user friendly though. Step 1: google where that file is stored. Step 2: Hunt for it. Step 3: Write the code, and do 5-6 save code/restart-the-browser cycles to figure out why it’s not working.

              Then, all the features Stylish has like importing/exporting for different sites, toggle the custom styles with a couple clicks, etc, is missing.

              1. 1

                That is only useful if you want the same styles on every single website.

                1. 2

                  The same stylesheet is used on every website, yes, but you can use a (currently Mozilla-specific) selector to apply certain styles to certain sites:

                  @-moz-document url-prefix(https://johndoe.example/blog) {
                      div.post {
                          max-width: 800px;
                      }
                  }
                  
                  @-moz-document domain(washingtonpost.com) {
                      p.interstitial-link {
                          display: none;
                      }
                  }
                  

                  There’s more documentation of @document/@-moz-document on MDN.

              1. 20

                The “lacks” of Go in the article are highly opinionated and without any context of what you’re pretending to solve with the language.

                Garbage collection is something bad? Can’t disagree harder.

                The article ends with a bunch of extreme opinions like “Rust will be better than Go in every possible task

                There’re use cases for Go, use cases for Rust, for both, and for none of them. Just pick the right tool for your job and stop bragging about yours.

                You love Rust, we get it.

                1. 2

                  Yes, I would argue GC is something that’s inherently bad in this context. Actually, I’d go as far as to say that a GC is bad for any statically typed language. And Go is, essentially, statically typed.

                  It’s inherently bad since GC dictates the lack of destruction mechanisms that can be reliably used when no reference to the resource are left. In other words, you can’t have basic features like the C++ file streams that “close themselves” at the end of the scope, then they are destroyed.

                  That’s why Go has the “defer” statement, it’s there because of the GC. Otherwise, destructors could be used to defer cleanup tasks at the end of a scope.

                  So that’s what makes a GC inherently bad.

                  A GC, however, is also bad because it “implies” the language doesn’t have good resource management mechanisms.

                  There was an article posted here, about how Rust essentially has a “static GC”, since manual deallocation is almost never needed. Same goes with well written C++, it behaves just like a garbage collected language, no manual deallocation required, all of it is figured out at compile time based on your code.

                  So, essentially, a GC does what language like C++ and Rust do at compile time… but it does it at runtime. Isn’t this inherently bad ? Doing something that can be done at CT during runtime ? It’s bad from a performance perspective and also bad from a code validation perspective. And it has essentially no upsides, as far as I’ve been able to tell.

                  As far as I can tell the main “support” for GC is that they’ve always been used. But that doesn’t automatically make them good. GCs seem to be closer to a hack for a language to be easier to implement rather than a feature for a user of the language.

                  Feel free to convince me otherwise.

                  1. 11

                    It’s inherently bad since GC dictates the lack of destruction mechanisms that can be reliably used when no reference to the resource are left.

                    Why do you think this would be the case? A language with GC can also have linear or affine types for enforcing that resources are always freed and not used after they’re freed. Most languages don’t go this route because they prefer to spend their complexity budgets elsewhere and defer/try-with-resources work well in practice, but it’s certainly possible. See ATS for an example. You can also use rank-N types to a similar effect, although you are limited to a stack discipline which is not the case with linear/affine types.

                    So, essentially, a GC does what language like C++ and Rust do at compile time… but it does it at runtime. Isn’t this inherently bad ?

                    No, not necessarily. Garbage collectors can move and compact data for better cache locality and elimination of fragmentation concerns. They also allow for much faster allocation than in a language where you’re calling the equivalent of malloc under the hood for anything that doesn’t follow a clean stack discipline. Reclamation of short-lived data is also essentially free with a generational collector. There are also garbage collectors with hard bounds on pause times which is not the case in C++ where a chain of frees can take an arbitrary amount of time.

                    Beyond all of this, garbage collection allows for a language that is both simpler and more expressive. Certain idioms that can be awkward to express in Rust are quite easy in a language with garbage collection precisely because you do not need to explain to the compiler how memory will be managed. Pervasive use of persistent data structures also becomes a viable option when you have a GC that allows for effortless and efficient sharing.

                    In short, garbage collection is more flexible than Rust-style memory management, can have great performance (especially for functional languages that perform a lot of small allocations), and does not preclude use of linear or affine types for managing resources. GC is hardly a hack, and its popularity is the result of a number of advantages over the alternatives for common use cases.

                    1. 1

                      What idioms are unavailable in Rust or in modern C++, because of their lack of GC, but are available in a statically typed GC language ?

                      I perfectly agree with GC allowing for more flexibility and more concise code as far as dynamic language go, but that’s neither here nor there.

                      As for the theoretical performance benefits and real-time capabilities of a GCed language… I think the word theoretical is what I’d focus my counter upon there, because they don’t actually exist. The GC overhead is too big, in practice, to make those benefits outshine languages without runtime memory management logic.

                      1. 9

                        I’m not sure about C++, but there are functions you can write in OCaml and Haskell (both statically typed) that cannot be written in Rust because they abstract over what is captured by the closure, and Rust makes these things explicit.

                        The idea that all memory should be explicitly tracked and accounted for in the semantics of the language is perhaps important for a systems language, but to say that it should be true for all statically typed languages is preposterous. Languages should have the semantics that make sense for the language. Saying a priori that all languages must account for some particular feature just seems like a failure of the imagination. If it makes sense for the semantics to include explicit control over memory, then include it. If it makes sense for this not to be part of the semantics (and for a GC to be used so that the implementation of the language does not consume infinite memory), this is also a perfectly sensible decision.

                        1. 2

                          there are functions you can write in OCaml and Haskell (both statically typed) that cannot be written in Rust because they abstract over what is captured by the closure

                          Could you give me an example of this ?

                          1. 8

                            As far as I understand and have been told by people who understand Rust quite a bit better than me, it’s not possible to re-implement this code in Rust (if it is, I would be curious to see the implementation!)

                            https://gist.github.com/dbp/0c92ca0b4a235cae2f7e26abc14e29fe

                            Note that the polymorphic variables (a, b, c) get instantiated with different closures in different ways, depending on what the format string is, so giving a type to them is problematic because Rust is explicit about typing closures (they have to talk about lifetimes, etc).

                            1. 2

                              My God, that is some of the most opaque code I’ve ever seen. If it’s true Rust can’t express the same thing, then maybe it’s for the best.

                              1. 2

                                If you want to understand it (not sure if you do!), the approach is described in this paper: http://www.brics.dk/RS/98/12/BRICS-RS-98-12.pdf

                                And probably the reason why it seems so complex is because CPS (continuation-passing style) is, in general, quite hard to wrap your head around.

                                I do think that the restrictions present in this example will show up in simpler examples (anywhere where you are trying to quantify over different functions with sufficiently different memory usage, but the same type in a GC’d functional language), this is just a particular thing that I have on hand because I thought it would work in Rust but doesn’t seem to.

                                1. 2

                                  FWIW, I spent ~10 minutes trying to convert your example to Rust. I ultimately failed, but I’m not sure if it’s an actual language limitation or not. In particular, you can write closure types in Rust with 'static bounds which will ensure that the closure’s environment never borrows anything that has a lifetime shorter than the lifetime of the program. For example, Box<FnOnce(String) + 'static> is one such type.

                                  So what I mean to say is that I failed, but I’m not sure if it’s because I couldn’t wrap my head around your code in a few minutes or if there is some limitation of Rust that prevents it. I don’t think I buy your explanation, because you should technically be able to work around that by simply forbidding borrows in your closure’s environment. The actual thing where I got really hung up on was the automatic currying that Haskell has. In theory, that shouldn’t be a blocker because you can just introduce new closures, but I couldn’t make everything line up.

                                  N.B. I attempted to get any Rust program working. There is probably the separate question of whether it’s a roughly equivalent program in terms of performance characteristics. It’s been a long time since I wrote Haskell in anger, so it’s hard for me to predict what kind of copying and/or heap allocations are present in the Haskell program. The Rust program I started to write did require heap allocating some of the closures.

                    2. 5

                      It’s inherently bad since GC dictates the lack of destruction mechanisms that can be reliably used when no reference to the resource are left. In other words, you can’t have basic features like the C++ file streams that “close themselves” at the end of the scope, then they are destroyed.

                      Deterministic freeing of resources is not mutually exclusive with all forms of garbage collection. In fact, this is shown by Rust, where reference counting (Rc) does not exclude Drop. Of course, Drop may never be called when you create cycles.

                      (Unless you do not count reference counting as a form of garbage collection.)

                      1. 2

                        Well… I don’t count shared pointers (or RC pointers or w/e you wish to call them) as garbage collected.

                        If, in your vocabulary, that is garbage collection then I guess my argument would be against the “JVM style” GC where the moment of destruction can’t be determined at compile time.

                        1. 8

                          If, in your vocabulary, that is garbage collection

                          Reference counting is generally agreed to be a form of garbage collection.

                          I guess my argument would be against the “JVM style” GC where the moment of destruction can’t be determined at compile time.

                          In Rc or shared_ptr, the moment of the object’s destruction can also not be determined at compile time. Only the destruction of the Rc itself; put differently the reference count decrement can be determined at compile time.

                          I think your argument is against tracing garbage collectors. I agree that the lack of deterministic destruction is a large shortcoming of languages with tracing GCs. It effectively brings back a parallel to manual memory management through the backdoor — it requires manual resource management. You don’t have to convince me :). I once wrote a binding to Tensorflow for Go. Since Tensorflow wants memory aligned on 32-byte boundaries on amd64 and Go allocates (IIRC) on 16-byte boundaries, you have to allocate memory in C-land. However, since finalizers are not guaranteed to run, you end up managing memory objects with Close() functions. This was one of the reasons I rewrote some fairly large Tensorflow projects in Rust.

                          1. 2

                            However, since finalizers are not guaranteed to run, you end up managing memory objects with Close() functions.

                            Hmm. This seems a bit odd to me. As I understand it, Go code that binds to C libraries tend to use finalizers to free memory allocated by C. Despite the lack of a guarantee around finalizers, I think this has worked well enough in practice. What caused it to not work well in the Tensorflow environment?

                            1. 3

                              When doing prediction, you typically allocate large tensors relatively rapidly in succession. Since the wrapping Go objects are very small, the garbage collector kicks in relatively infrequently, while you are filling memory in C-land. There are definitely workarounds to put bounds on memory use, e.g. by using an object pool. But I realized that what I really want is just deterministic destruction ;). But that may be my C++ background.

                              I have rewritten all that code probably around the 1.6-1.7 time frame, so maybe things have improved. Ideally, you’d be able to hint the Go GC about the actual object sizes including C-allocated objects. Some runtimes provide support for tracking C objects. E.g. SICStus Prolog has its own malloc that counts allocations in C-land towards the SICStus heap (SICStus Prolog can raise a recoverable exception when you use up your heap).

                              1. 1

                                Interesting! Thanks for elaborating on that.

                          2. 3

                            So Python, Swift, Nim, and others all have RC memory management … according to you these are not GC languages?

                        2. 5

                          One benefit of GC is that the language can be way simpler than a language with manual memory management (either explicitly like in C/C++ or implicitly like in Rust).

                          This simplicity then can either be preserved, keeping the language simple, or spent on other worthwhile things that require complexity.

                          I agree that Go is bad, Rust is good, but let’s be honest, Rust is approaching a C++-level of complexity very rapidly as it keeps adding features with almost every release.

                          1. 1

                            you can’t have basic features like the C++ file streams that “close themselves” at the end of the scope, then they are destroyed.

                            That is a terrible point. The result of closing the file stream should always be checked and reported or you will have buggy code that can’t handle edge cases.

                            1. 0

                              You can turn off garbage collection in Go and manage memory manually, if you want.

                              It’s impractical, but possible.

                              1. 2

                                Is this actually used with any production code ? To my knowledge it was meant to be more of a feature for debugging and language developers. Rather than a true GC-less option, like the one a language like D provides.

                                1. 1

                                  Here is a shocking fact: For those of us who write programs in Go, the garbage collector is actually a wanted feature.

                                  If you work on something where having a GC is a real problem, use another language.