To those who haven't used Roam Research or Logseq or anything similar, you are missing some really good stuff.
210 karma · joined August 11, 2020
To those who haven't used Roam Research or Logseq or anything similar, you are missing some really good stuff.
DT is a hot topic in the PL community recently. It massively enhances the capability of a type system by turning it into a comprehensive logic system, so you can encode whatever properties you'd like to enforce into a type signature. Theorem provers have been taking advantage of the Curry-Howard correspondence for some time, but the implication of DT on real-world programming is still not well understood (we need more real-world projects written in DT languages). There are also ambitious projects that want to bring DT into the mainstream.
If you are interested, you can take a look at Lean [1], Idris [2], and a few others [3,4]. Often these languages have esoteric syntax, but there are projects using a more conventional syntax, too, e.g. Cicada [5]. "The Little Typer" [6] is a pretty good introduction to this topic.
[1] https://leanprover.github.io [2] https://www.idris-lang.org [3] https://github.com/agda/agda [4] https://coq.inria.fr [5] https://cicada-lang.org [6] https://mitpress.mit.edu/books/little-typer
Rust: Result::and_then()
Scala: Option.flatMap()
JS: Promise.then()
...
I agree it can take some time to discover the similarities between these interfaces. But a monad is just that. It's so simple that we are blind to see.Unfortunately, it requires a sufficiently advanced language to express the concept of Monad. You need at least higher-kinded types and means to describe behaviors of HKTs.
Can you understand or describe monads in a less powerful language, say Rust? Definitely!
But will you appreciate the power that monads give you in Rust? Probably no.
Since GHC 9.2, with UnliftedDatatypes you can define real uninhabited types!
{-# LANGUAGE UnliftedDatatypes #-}
import GHC.Exts
data Void :: TYPE UnliftedRep where
Void :: { absurd :: forall a. a } -> VoidI believe most Chinese do think Mandarin and Cantonese are parallel and won't consider Cantonese a dialect of Mandarin. It's better to put it this way: both Mandarin and Cantonese (and many others) are "dialects" of a fictional, idealized language "Chinese". Hence, "dialect" here might be an incorrect translation of "fangyan", which literally just means "regional language".
Arch Linux wants to provide an _up-to-date_ compatible set of Haskell packages, instead of relying on the established Stackage like what NixOS does. This certainly causes frustrations as they must do a lot of patches.
End users don't benefit from this decision; they complain about a large number of tiny packages. Haskell devs don't benefit either, as they don't use packages provided by the OS -- just like any other Python/Node/Java/etc dev. Rust programmers use rustup+cargo, Python programmers use pip; likewise Haskell programmers use ghcup+cabal.
That being said, I respect their efforts and the packagers (Felix Yan, et al.). They made great efforts in updating a large number of old packages to be compatible with GHC 9.x.
This is true (when you insist on mutable shared memory), but in Haskell the safety guarantee is in fact not so far from Rust. The Haskell community is well aware of concurrency issues very early, and they developed a dozen of composable abstractions to solve these problems. (IMHO Rust is the only one that has a truly novel solution in recent years.)
In Haskell, either you have to use `unsafe` functions to share bulk of memory or you don't share memory at all (as mutability is a pita in Haskell). On top of these unsafe functions, you can build other safe abstractions, just like Rust. These abstractions are often available as higher-order functions in libraries, making them highly composable and reusable.
E.g., the canonical memory-sharing data type is `TVar`, which must be access from the `STM` monad [1], guaranteeing both atomicity and data race freedom. (BTW, STM stands for software transactional memory if you aren't familiar with it.) A major offender is `IORef`, and fortunately Haskell provides atomic operations on `IORef`s.
GHC Haskell also offers free parallelism for all pure computations via the `Eval` monad [2].
> Mistakenly instead of processing 1GB per thread, you process the same 1GB on all sixteen threads
This is highly unlikely in Haskell. Haskell emphasizes on composable combinators, so a program that does not share memory would probably look like
foldr accumulateFunc initValue . mapConcurrently processBatch . split batchSize $ data
(mapConcurrently here is a real function [3].) I'm sure C++, Java, and Rust have similar abstractions, but imperative languages make it too easy for programmers to do `for` loops, making it much more error-prone.Several array libraries provide safe parallelism. The interface is similar to Rust rayon, but the way they work differs greatly. For example, in Repa [4] you can do
computeP $ traverse inputArray newShapeFn (\getElem curIdx -> ...)
Here `traverse` results in a "delayed" array: the results are not immediately available, and what you are doing is just composing array operations. `computeP` forces the computation to happen _in parallel_. Since GHC is very capable of optimizing intermediate data structures away, this code can compile to very efficient machine code -- there is no mutability at all, yet you get very safe and efficient parallelism.PS: what Haskell is really bad at so far is safe in-place updates for arbitrary data types (like custom ADTs), which is solved by Rust cleanly. GHC 9.0 added Linear Types [5] to address this problem. Haskell and Rust are both bad at safe in-place updates of arrays, vectors, etc., and are likely to be bad for a long time -- their indices are arbitrary integers, which cannot be analyzed statically at compile time. Haskell probably can solve this problem completely when it gets dependent types [6], whereas Rust is unlikely to support DT any time soon, if ever [7].
[1] https://hackage.haskell.org/package/stm-2.5.0.1/docs/Control...
[2] https://hackage.haskell.org/package/parallel-3.2.2.0/docs/Co...
[3] https://hackage.haskell.org/package/async-2.2.4/docs/Control...
[4] https://hackage.haskell.org/package/repa
[5] https://github.com/ghc-proposals/ghc-proposals/blob/master/p...
[6] https://github.com/ghc-proposals/ghc-proposals/blob/master/p...
So yeah, to Chinese, it is stealing. But the funny thing is that Chinese also think the man in the story is keen on studying, and this idiom is actually used as a praise to people who never stop studying even during the hardships.
[1] https://github.com/ghc-proposals/ghc-proposals/blob/master/p...
They are only different superficially.
The deeper problem here is they both assume that "a new, valid value can only reference old, existing values". (For the sake of simplicity, let's pretend Haskell does not have laziness for a moment.) This is reasonable in almost any language because it enables easy inductive reasoning. Rust forbids circular references exactly for this reason, the whole borrow check builds on this principle: a new pointer becomes invalid when the old pointer becomes invalid. You cannot create them in Haskell either due to this very reason, if there is no laziness.
Techniques in Haskell, like tying the knot, work just because laziness makes it possible to reference values in the future. Unfortunately, it also creates possibilities for bottoms, which renders it unusable for a sound proof system -- Rust cannot do the same thing because borrow check should be sound and terminating.
It's easy in C++ because C++ does not guarantee validity of objects. You know, NULL is _always_ an invalid pointer. Rust and Haskell both guarantee that a constructed object is always valid. (ofc, only in most cases where the escape hatches are not used.)
> all of these can fail, and there's nothing in the type system to alert you of this (in the standard library), such failures just mindlessly throw exceptions like some Java monstrosity
There's `MonadThrow` in Control.Monad.Catch which hints that the monad in question can throw exceptions. Admittedly, partial functions like `undefined` and `error` are still usable...
> Not to mention async exceptions in Haskell, which can happen anywhere, [...] anywhere could be interrupted by an async exception
... and they can throw exceptions everywhere, just like asynchronous exceptions, but it's actually a strength! Haskell enforces a clean separation between impure code (typically in a monad) and pure code. You can only catch exceptions in the IO monad, which often lies outside of the core logic. Due to this unique strength, Haskell is one of the very few languages that can safely terminate running threads.
Impure code can become harder to write because of exceptions, but since you don't write everything in the IO monad, the problem is largely mitigated. Yes, exceptions are hard to get right, and that's exactly why other languages are trying to get rid of, but Haskell makes it quite tractable, (though still quite annoying). Rust used more Maybes and Eithers in the IO monad (to borrow jargons from Haskell), but it's also got panic, which is the actual Haskell exception counterpart.
> and the fact that every value is really of type value|undefined, due to laziness
To be pedantic, Haskell has levity polymorphism, which gives you unlifted datatypes, like in OCaml and Idris. Even older Haskell has unboxed datatypes that are not lifted.
> ...Haskell's love for untyped exceptions everywhere...
Nope, Haskell's exceptions are typed.
Surprisingly, a task as simple as web crawler can be related to so many concepts: regex, parsec, optics, conduit, concurrency, I/O...
The fun of programming Haskell is often you introduce a higher-level concept to your program to make it faster or more generalized, but the code gets shorter and more functional! Wish you a happy Haskell journey and find Haskell fun as much as I do. :-)
Things like this could be a reason why we should write in the functional style: completing a typed hole doesn't need you to reason about other implicit states.
It's supported by text properties [1] and overlays [2]. Character properties are special attributes attached to a single character, while overlays can attach attributes to a "region" and override old text properties (like div in HTML).
In Emacs,
> A button is essentially a set of text or overlay properties, attached to a stretch of text in a buffer. [3]
The Emacs display engine is sorta like an HTML render engine.
[1] https://www.gnu.org/software/emacs/manual/html_node/elisp/Te...
[2] https://www.gnu.org/software/emacs/manual/html_node/elisp/Ov...
[3] https://www.gnu.org/software/emacs/manual/html_node/elisp/Bu...