HN user

ywei3410

230 karma
Posts0
Comments148
View on HN
No posts found.

There's also the new JVM option which eludes me at the moment which sweeps the strings which are promoted to the older generation and interns them.

Not certain about whether `String.intern` is permanently stored; I rather suspect that it sweeps the existing strings since iirc the java string has a hash associated with it anyway.

They are genuine issues, within Rust, but the way you /categorize/ the severity of the issue is different within language communities. They don't impact usability /much/ because of Rust's community, focus, size and niche.

Compile times in Rust are a problem, but the RLS does a good enough job of feedback and the development loop happens enough in compile time, that users don't miss it enough, compared to, say, Java which relies heavily on runtime behaviour/reflection for application behaviour.

Debugging token macros could be better, such as with a better macro-stepper, but you don't write enough macros in Rust to care heavily about it to make it a big deal, unlike in a Lisp.

Hot reloading would be nice, but not and ~essential~ part of development and deployment like in BEAM. Better reflection utilities might be nice but I doubt Rust libraries will ever be heavily dependent on it in the same way that Go or Java are.

Not really third-party; ocaml-ppx is something similar to `javax` in the Java space? But it is optional and doesn't come bundled with OCaml.

Recursive structures are only rare in Rust _because_ they suck to write and have terrible performance characteristics in the language. In languages like Haskell, OCaml and Scala using tagless initial encodings for eDSL's [2] are really, really common.

I don't write Rust like I write any semi-functional language with function composition because, well, it's _painful_ and not a good fit for the language.

`T.show foo` or `Foo.to_bar value` is a lot of syntactic overhead

The `T.()` opens a scope so OCaml code generally looks like:

``` let foo = T.(if equal zero x then show x else "not here") ```

for,

``` fn foo(t: T) -> String { if(t == T::ZERO) { t.show() } else { "not here".into() } } ```

Even when talking about usage, each language community for better or worse has a predefined workflow in mind. Your questions and workflow are specific to a particular language; you don't, for example, ask about the REPL, the compile times, debugging macros, the cold-start of the compiler, whether hot-reloading is a thing, the debugger, how it interacts with perf, how to instrument an application. Presumably because the language you use or the programs that you make don't have those components as part of their main workflow.

[1] https://github.com/ocaml-ppx [2] https://peddie.github.io/encodings/encodings-text.html

You gave a beautiful answer about programming language

You do the same thing as in Rust, Scala or Haskell and derive the printer [1]. Then at the callsite, if you know the type then you do `T.show` to print it or `T.eq`. If you don't know the type, then you pass it in at the top level as a module and then do `T.show` or `T.eq`.

Or to convert one type into another type?

If you want to convert a type, then you have a type that you want to convert from such as foo and bar, then you do `Foo.to_bar value`.

We can keep going, but you can get the point.

You _can't_ judge a language by doing what you want to do with one language in another. If I judge Rust by writing recursive data structures and complaining about performance and verbosity that's not particularly fair correct? I can't say that Dart is terrible for desktop because I can't use chrome developer tools on its canvas output and ignore it's hot-reloading server. I can't say Common Lisp code is unreadable because I don't have type annotations and ignore the REPL for introspection.

[1] https://github.com/ocaml-ppx/ppx_deriving

I don't think this list is fair at all.

No standard and easy way of implementing interfaces

Firstly another commenter has mentioned [1] modules as interfaces. There is also the object system [2] which I've seen used to great effect in similar scenarios.

Typeclasses and modular implicits are fantastic - being a heavy user of Scala and Rust; but I've always found that they tend to be best when used as an alternative to macros, for derivation purposes.

The interface and code split in OCaml is also really nice for value types and comes with no limitations; for years, we had issues in Scala because of the host platform, and in Rust exposing functions for your newtype can be oddly boilerplate heavy.

Bad standard library

The OCaml standard library is (infamously) anemic and obviously is not as fully-featured as something like Java or Python, but since alternative libraries tend to be compatible with the bundled StdLib (both use the same `option` and `list` type), it means that having libraries dependent on different ones is not a huge issue. Learning resources, can be confusing though.

I'm not sure why it's a problem certain data structures are mutable and some are persistent; the documentation is usually fairly clear and you usually have a performance characteristic in mind when selecting your data structure.

Universal equality and hashes are indeed a little annoying (here an `Eq` typeclass would be great!) but I do understand the original decision to have them inside. At the very least, it's not pointer based equality and since records and most types don't have subtypes, you don't run into as many issues. You can of course use the equality methods on the modules themselves and since `Hashtbl` and co have functors, you aren't penalized for it.

Strings are a problem-ish (what older language doesn't have string issues ha!), but there is utf-8 support now with [3].

but just two years ago it was common to use Makefiles to build OCaml projects

It is true that OCaml has a unix-y flavour, but honestly I don't really understand the snipe at make. Sure there are alternatives, but make is bundled on most distros, has a quick startup time and a lot of people are familiar with it.

More of the community is moving towards dune as well, which is pretty good; the docs could do with better indexing but there are some features such as promotion which are really nice.

was no standard way of doing compile-time metaprogramming

I haven't used ppx much so I can't really comment here.

Finally they've missed the biggest reasons to /use/ OCaml.

[1] https://news.ycombinator.com/user?id=4ad [2] https://ocaml.org/docs/objects [3] https://v2.ocaml.org/api/Stdlib.String.html#utf_8

Of regrets 3 years ago

(IANAL)

The most horrible part is that _they are allowed to drop their case anytime_ in the UK civil court. In previous cases when the accused has run out of money, the defendants have to pay their costs [1] to keep the trial running - with zero chance of being able to recoup costs because the accused will be bankrupt anyway. The UK state should not make private individuals fund the creation of novel case law; it's absolutely stupid.

[1] https://www.dpsa.uk/wp-content/uploads/2021/11/Malkiewicz-v-...

Emacs launches processes on the `PATH` so using `direnv`, you can change the `PATH` to the version of `rust-analyzer`/`gopls`/`python`/`psql`/`protoc` that you're using as well as setting the correct load paths for each language.

Emacs 29 is nigh 4 years ago

Thanks for the link; not sure how I missed that completely while going through the thread documentation.

I completely forgot about native modules completely...

I don't think I disagree with anything you've said; though I would add that the fact that not many things use threads means that it's self-fulfilling (for example I have a package in the works and it was _super_ unclear to me about what the semantics of how threads interacted with the existing processes and when you could guarantee certain things ran before others).

Yes, Racket's model is broadly as you've described (and soon ocaml will have a similar model I think?), and imo it works really well.

I meant exactly that, both core JS and ELisp share the same model of a global interpreter where blocks of code can be interleaved and yielded (both manually and in response to external events). I wanted to know what in particular about JS you were missing.

Emacs 29 is nigh 4 years ago

Regarding threads, they are definitely more co-operative co-routines than threads.

However, if I recall there are only a few calls which can switch the context; the usual culprits, such as `thread-yield`, `sleep-for`, `accept-process-output` and atomicity is guaranteed apart from when you use these calls. So you'd never be in a problem state of `setq` failing halfway, for example.

Personally, I think this model works much better with the current code in Emacs. It would be good if we could spin up domains like in Racket for true parallelization (which uses channels for communication) as well, but threads are pretty useful already if hard to code with.

I don't believe there are any locks or semaphores at all, but I'm happy to be educated.

EDIT: caveat ofc, that non-Elisp code can be parallel but the filters and sentinels will only run if they can grab the context.

Emacs threads are not parallel and are co-operative rather than preemptive. The co-operative parts mean that you can guarantee atomic updates very easily when updating global variables or buffers so you don't run into some of the more nasty issues that can happen with preemptive threading.

I agree,

You can /always/ go from `f : t -> r` to `f' : t option -> r option` with `Option.map` (ML-syntax, translate to w/e type syntax you're most familiar with), but the reverse is not true.

I think that the example is super-simplified though and I think that the central point still stands if you have multiple errors though.

I think it's worth noting that when we talk about a system operating on a set of components, it literally means a function which either reads and creates new components or entities or mutates existing components.

Most "things" don't exist in ECS. There's no singular "Mario" or "gravity". If you feel like you ought to split it into multiple identifiers for your systems (for example, treating each part of Mario like a different sprite), then you do so.

The hardest part of ECS IMO, is that dependencies between systems are super loosely defined since it's meant to be modular and the dependencies can only be tracked by components. The semantics of ECS systems are also highly technical (do you have hard sync points, how do you deal with recursive entities etc...) which means it can be quite fiddly to work with.

Yes, Haskell has green threads/co-routines.

Racket /also/ has `thread` which is a co-routine but afaik you cannot pass the co-routine off to another native thread and there are certain constraints passing data between different threads, similar to transferring ownership.

I am not aware of the SBCL ecosystem to comment.

Honestly the closure + `transpose` is really ugly as sin. I had to do a double take and reason through the steps which you took to get that particular snippet (while not reading the imperative one).