HN user

sciolizer

118 karma
Posts2
Comments41
View on HN
Local Git remotes 2 months ago

You can't use a worktree without write access to the shared object database. You can use worktrees, or you can containerize your agents, but you can't do both.

Local Git remotes 2 months ago

hardlinks the index

I think you mean hardlinks the object database? The index (staging area) changes constantly. Not much point in hardlinking it.

Hardlinking the object database is the default behavior with you clone locally on linux. It's great for one-off clones such as CI/CD pipelines and agentic containers, but the benefit in terms of disk space saved is short-lived: as repos evolve independently, they replace their packfiles with new ones, and the new ones will not be hardlinked because they don't contain the same objects.

I like to keep bare repositories in dropbox, but I use `--no-hardlinks` when cloning, because before I did that, on one occasion, dropbox corrupted the bare repo, and my working repo was corrupted as a result.

Good question. Technically, ToOwned counts:

  pub trait ToOwned {
    type Owned: Borrow<Self>;
    fn clone_into(&self, target: &mut Self::Owned) { ... }
  }
Here the `Owned` is technically an input to `clone_into`, but of course semantically it's still an output.

A more subtle one is `StreamExt`:

  pub trait StreamExt: Stream {
    type Item;
    fn map<T, F>(self, f: F) -> Map<Self, F>
       where F: FnMut(Self::Item) -> T,
             Self: Sized { ... }
  }
Here, the associated type `Item` is the input to the mapping function, and since the mapping function is an input to the map function, it is an input-to-an-input - which basically makes it an output. i.e. the stream "outputs" the items into the mapping function. (aka two contravariants make a covariant).

I couldn't find any more direct examples.

To clarify things a bit further, I find it helpful to think of traits as open compile-time functions that input types (including Self) and output both types and functions.

  pub trait Mul<Rhs> {
      type Output;
  
      fn mul(self, rhs: Rhs) -> Self::Output;
  }
This begins the open declaration of the compile-time `Mul` function. It has two inputs: `Rhs` and `Self`. It has two outputs: `Output` and `mul` (the top-level runtime function).

Note that we haven't defined the compile-time `Mul` function yet. We've only opened its definition. It's sort of like writing down the type of a function before we write down its implementation.

The implementation is never written down, though, because it is always the same: a lookup in a table that is constructed at compile-time. Every impl fills in one cell of the compile-time table.

  impl Mul<f32> for i32 {
    type Output = f32;
  
    fn mul(self, rhs: f32) -> Self::Output {
      self as f32 * rhs
    }
  }
This adds a single cell to the `Mul` table. In psuedo-code, it's like we are saying:
  Mul[(i32,f32)] = (Output=f32, mul={self as f32 * rhs})
The cell is a pair of a type and a function. For traits with lots of functions, the cell is going to be mostly functions.

The main thing I'm pointing out (that the author didn't already say) is that `mul={self as f32 * rhs}` is also part of the compile-time table, not just `Output=f32`. The author says that asso­ci­ated types are no more than the return of a type-level func­tion, and I want to clarify that this isn't a metaphor or mental short-hand. Traits ALWAYS HAVE BEEN type-level functions. They input types and output mostly functions. Associated types just allow them to output types in addition to outputting functions. Notice how associated types are defined inside the curly braces of an `impl`, just like the functions are.

Once you realize this, it's all very simple. I think there are a few things that obscure this simplicity from beginners:

1. `Self` is an implicit input to the compile-time function, with its own special syntax, and for many traits it is the ONLY input. When reading a book on rust, the first examples you encounter won't have (other) type parameters, and so it's easy to overlook the fact that traits are compile-time functions.

2. Rust traits are syntactically similar to object-oriented polymorphism, but semantically duals of each other, so experienced OO programmers can jump to wrong conclusions about rust traits. Rust traits are compile-time and universally typed. Object-oriented polymorphism is run-time and existentially typed.

3. Because the trait-as-compile-time-function's implementation is so highly structured (it's just a table), it can actually be run backwards as well as forwards. Like a prolog predicate, there are 2^(#inputs+#outputs) ways to "invoke" it, and the type-inference engine behaves more like a logical language than a functional language, so from a certain perspective associated types can sometimes look like inputs and type parameters can sometimes look like outputs. The reason we call them functions and not merely relations is because they conform to the rule "unique inputs determine unique outputs".

Why do you need runtime codegen? What exactly needs to be "instantiated"? Ultimately the runtime representation of a type parameter comes down to sizes and offsets. Why not have the caller pass those values into the generic method?

Large proportions of the supposedly human-produced content on the internet are actually generated by artificial intelligence networks in conjunction with paid secret media influencers in order to manufacture consumers for an increasing range of newly-normalised cultural products.

This isn’t true (yet)

It's at least partially true:

https://www.jasper.ai

Depends on what you mean by "decline". But if you're talking about stock price, then the current Shiller PE Ratios are:

Amazon = 238.08 Netflix = 219.32 Google = 73.95 Facebook = 68.36 Apple = 63.17

The Shiller PE Ratio of the S&P 500 is current ~30. All of these companies are overpriced, and Amazon and Netflix significantly so.

Answer to #1:

In the google search results, click the three vertical dots above the link and to the right of the domain. If using mobile, you'll need to switch to desktop mode to see the three dots. After clicking, an "About this result" pane will pop up to the right, probably[1]. In that pane you'll see the true link, and you can Right Click > Copy Link.

[1]: On my computer, the "About this result" pane says "BETA", so not sure if everyone can use it. It works for me in a private window, though.

I have to disagree strongly with point 2. IDEs are much better at the read path than simple text editors are. Critical to reading code quickly is being able to navigate it quickly, answering questions like "What are the subclasses of this?", "Where is this called?", "What are the methods in this interface?", "What does super do?", etc. Grep can only take you so far. Language server protocol is definitely improving things, but it still can't answer questions like "What is the chain of function calls linking the creation of this object to its eventual use?"

If I wanted to gain a more intuitive appreciation of the patterns described in the presentation by learning a language, which is the best place to start: APL, J, K, or something else?

Perhaps a better analogy would be, do you believe that matter exists beyond the observable universe?

Because of the expansion of the universe, it is theoretically possible for me to get in a space ship and travel away from you at such a high speed that we would never be able to meet again, even if at some point we both decided to head back toward each other as fast as possible, and even if we were both immortal. Our light cones would no longer intersect.

Probably you would assume that I continue to exist, despite that for all intents and purposes I cannot affect your life in anyway and therefore "don't" exist.

Similarly if I crossed the event horizon of a black hole.

I believe that a qubit is in superposition (i.e. exists in multiple miniature "universes"), because that theory produces results that are consistent with experiment. When the qubit's universes de-cohere with mine, such that for all intents and purposes I can now only ever observe the qubit in one particular state, why should I assume that the other universes have ceased to exist? Decoherence is a continuous process, just like traveling outside of a light cone, so why treat them any differently. It's rather arbitrary to say that increased distance in space preserves existence, while increased distance in phase space does not.

Do you believe god exists and has a psychology? I'm trying to understand if you have trouble believing because you don't think god would create such a universe, or if you have trouble believing because such a universe is innately unbelievable.

If the latter, then I would ask, "Do you believe the ocean is deep, even though you can only see the surface?"

Not a physicist either, but I think "local realism" is a conjunction of "local" and "realism". Bell inequality experiments show that "local realism" is false, and so we have to reject either "local" or "realism", but we don't necessarily have to reject both.

Is it too much of a simplification to say that this paper is basically advocating object-oriented design, before that term was coined?

My summary:

The paper compares two different ways of dividing a codebase into modules. The program under question is KWIC. In the first case, the modules are divided into the four major steps of executing the KWIC algorithm: Input, Circular Shift, Alphabetizing, and Output. In the second case, the modules are nearly the same, but they are more service-oriented than action-oriented, e.g. Circular Shifter and Alphabetizer instead of Circular Shift and Alphabetizing. The chief difference is the interfaces between the two. In the first case, the contract between modules is the format of the data structures being passed from step to step. In the second case, the data structures are encapsulated, and the contract between modules is the api for manipulating the hidden data structures.

The author argues that the second way is better in terms of changeability, independence of development, comprehensibility.

At this point I lost interest in the paper (because I sing in the choir), but scanning the rest, the author talks about how to avoid taking a performance penalty when using the second design, and then gives a second example, this time of a markov algorithm translator instead of a KWIC.

Here's a suggestion: design the SDK such that calling a method returns you everything you need to construct the REST call yourself: the verb, the url, the query params, the form params, and perhaps some headers.

    case class HttpCall[OutputType](
        verb: String,
        url: String,
        queryParams: List[(String,String)],
        formParams: List[(String,String)],
        resultType: Class[OutputType])

    trait Api {
      def getMyTasks(): HttpCall[List[Task]]
      def getTask(taskId: String): HttpCall[Task]
    }
The SDK can also provide a driver for making the http calls:
    trait Driver {
      def call[Out](httpCall: HttpCall[Out]): Out
    }
So now api invocations look like this:
    val myTasks: List[Task] = driver.call(api.getMyTasks())
    val aTask: Task = driver.call(api.getTask("foo"))
What's nice about this design is that the consumer can write their own driver if they don't like yours. Prefer asynchronous over synchronous? No problem!
    trait Driver {
      def call[Out](httpCall: HttpCall[Out]): Future[Out]
    }
Everything in the article except for grep-ability can be achieved with this kind of design.

It's a trade off. There's a lot more money in the first-world. But there's a lot more users in the third-world. That's why the third-world is called the majority-world.

Computer science curriculums vary wildly. And just because you were taught assembly and how a cpu works, doesn't mean you learned how to do it on unix or even x86. (My college taught with the spim mips emulator. I also taught myself dos assembly when I was younger, but this is the first time I've seen unix assembly.)

Java's lack of traits does indeed suck, and I would definitely prefer Scala over Java, but the only copy-pasting you need to do is of delegation methods. It's duplicate boilerplate, but it's not duplication of logic.

Higher Order PHP 17 years ago

List comprehensions cover 90% of the use cases, but I'm glad map and filter were not removed. List comprehensions do not have first class status. You can pass map and filter around as arguments to other functions, but you cannot pass around "for", "in", and "if".