HNHacker News
TopNewBestAskShowJobs

bPspGiJT8Y

38 karma · joined April 14, 2023

submissionscomments
bPspGiJT8Y··on 'It's better for humans in general': The 4-day workweek is closer than you think
I'd like to believe this too, but given that medieval peasants and antique slaves worked more or less the same hours as we do I'm quite pessimistic. Whenever we automate something they just come up with a new bullshit job that adds nothing to the standards of living, instead of reducing the workload for everyone.
bPspGiJT8Y··on Algebraic Data Types for C99
> And defining a function that will only accept particular variant is nice

This is possible to achieve (or hack your way through, if you will) by parameterizing the type and using a nullary type (a type which is impossible to have) to exclude specific cases of a sum type. In Haskell this would look like this:

    data Weather a b c = Sunny a | Rainy b | Snowy c

    -- can't snow in the summer!
    onlySummerWeather :: forall a b. Weather a b Void -> String
    onlySummerWeather weather = case weather of
      Sunny _ -> "Got sunny weather"
      Rainy _ -> "Got rainy weather"
      Snowy v -> absurd v
where `absurd :: forall a. Void -> a` "if you give me something you can't ever have, I will give you anything in return".
bPspGiJT8Y··on Binance founder Changpeng Zhao sentenced to four months in prison
It is well known that Binance, among many other crimes, runs many internal trading desks which trade against Binance's own customers.
bPspGiJT8Y··on Carapace: A multi-shell completion library and binary
> so I eventually gave up and just made fish itself my nushell's completion engine

That's an interesting idea. Do you have a link to show how you did this?

bPspGiJT8Y··on Carapace: A multi-shell completion library and binary
> BTW I believe `-C` will disable some cache checking, caching is enabled by default

You're right, my memory has let me down.

> Do you have any pointers for the "load on tab" idea? I didn't turn up any good results in DDG and LLMs were just hallucinating.

The simplest implementation would be something like

    bindkey ^I init_completions

    init_completions () {
      # ... init logic here ...

      # rebind tab to complete
      bindkey ^I complete-word
      # actually do complete the initial request
      zle complete-word
    }
Edit: I see now you already figured it out, yeah that's exactly what I meant
bPspGiJT8Y··on Carapace: A multi-shell completion library and binary
> much faster than zsh script completions

I'm curious where did you get that from.

Edit: I read the note in your dotfiles repo. Yes calling `compinit` on each shell invocation is going to be really slow. That's not how you're supposed to do it, you could at least add the `-C` flag to cache the completions. Ideally you'd also use `zcompile` to compile the cache to ZSH word code. This puts my completions initializing time at ~20ms on a lower/mid-end laptop. Additionally you can do the trick `fish` does and defer the initialization of completions until the first hit of Tab key, so the impact on shell startup time is exactly 0.

bPspGiJT8Y··on Tips on how to structure your home directory (2023)
I'm shifting towards simply "not having" a home directory. Actual data is stored on other partitions, eg `~/git/project1` => `/git/project1`, `~/Pictures/Wallpapers` => `/data/Pictures/Wallpapers`, etc. The shell can be set up to spawn in some custom directory, $CDPATH can be altered for quicker navigation or completions, configurations are managed by Nix so I simply don't have a reason to look at my $HOME that often.

Programs can pollute it all they want, since I don't use it myself I also don't have to care about that.

bPspGiJT8Y··on Fast, Declarative, Reproduble and Composable Developer Environments Using Nix
Not fast at all in my experience. Just running (for the first time) what would take seconds with something like `asdf` could take up to half an hour with Nix.
bPspGiJT8Y··on How much faster are the Gnome 46 terminals?
Gnome terminal would also need some unicode handling improvements, its current score in `ucs-detect` rating is D-[0].

[0] https://ucs-detect.readthedocs.io/results.html

bPspGiJT8Y··on Currying
This function would use an anonymous record:

    splitString :: { pattern :: String, string :: String } -> Array String
    splitString { pattern, string } = ...
This function would use positional arguments:

    splitString :: Pattern -> String -> Array String
    splitString pattern string = ...
bPspGiJT8Y··on Currying
> Because possibly you return the partial assuming it's a number, and try to use it somewhere else, possibly even in another file.

Sorry, I must've been more explicit instead of implying certain usage patterns. What I meant here is that I have a hard time imagining this happening because I would start working on a function by writing its type signature. Unless my types check out, I won't be able to mark this function as "done" and jump to another part of code. So the situation "you return the partial assuming it's a number" simply can not happen, that's exactly what type checking is for. By the time I use it in another place, it has to already have been type checked.

bPspGiJT8Y··on Currying
I don't really understand the distinction between "multi-arity" and "unary" functions. In my mental model, all functions are unary, it's just that in "traditional", non-FP languages it's more common to pack arguments into tuples. That is `(a, b, c) => ...` in JS is a unary function which takes a tuple of 3 items. But the thing is, FP has tuples too (and in Haskell, with pretty much the same syntax), it's just that most of the time arguments aren't packed into tuples.

So could you elaborate to me where the distinction lies here and why is there a need to "simulate" things?

bPspGiJT8Y··on Currying
> What if you forget, not in quotes, to give the second argument?

I will get a type error and it will take me 2-3 seconds to figure out what it is about.

> Why on earth would you want to get a type error in a completely different part of the code because you got a function instead of an integer?

Why would it be in a completely different part of the code? At most it would be 2 lines away, but usually on the same line.

bPspGiJT8Y··on Currying
In Gleam it also seems to be done well: https://tour.gleam.run/functions/function-captures/
bPspGiJT8Y··on Currying
> After like ten or fifteen hours of trying to understand what currying is, I still have no idea

Maybe you could recall which learning materials you used?

bPspGiJT8Y··on Difftastic, a structural diff tool that understands syntax
Tree-sitter optimizes for performance (to use in editors), not for correctness. In fact even TS' core developers advocate for not bothering too much with correctness of grammars[1]. I imagine this constraint would be a deal-breaker for GitHub or anyone else in their position.

[1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...

bPspGiJT8Y··on Using enums to represent state in Rust
> If you have pickRandom<A|B, B|C> x y you would get nested Either's

If this wasn't the case, how would the information about what you got be retained? It's either positional, or by a tag/key (row-polymorphic variants), or none retained.

I don't see why would you want to use monadic API for approaching an "anonymous sum type" problem in the first place. As I said before, there are fundamentally just 2 operations you would want to use: inject and project. Maybe you could also mention assoc for re-association but I'd say if you're using it you're likely handling the problem the wrong way. So I still don't see how monad transformers play into this. They are a nice (decent, at least) trick for dealing with some situations but the problem we're talking about here isn't one of them.

bPspGiJT8Y··on Using enums to represent state in Rust
> Either<Either<A,B>,A>> doesn't express our intent for a function return or parameter type if we don't care about the position of A, just whether it is an A

> so we'd want all nested variations normalized to Either<A,B>.

Sorry, perhaps my thinking is shaped by nominal type systems rather than structural, but if the only thing we care about is whether the type is A, then how do we end up having Either<Either<A, B>, A>> in the first place? Thinking about this in terms of a nominal type system, the specific type you present here has to have some specific meaning associated with, specifically, this type, otherwise we would have chosen some other type. So the key thing here is that if we have Either<A, A> then it HAS to be distinct from simply A, otherwise we wouldn't have this type in the first place. Us constructing it means we associate it with a specific meaning so it has to be distinct from A. But if we DON'T care, then, I guess, we shouldn't use this type? Use the type we do care about? The same goes for Either<A, B> and Either<B, A>.

> or we need to hide the complexity by using more abstract tools like e.g. monad transformers

This is interesting, how do monad transformers relate to this problem?

bPspGiJT8Y··on Using enums to represent state in Rust
So basically the idea here is that you want to have TS-style untagged unions, but instead they're also tagged, but still unify and compose the way they do in TS? Then why couldn't you just do `{ tag: A, data: … } | { tag: B, … } | { tag: C, … }`? Wouldn't it solve your problem?

We didn't start with composability as a requirement but you're right in that if it's a goal then nesting Either's is a rather poor solution. A better fit would be variants based on row polymorphism as I described in the reply to the other poster.

It wouldn't be a 1:1 mapping to your first example though, if your union is ultimately closed (as in your first example) then you'd still need to have one extra no-op function call to unify the types. Not a big deal but row-polymorphic variants lose here. On the other hand, IMO the possibility of having them open as well is the killer feature.

Ultimately though, I don't like this style of type unification as the one happening in your first example. Shaped by the languages I'm working with, I simply don't end up in situations where I'd need something like this. I just approach the problems differently. But this is more of a subjective territory here.

bPspGiJT8Y··on Using enums to represent state in Rust
> Because your code might not need to care about the position you insert your A or B

This is understandable. But what does it have to do with "collapsing" `a | a` into `a`? Throughout your post I think you're talking about plain untagged union types but that's something the guy I've been replying to already ruled out. Position problem can be handled beautifully by variants based on row polymorphism, such as in OCaml or PureScript. There you can access the fields not by their position but by a key, like keys in objects in JS, meaning that they don't have to be ordered at all. It's like an inverse of a struct: in a struct all fields/keys are guaranteed to exist, but in a variant only one of them exists. Due to row polymorphism they can also be extensible. You can even "handle" a particular field/key and remove it from the type but keep all the other ones and delay handling them.

> you also might not care whether it's an (encoding as) Either<A,B> or SomeoneElsesEither<A,B>

This is a theoretical issue but in practice I don't think I've ever seen anyone using some non-standard Either-like datatype in languages I've dealt with. Where Either needs to be used people just use Either.

> and you also don't want to have to deal with flattening nested Either's as in the example

What would "flattening" mean here? Fundamentally there are only 2 operations you can do on a generic sum type like this: either inject a value (construct the type) or try to get the value at a certain position. You might also think pattern matching will get tedious, but that's not the case either, you can just have a function `actOnAorBorC` and call it with `actOnA`, `actOnB` and `actOnC` and do the pattern matching inside these functions.

bPspGiJT8Y··on Show HN: Tome, aka Tom's Editor – a new command-line text editor
You can try Kakoune or Helix.
bPspGiJT8Y··on Chez Scheme: Lisp with native code speed
There's also a Chez backend in development for PureScript.
bPspGiJT8Y··on Using enums to represent state in Rust
> because Either<A, Either<A, B>> cannot type check as Either<A, B>

Why would you want the former to type check as the latter? Where do you see the complexity?

bPspGiJT8Y··on Using enums to represent state in Rust
> To be concrete, I am talking about tagged, disjoint union type

But you just said "For example `A | B | A` is the same type as `A | B`". How would this be possible for tagged union types?

> that does not require naming a new type to use

> This is also not covered by the Either/Result type

It's more probable that I'm just not understanding what you're talking about, but *the only* re-usable tagged union type similar to tuples is *the* sum type.

Let's say you're dealing coffee. People want it either with sugar or without sugar. You don't want to create a new sum type CoffeeFlavor? Fine, just use Either<Sugar, NoSugar>. This is *the* equivalent of a tuple. You need more than 2 options? No problem, Either<Sugar, Either<JustABit, NoSugar>>. I don't know what else could be a "anonymous tagged union".

bPspGiJT8Y··on Using enums to represent state in Rust
So you're talking about untagged unions?

> This is useful as a shorthand when you don't want/need a new type to represent your problem, similar to tuples.

Yes this is handled perfectly by the generic sum type, you don't need untagged unions for this. Rust used to have Either in its standard library, but they removed it and kept Result only. Semantically they're the same (a ⊕ b) but Result's name implies it has something to do with some "results". Anyways nothing stops you from creating one yourself, or even using Result if you're fine with the weird-sounding name.

bPspGiJT8Y··on Using enums to represent state in Rust
> They are to enums what tuple types are to structs.

But this is just a generic sum type?

    data Sum a b = L a | R b
    infixr 5 type Sum as ⊕
    type E₂ a b z = a ⊕ b ⊕ z
    type E₃ a b c z = a ⊕ b ⊕ c ⊕ z
    -- and so on…

Here, `Eₙ` represents a sum type with at least `n` members indexed by their position, and `z` represents any type so that it's possible to keep extending the number of positions via further nesting. When you're done you set it to a type with no members:

    type E₃AndNoMore a b c = a ⊕ b ⊕ c ⊕ Void
I don't know Rust so I can't claim if it allows it, but I'm almost certain it does.
bPspGiJT8Y··on Learn Physics with Functional Programming

  fact n = do
    let n' = n - 1
    if n <= 1 then 1 else n * fact n'
Here you go. Not sure about Haskell but in PureScript it compiles. Use "<-" for functions which return a value in IO type constructor, otherwise use "let".
bPspGiJT8Y··on What does it mean for a monad to be strong?
> How would you create a non-strong monad in Haskell, and why might you want to, or is it impossible?

I think the article implies it clear enough that it's impossible. Even if your monad has nonsensical (but lawful) semantics like `Proxy t` it's still possible to use it with `strength`. BTW the `strength` function is called `sequence`.

bPspGiJT8Y··on Rob Pike explains why every programmer should know about the array languages
I got curious and decided to google around. I din't find anything from Pike specifically, but my overall impression is that the maintainers simply refuse to see significant value in sum types. They do acknowledge the benefits but not their importance. Interestingly, the overwhelming majority of Go users (at least as indicated by this[0] GH issue) do wish for Go to have sum types. My speculation for Pike in particular though is that he's not that important to have significant influence on the evolution of Go.

[0] https://github.com/golang/go/issues/19412

bPspGiJT8Y··on FP-Go: Functional programming library for Golang
Safe destructuring could be achieved with Church encoding.
Page 1 of 2Next →