HNHacker News
TopNewBestAskShowJobs

eeperson

335 karma · joined July 3, 2010

submissionscomments
eeperson··on Golang generics proposal has been accepted
I think you want generics for many useful sum types (e.g. options) so you don't have to repeat your sum type definition everywhere it is used. Also, you can have options and matching without sum types (e.g. scala).
eeperson··on The values of Emacs, the Neovim revolution, and the VSCode gorilla
> Don't use wayland. Run gnome in xorg mode.

Interestingly, for me, I only see some of the issues the GP mention when using xorg instead of wayland.

eeperson··on Firefox 84.0
> Even though the Android version is barely usable after the rework...

Yeah, the initial launch was pretty bad. However, this has improved quite a bit from the initial launch if you haven't tried it in a while.

eeperson··on The Future of Clojure
I've used both and end up writing much more Scala. There are a few reasons why this was the case for me:

  - Scala is much closer to Java making it an easier sell to other Java programmers.
  - I think static typing makes a lot more sense for larger projects
  - I found a rich static type system easier to use and more generally applicable than macros.
eeperson··on Assorted Thoughts on Zig and Rust
In my experience, it is both.
eeperson··on Hash Array Mapped Tries (HAMT) to the Rescue
Scala experimented[0] with implementing those in its standard library. I'm not sure if it made it in to the final release or not.

EDIT It looks like it did make it in: https://github.com/scala/scala/commit/d5ae93e1b35fac4002cfbf...

[0] https://github.com/scala/collection-strawman/issues/192

eeperson··on Who is the greatest knife steel metallurgist of all time?
If a fixed blade knife would work, I suggest looking at a Morakniv Companion. They are super cheap and surprisingly well made
eeperson··on Who is the greatest knife steel metallurgist of all time?
That's why I like to carry a Spyderco Dragonfly. A small (<2.5in) knife that is legal pretty much anywhere but feels like a much bigger knife when you use it.
eeperson··on Problems with TypeScript
Do you mean:

    Failure { error: Error }
    Success<T> { value: T }
I assume you want value or error not value and error.
eeperson··on Love It or Hate It, Java Continues to Evolve
I don't think any of your description about how the JVM GC works is accurate. The default GC is generational so it generally does lots of garbage collection before it takes up the reaches the heap limit. I assume what you really want is for the Java heap to return memory to the OS. The default JVM GC isn't configured to do that but you can easily configure the JVM to do that.

> JVM is such a failure

I'm genuinely confused why you would consider that to be the case. Especially since your preferred alternative (WebASM) is just about as bad or worse by your stated measures (start up time, GC).

eeperson··on Compile-Time DI vs. Run-Time DI
Sorry, perhaps I should have been a little clearer. What I meant is that you don't need to figure out the order in which you have to construct the dependency tree. The order of the tree itself (as in what depends on what) is something you still need to specify and should know.

EDIT - You have a tree of dependencies. This is something you care about. In order to create that tree in code, you have to convert it to a linear series of statements. This conversion is something you don't really care about as long as you end up with the tree you want. Dependency injection frameworks should make it so you can declaratively specify that tree without having to worry about how it is actually constructed.

eeperson··on Compile-Time DI vs. Run-Time DI
The benefit of a dependency injection framework is that you don't have to figure out the order of the dependency tree. If you do this work manually, then you have to make sure to instantiate things in a particular order so that dependencies are initialized before the classes that depend on them. This can be big pain if you have a large number of classes and start refactoring things.

I can understand some hesitation about using a dependency injection framework. Some of them can be very hard to reason about (especially the run time frameworks). However, they don't have to be that hard to reason about. My personal favorite is a compile time dependency injection framework for scala called Macwire[1]. In scala, you might do manual dependency injection like this:

  val instantiated = new Class(dependency1, dependency2)
With Macwire you could write the same thing as:

  val instantiated = wire[Class]
The wire macro above, automatically looks up dependencies by type and creates an instance of Class with those dependencies as arguments. If you combine that macro with Scala's built in support for laziness then you don't have to worry about initialization order either. This gives you something that is fairly easy to reason about (it is basically what your were doing by hand before) and will sort our necessary dependencies for you (and error out of they are missing).

[1] https://github.com/adamw/macwire

eeperson··on So Long, Macbook. Hello Again, Linux
I've set this up before. How many video cards are you trying to use to connect these monitors? I've always had it just work if you are only using one card. In the past I've had to invoke xrandr with the --setprovideroutputsource flag (or was it --setprovideroffloadsink ?) to get multiple monitors to work with multiple video cards. Note, this only works with the open source drivers and you must be using an X server that supports randr 1.4.
eeperson··on Type erasure and reification
> But it also would have been a Pyrrhic victory, because you'd have lost the ability to run any existing Scala code, which would have scads of dependencies on other bits of the JDK as well, on Scala for .NET. At which point, what's the point?

Wouldn't it be in the same boat as scala-js? There are quite a few libraries that support scala-jvm and scala-js.

eeperson··on OpenZFS vs. Btrfs and other file systems (2017)
> You do know how journalling works, yes? Unless the drive lies about the state of data being written, in which case not even COW will save you, a journal is just as good as COW. Exceptions being coding errors but those can occur on COW with the same devastating result.

Yes, I'm aware of how journaling works. The problem with your description is that it requires both data and metadata journaling. This is rarely the case because data journaling requires that you write all of your data to the disk twice (which also requires additional seeks as well). So, nearly all in-place filesystems default to only metadata journaling for performance reasons (this is the case for ext4). This means that, in practice, if you get a system crash mid file write, you will end up with a partially updated file. On a COW filesystem you are always guaranteed the old file or the new file.

> Maybe you don't know as much about filesystems as you want to appear to?

That is possible, although I'm not sure how much I want to appear to know :P. More seriously, this is extremely condescending. Why is this necessary? If you think I'm wrong, just tell me I'm wrong. There is a pretty decent chance I'm wrong about most things. So, condescension shouldn't be required.

eeperson··on OpenZFS vs. Btrfs and other file systems (2017)
> Bitrot is rare. Over the average lifetime of most consumer desktops, the loss of any personal documents is very unlikely unless it makes up a majority of the disk. And if it does, it's most likely that it affects negligible parts of the file (ie, corruption in a frame of video).

> The risk of bitrot becomes interesting when you talk about long timeframes like a decade.

I think you are making a lot of assumptions here about frequency of bitrot, especially in the face of failing hardware. Also, consumer desktops are not the only use. There are bunch usages where the risk of bitrot is much more concerning. Or were you intending your original post to be only about consumer usage?

>Considering the capacity and price of a 2TB disk, compression isn't the immediate need of the consumer either, unless they have a NAS, where Syno and QNAP already provide integrated solutions with btrfs and ext4 both.

This kind of feels like you are saying that it isn't needed because it is already being used when it is needed. Am I misunderstanding? Consumers may want compression for smaller hard drives they already own in order to get more out of them. Also, compression can speed up disk IO in some workloads.

> The modern ext4 journaling is also very battle tested, it's a very simple and resilient physical journal, data loss in ext4 happens largely when the disk is already failing or has been cheap enough to lie about the state of data being written or not.

The issue with ext4 resilience isn't about quality of the implementation. It is a fundamental limitation of no COW filesystems. Since things are overwritten, if the system crashes mid write you can end up with partial data. COW filesystems always give you the old data or the new data. This can be worked around but that requires that all of your applications do this workaround.

> For the average consumer that sums up to "it doesn't matter enough". Something like bcachefs could change the game by being btrfs+ZFS but without the dataloss risk.

What makes you think bcachefs is going to be any better? A lot of functionality still has to be implemented. What makes you think that the implementation of this functionality will be any less error prone?

You also haven't addressed the large pile of new features provided by BTRFS/ZFS. Snapshotting alone can make it worth the price of admission for consumers. With snapshotting, you can set up a system where rollbacks after any upgrade can occur seamlessly.

eeperson··on OpenZFS vs. Btrfs and other file systems (2017)
> So why pick btrfs with it's dataloss stories or ZFS with it's complex management?

As you mentioned, the bitrot. Also, my understanding is that a COW filesystem can be more reliable than a traditional journaling filesystem in a system crash since nothing is overwritten. Plus there is compression and all of the features provided by a COW filesystem.

eeperson··on OpenZFS vs. Btrfs and other file systems (2017)
> btrfs, which I do use nowadays but I'm absolutely aware of the high risk of data corruption because the underlying code is terrible.

Is the underlying code terrible? I haven't heard any particular concerns about code quality (aside from the original implementation of RAID 5/6). Most of the issues I've heard about were due to people using features that weren't ready yet. I've heard a lot fewer of these issue since BTRFS started providing clear statuses for individual features[1]. Do you have a different impression?

[1] - https://btrfs.wiki.kernel.org/index.php/Status

eeperson··on OpenZFS vs. Btrfs and other file systems (2017)
BTRFS does provide a status for all the major features: https://btrfs.wiki.kernel.org/index.php/Status
eeperson··on A Comparison of Functional Data Structures on the JVM
Scala 2.13 greatly simplifies the external interface of the collections and no longer requires CanBuildFrom [1]. You may find them much easier to work with now.

[1] https://scala-lang.org/files/archive/api/2.13.0-M5/

eeperson··on The Java type system is broken
How is reification any more type safe than erasure?
eeperson··on From Java to Kotlin and Back Again
> https://kotlinlang.org/docs/reference/type-safe-builders.htm.... > > It's more designed to build DSLs or similar, so you'll find more documentaion/examples in that area of the docs.

Thanks for the link. However, I feel like that still doesn't give a lot of detail. Can you have multiple receivers? Can receivers be generic? Can receiver resolution be controlled by other arguments?

> It's just a closure with a compiler-added paremeter. It can be nullable if you want and exceptions work the same as any other closure. If you want async/await you'd use coroutines. I think coroutines and receivers mix just fine, you'd just make the type a suspending lambda with receiver, but I've never tried

Sorry I didn't mean to imply that Kotlin was incapable expressing all of these things together. I meant it would be desirable to express all of these things with the same tools. It seems like the you would have to express these with several different paradigms and be aware of how they interact. That also means you would need more boilerplate because you have to abstract each of these things separately.

eeperson··on From Java to Kotlin and Back Again
It is interesting to see that is possible in Kotlin. I just learned a lot about receivers (as an aside, do you know where I can find better documentation? The best I could find was a stack overflow post).

I think this issue with this is what happens when you combine DB transactions with async queries with errors and null handling? It seems like you have to combine 4 different mechanisms in order to do this (receivers, async/await, try/catch, if(null)). Is there some way to do this all with receivers? It seems like they don't really compose.

The 'command' version in Scala could be written roughly the same way for any combination of the 4 features (the types would just change). For example you could write a Scala version of your original example as:

    def test(): Unit = {
        // update() compiles but doesn't do anything to the DB
        val helloDBOperation = for {
            _ <- query()
            _ <- update()
            _ <- query2()
        } yield "Hello"
        
        val hello = db.run(helloDBOperation)
        println("$hello") // prints "Hello"
    }
This would look almost exactly the same no matter which combination of the 4 features you use.

EDIT - it looks like I somehow also accidentally replied to you with an earlier version of this comment. Unfortunately , I didn't notice until it was too old to delete or edit. Please ignore the other comment that is very similar to this one. Sorry about the confusion.

eeperson··on From Java to Kotlin and Back Again
It is interesting to see that is possible in Kotlin. I just learned a lot about receivers (as an aside, do you know where I can find better documentation? The best I could find was a stack overflow post).

I think this issue with this is what happens when you combine DB transactions with async queries with errors and null handling? It seems like you have to combine 4 different mechanisms in order to do this (receivers, async/await, try/catch, if(null)). Is there some way to do this all with receivers? It seems like they don't really compose.

The 'command' version in Scala could be written roughly the same way for any combination of the 4 features (the types would just change). For example you could write your original example as:

    def test() {
        // update() compiles but doesn't do anything to the DB
        val helloDBOperation = for {
            result1 <- query()
            _ <- update()
            result2 <- query2()
        } yield "Hello"
        

        println("$hello") // prints "Hello"
    }
eeperson··on From Java to Kotlin and Back Again
It just dawned on me that Kotlin receivers[1] and extension methods[2] are basically the equivalent to Scala implicit params and conversions respectively. Although it appears that receivers can only really be used with anonymous functions. This appears to be similar to implicit function types proposed for Dotty[3]. So the Dotty equivalent to the above would be:

    type MustBeInTransaction = implicit MyTransaction => Unit

    def makeObject(i: Int): MustBeInTransaction = {
        return { /* do something with i or whatever in a transaction */ }
    }
    
    def test(): Unit = {
        var genericArray = Array(1, 2, 3).map(makeObject)
        //genericArray.forEach { _() } // fails to compile
        transaction {
            genericArray.forEach { _() }
        }
    }
EDIT - I just noticed that this is almost exactly the same as the example used in the function types link below

[1] https://stackoverflow.com/questions/45875491/what-is-a-recei...

[2] https://kotlinlang.org/docs/reference/extensions.html

[3] https://www.scala-lang.org/blog/2016/12/07/implicit-function...

eeperson··on The myth of ‘mad’ genius
I wonder if this is a link between 'madness' and 'genius' or if this is the result of pressure put on kids to succeed at a prestigious middle school.
eeperson··on Towards Scala 3
It looks like you can force strict equality everywhere with the flag '-language:strictEquality'. See the dotty documentation for more details: http://dotty.epfl.ch/docs/reference/multiversal-equality.htm...

EDIT just realized you are m50d on reddit and probably saw the exact same comment I got that from.

eeperson··on Towards Scala 3
It already has support although it is not complete: http://dotty.epfl.ch/docs/usage/ide-support.html
eeperson··on Towards Scala 3
> Macros going wrong with complicated builds, implicits galore all over the place, just the usual complaints.

I'm not sure I totally understand this. What do you mean by "macros going wrong"? It doesn't seem like there are that many implicits used. The mostly to add methods to strings to make expressing things more concise. Do you think implicits shouldn't be used at all?

> The thing that makes sbt difficult in practice for me is twofold. I don't want to deliver hackedy shit to a client that I then have to defend if their quality control comes back complaining about it. Being able to use regular scala in sbt definitions is convenient but at the cost of making things expressable that should wisely be done another way.

> Which brings me to the second point which is getting a sbt definition from a client and then they don't want to change it, or they don't have quality control so it's just awful, or its so bad that someone has to rewrite it for the solution. The latter being the easiest to deal with, but that also costs time.

This seems contradictory. It seems like in the first paragraph is at odds with the second. In the first paragraph, it sounds like you are saying that you want to be able to write messy builds and not have anyone complain. In the second paragraph, it sounds like you are saying you don't want deal with other people's messy builds. Am I understanding correctly? If so, do you want messy builds or not? How is this related to the build tool? What build tool prevents people from writing messy builds? Are you thinking of something like maven that is very rigid?

> For those looking, what things do you feel have most improved the sbt experience?

Some things that may (since I don't know when you last looked) have improved:

* SBT 0.13 greatly improved the .sbt build file syntax (no more line breaks between settings, multi project in one file)

* SBT 0.13 also cleaned up the symbol soup. Now to define tasks/settings you only need to know 3 symbols that are fairly self explanatory ':=', '+=', and '++='. If you want to depend on another task/setting output you just have do `taskName.value` in your task/setting definition.

* A new artifact resolver has been written[1] that fixes all of the issues with ivy. It can be used today and is currently in the process of being integrated in the default SBT install.

* SBT 1 introduced a build server concept with language server protocol support that is starting to improve IDE/editor integration

* This isn't strictly SBT but IntelliJ now natively understands .sbt files so you can autocompletion and code navigation.

[1] https://github.com/coursier/coursier

eeperson··on Towards Scala 3
> SBT is the worst build tool I've seen in the 20 years of programming across a dozen languages. > > And by a very, very long margin. It has arcane syntax, is slow, is incredibly complex if you want to extend functionality

This seems a bit hyperbolic and a little vague. So, I can't really address it directly. Interestingly, I find sbt much easier to extend than most build tools because sbt tasks generally don't rely on side effects to communicate. Obviously YMMV.

> it has multiple configuration files

Doesn't every build tool have this? Or do you mean multiple config file formats? If that is the case, newer version of sbt have moved a single file format.

> the ridiculous Ivy global lock and inability to fetch Ivy artefacts in parallel etc

I can understand this concern. Ivy is kind of annoying. Fortunately, a new artifact resolver called Coursier[1] has been written and it solves this problem. You can use it now and it is currently in the process of being integrated as part of the default SBT install[2].

[1] https://github.com/coursier/coursier [2] https://github.com/sbt/sbt/issues/2997

← PreviousPage 2 of 9Next →