HNHacker News
TopNewBestAskShowJobs

T-R

1,361 karma · joined June 14, 2010

submissionscomments
T-R··on Hepburn Romanization: How to Read Japanese in the Latin Alphabet
But who is this solution for? Unless you change the pronunciation of the language, it would be adding markers around whole syllables to disambiguate - syllables that represent the kanji they came from (many of which already have phoentic components) - but those syllables would still be less dense and visually identifiable than kanji is for someone who already speaks the language.

It's similar to initialisms or emoji in English; it's of course frustrating for people who don't know them, and there are reasonable complaints about some of the extremes, but speakers/writers use them because they find them useful when communicating with an audience that's also assumed to know them.

T-R··on Hepburn Romanization: How to Read Japanese in the Latin Alphabet
A lot of material for younger readers uses similar spacing to Korean, like the Pokemon games. It works well enough when you know the vocabulary being used is limited, but even there it gets frustrating.

When you're reading, you're trying to identify a substring and map it back to the unit of meaning, which is effectively the kanji. That works seamlessly with Hangul, because it mostly blocks (as syllables) the same way as kanji, and, as you mentioned, the extra vowels and silent consonants provide more disambiguation. With kana, the length you have to scan to read, and then guess at (using only context to disambiguate), a root word, is a lot more arbitrary.

Mechanically kana is just less compressed, less aligned, and makes it harder to visually identify/scan for units of meaning. Obviously Korean shows that you can make different tradeoffs, but for Japanese losing the kanji would probably necessitate reworking Kana as well - and unless the pronunciation changed, it would probably need to be in a way that represents the pitch accent, which is a modifier on the whole syllable, rather than just a few extra consonants or vowels.

T-R··on Hepburn Romanization: How to Read Japanese in the Latin Alphabet
Spoken Japanese uses pitch accent to distinguish homophones
T-R··on Super Mario Derivations
To add something a little more practical than "it's fun" or "it's philisophical" -

For someone packaging things with Nix, it's tempting to think of derivations as just a builder - you write your scripts, accept your inputs, and your result ends up in the store.

The article suggests, taking advantage of lazy evaluation, that you can put a whole state machine in Nix (i.e., lift the DSL for describing states into the Nix language). If you had a team of developers that need various customizations of something, they could describe it in Nix, and benefit from the caching of intermediate steps. If someone's just changing something at the end of the long string of customizations, they only need to build their changes.

A more concrete, if very niche, example - Back in the days when we were all training our own deep learning models, my company had assembled a list of facts for every product we could find on the internet. Our data scientists wanted to be able to test datasets with different aggregates of the lists - we needed to both build the training set, and compute it at runtime for new products. There was some compositionality to the aggregates - one run might use the mean, another would subtract the mean from every element, etc. It was time consuming and annoying to manage. One of our developers made a DSL in Haskell to describe the set of aggregates desired for a given dataset, and needed to build caching for each column. The technique the article suggests could have done this out of the box (at build time, even) - build a function and a vector for each aggregate, throw them in the nix store, and then wrap them up as a training set and a server.

T-R··on Why I still reach for Lisp and Scheme instead of Haskell
No, the issue is if the first binding is on the same line as the `let`, you are required to write, e.g.:

    someValue = let f = 9
                    fo = 10
                    foo = 123
      in f+fo+foo
rather than:

    someValue = let f = 9
      fo = 10
      foo = 123
      in f+fo+foo
I think it used to be the case that it had to be indented past the `=` or the `let` even if it was't on the same line. Note also that `in` has to be indented past `someValue`, but doesn't need to be indented as far `let`.

This is fine:

    someValue = let
      f = 9
      fo = 10
      foo = 123
      in f+fo+foo
So, it is possible to land on sane indentation, but the parser is much pickier than, e.g., Python's off-sides rule, so it takes some trial and error for new users to find it, and it can be frustrating if you're just temporarily modifying an expression to quickly try something out.

I honestly think it would be less surprising if the parser just disallowed writing the first binding on the same line as the `let` entirely, treating it only as a block, but some people (bewilderingly) do seem to prefer to write their code with the excessive indentation (I'd imagine with editor support, rather than manually maintaining the spacing).

T-R··on Why I still reach for Lisp and Scheme instead of Haskell
> inner expressions must be indented more

Not to support the parent comment, which I disagree with, but If you use multi-line let-bindings, those require that you indent not just more than the previous line, but as much as the first token after the let keyword on the previous line. It’s a very strange rule, all the more surprising because it’s inconsistent even with the rest of the language. It is totally avoidable if you, like I think most experienced haskellers do, just prefer ‘where’, but people more familiar with procedural code usually lean into using ‘let’ everywhere because it feels more familiar.

I think the strange indentation used to be required in more places - I vaguely remember running into it a lot more when I started with Haskell 20 years ago, but that was also just when I was new to the language. These days I just keep ‘let’ to a bare minimum, so it doesn’t bother me. One thing that made Elm frustrating was that it disallowed ‘where’ clauses, forcing you to deal with this weird edge case all the time.

T-R··on Virtual Boy on TV with Intelligent Systems Video Boy
I'm a huge fan of it - I was never able to get one while they were officially available, I only managed to rent one from Blockbuster a few times. I eventually picked one up on eBay in the early 2000's for $60, before they became collectors items.

The appeal is definitely somewhere between intrinsic and from the rarity - there's not just been no official way to play these games for the last 30 years, they've barely even gotten a passing mention, and even emualators have been few and far between. It was sad that Teleroboxer, Wario Land, and Mario Clash were basically lost to time.

The games definitely weren't up to to NES/SNES quality, but they were at least up to par with the average portable game at the time, which I think is what they were meant to be compared against. I see the Virtual Boy games as Game Boy games that suffered from the mechanics of the platform the platform they were released on. In that light, I think they compare pretty favorably to similar lesser-known games of the time; most WonderSwan games I've played, hell even a lot of Game Gear and Game Boy games, had much more serious gameplay issues, even though they were on hardware that was arguably less... challenging.

T-R··on Selective Applicative Functors
The article seems to assume readers are already familiar with the context, or maybe that they'll stop to read the original paper. For those who aren't familiar, Selective Applicative Functors were presented as part of the development of the Haxl library at Facebook (after they hired a series of prominent Haskellers like author of "Real World Haskell", Bryan O'Sullivan, and GHC Co-Developer Simon Marlow). Haxl is a batching framework for Haskell (to, e.g., solve the "N+1 database query problem"), which later inspired the various DataLoader libraries in other language ecosystems.

In Haskell, there's a lot of desire to be able to write effectful code as you normally would, but with different types to do things like restrict the available actions (algebraic effects) or do optimizations like batching. The approaches generally used for this (Free Monads) do this by producing a data structure kind of like an AST; Haskell's "do" notation transforms the sequential code into Monadic "bind" calls for your AST's type (like turning .then() into .flatMap() calls, if you're from Javascript), and then the AST can be manipulated before being interpreted/executed. This works, but it's fundamentally limited by the fact that the "bind" operation takes a callback to decide what to do next - a callback is arbitrary code - your "bind" implementation can't look into it to see what it might do, so there's no room to "look ahead" to do runtime optimization.

Another approach is to slide back to something less powerful than Moands, Applicative Functors, where the structure of the computation is known in advance, but the whole point of using Monads is that they can decide what to do next based on the runtime results of the previous operation - that they accept a callback - so by switching to Applicatives, by definition you're giving up the ability to make runtime choices like deciding not to run a query if the last one got no results.

Selective Functors were introduced as a middle ground - solidifying the possible decisions ahead of time, while still allowing decisions based on runtime information - for example, choosing from a set of pre-defined SQL queries, rather than just running a function that generates an arbitrary one.

T-R··on Category Theory Illustrated – Natural Transformations
Abstract Algebra, looked at through the lens of Programming, is kind of "the study of good library interface design", because it describes different ways things can be "composable", like composing functions `A -> B` and `B -> C`, or operators like `A <> A -> A`, or nestable containers `C<C<T>> -> C<T>`, with laws clearly specifying how to ensure they don't break/break expectations for users, optimizers, etc. Ways where your output is in some sense the same as your input, so you can break down problems, and don't need to use different functions for each step.

Category Theory's approach of "don't do any introspection on the elements of the set" led it to focus on some structures that turned out to be particularly common and useful (functors, natural transformations, lenses, monads, etc.). Learning these is like learning about a new interface/protocol/API you can use/implement - it lets you write less code, use out-of-the-box tools, makes your code more general, and people can know how to use it without reading as much documentation.

Focusing on these also suggests a generally useful way to approach problems/structuring your code - rather than immediately introspecting your input and picking away at it, instead think about the structual patterns of the computation, and how you could model parts of it as transformations between different data structures/instances of well-known patterns.

As a down-to-earth example, if you need to schedule a bunch of work with some dependencies, rather than diving into hacking out a while-loop with a stack, instead model it as a DAG, decide on an order to traverse it (transform to a list), and define an `execute` function (fold/reduce). This means just importing a graph library (or just programming to an interface that the graph library implements) instead of spending your day debugging. People generally associate FP with recursion, but the preferred approach is to factor out the control flow entirely; CT suggests doing that by breaking it down into transformations between data structures/representations. It's hugely powerful, though you can also imagine that someone who's never seen a DAG might now be confused why you're importing a graph library in your code for running async jobs.

T-R··on Programming languages should have a tree traversal primitive
Optics let you concisely describe the location, but defer the dereferencing, so you could definitely approximate optics, not by passing around pointers you compute with `offsetof`, but passing around functions that use `offsetof` to return memory locations to reference (read/write to). You could certainly write a composition operator for `*(*T) => List<*R>`... Some people have done something like it[1][2]:

    Account acc = getAccount();
    QVERIFY(acc.person.address.house == 20);

    auto houseLens = personL() to addressL() to houseL();
    std::function<int(int)> modifier = [](int old) { return old + 6; };
    
    Account newAcc2 = over(houseLens, newAcc1, modifier);
These also use templating to get something that still feels maybe a little less ergonomic than it could be, though.

[1] https://github.com/graninas/cpp_lenses [2] https://github.com/jonsterling/Lens.hpp

T-R··on Programming languages should have a tree traversal primitive
I definitely agree for traversals, but Lenses need some sort of primitive support - even in Haskell they're mostly generated with TemplateHaskell, and the language developers have spent a long time trying to make the `record.field` accessor syntax overloadable enough to work with lenses[1][2]. Hopefully someday we'll be free from having to memorize all the lens operators.

Optics are famously abstract in implementation, but I don't think people have trouble applying them - people seem to like JQuery/CSS selectors, and insist on `object.field` syntax; it's kind of wild that no mainstream language has a first-class way to pass around the description of a location in an arbitrary data structure.

[1] https://ghc-proposals.readthedocs.io/en/latest/proposals/002...

[2] https://ghc-proposals.readthedocs.io/en/latest/proposals/015...

T-R··on Functors, Applicatives, and Monads
> should I implement the Monad, Applicative or Functor type class?

I struggled with this when I first learned Haskell. The answer is "yes, if you can". If you have a type, and you can think of a sane way to implement `pure`, `fmap`, and `bind` that doesn't break the algebraic laws, then there's really no drawback. Same for any typeclass. It gives users access to utility functions that you might not really have to document (because they follow a standard interface) and you might not even have to maintain (when you can just use `deriving`).

Doing so will let you/users write cleaner code by allowing use of familiar tools like `do` notation, or functions from libraries that say they'll work for any Monad. It saves you from coming up with new names for those functions, and saves users from having to learn them; if I see something's a Monad, I know I can just use `do` notation; if I see something's a Monoid, I know I can get an empty one with `mempty` and use `fold` with it. As long as it's not a really strange Monad, and it doesn't break any laws, it probably just works the way it looks like it does.

If you can define `bind` et. al., but it breaks the laws, it means the abstraction is leaky - things might not work as expected, or they might work subtly differently when someone refactors the code. Probably don't do that.

If you don't implement a typeclass that you could have, it just means you might have written some code where you could've used something out of the box. Same as going through old code and realizing "this giant for-loop could've just been a few function calls if I used underscore/functools or generators".

That said, it's not too common to stumble on a whole new Monad. The Tweet type probably isn't a Monad - what does it mean for a Tweet to be parameterized on another type like `Int`, as in `Tweet<Int>`? What would it mean to `flatMap`(`bind`) a function like `Int -> Tweet<String>` on it? A Tweet is probably just a Tweet. On the other hand, it's a little easier to imagine what a `JSON<Int>` might be, and what applying a function like `Int -> JSON<String>` to it might reasonably do. Or what applying an `Int -> Graph<String>` to a `Graph<Int>` might do.

Most Monads in practice are combinations of well known ones. Usually you'll be writing some procedural code in IO, or working with a parser, and realize "I'm writing a lot of code checking for errors", "I'm tired of explicitly passing this same argument", or "I need some temporary mutable storage", or some other Effect - so you wrap up the Monad you're using with a Monad Transformer like `ExceptT`, `ReaderT`, or `StateT` in a `newtype`, derive a bunch of typeclasses, and then just delete a bunch of messy code.

T-R··on Functors, Applicatives, and Monads
Thinking too concretely about monads as boxes might make the behavior of the ListT monad transformer seem a bit surprising... unless you were already imagining your box as containing Schrodinger's cat.

I can definitely understand the author taking offense to the interaction, but now that a lot more programmers have had some experience with types like Result<T> and Promise<T> in whatever their other favorite typed language with generics is, the box/container metaphors are probably less helpful for those people than just relating the typeclasses to interfaces, and pointing out that algebraic laws are useful for limiting the leakiness of abstractions.

T-R··on Game Boy Advance Architecture – A Practical Analysis
A good place to start is with the original Game Boy - you can probably gather from the article that, to build a GBA emulator, you need to support some aspects of the Game Boy anyway (notably the sound chip). That would give you a simpler place to start getting familiar with the general concepts (like memory-mapped IO, graphics hardware, etc) and structure (like syncing emulation speed to the sound buffer) as a foundation before jumping into the more complicated GBA.

There's tons of tutorials[1], documentation[2], and tests[3] for building a Game Boy emulator, not to mention existing implementations in every language under the sun. Your favorite LLM has also surely already read them all, so you can have as much hand-holding as you'd like.

[1] https://www.youtube.com/watch?v=e87qKixKFME&list=PLVxiWMqQvh...

[2] https://gbdev.io/pandocs/

[3] https://github.com/c-sp/game-boy-test-roms

T-R··on 3D Map of Shinjuku Station in Three.js
Shinjuku station has also changed dramatically from all the construction over the last 10 or so years. I lived in Shinjuku (the ward; I was actually a few stations north on the Oedo line) in 2009/2010; was back there a few weeks ago and it was unrecognizeable. Right now, the whole area over by the Yodobashi Camera, and where they used to have the night bus pickup is all walled off under active construction (if they haven't finished already).
T-R··on GHC 9.4.2 regresses being able to do math on aarch64
Alexis also just posted a video on the GHC optimization pipeline:

https://www.youtube.com/watch?v=fdyh3YQ-ZWI

T-R··on Software Development Languages: Haskell
I think, coming from other languages, it takes a while to really absorb how much you can rely on types to tell you what a function is doing - to the degree that for some functions you can infer the implementation from them[1].

That said, Haskell documentation tends to be really lacking on some of the basics that other ecosystems get right, like where to start, shielding beginners from functions for more niche usecases, examples of how to build or deconstruct values, or end-to-end case studies. Some of the nicest things to _use_ have an API that primarily comes from typeclasses in base, and don't provide any documentation beyond a list of typeclasses. My impression has been that most Haskellers seem to be on the same page that those things need work, though - I'm optimistic about the situation improving, but it's still hard to recommend to anyone expecting the experience of working with popular libraries from more mainstream ecosystems.

[1] https://bartoszmilewski.com/2014/09/22/parametricity-money-f...

T-R··on Effective Concurrency with Algebraic Effects in Multicore OCaml
Doing things in an IO monad, you don't distinguish much between types of effect, everything's just in IO, and you just execute the action when you run into it, which means that you don't have, e.g., lookahead to see if you can do one batch request instead of 10 individual requests. There have been a few attempts to address these -

Monad transformers allow you to separate types of effects (so you can specify e.g., "this code only needs environment variables, not database access"), and, at least at compile time, select a different implementation for each effect. In Haskell, at least, though, they have a drawback of needing to define typeclass instances (interpreters) for every concrete monad stack (basically explicitly describe how they interact with each other - the n-squared instances problem. In practice, there's a bunch of template code to help mitigate the boilerplate).

Somewhat relatedly, Haxl, in an attempt to optimize effects, introduced a compiler change to identify less dynamic code (code that only needed Applicative), and Selective Functors, to allow for more optimization based on what's coming next.

Algebraic Effects (assuming I'm not incorrect to conflate them a bit with free effects/extensible effects) make things more dynamic, so you're instead effectively building the AST you want, and separately interpreting it at runtime. This should let you look at more of the call tree to decide on an execution plan. Since you'd also not be relying solely on the typeclass mechanism to pick an interpretation strategy, you should also be able to more easily describe how the interpreters compose, saving you from all the boilerplate of the transformers approach.

T-R··on Apache Arrow 4.0
This is what I was trying to get at - using column vectors gives good cache locality and lets you use SIMD for "multiply all of these by this scalar" for each column, and then for "sum all of these" for the resulting rows. I'd imagine it could also let you optimize multiplications into things like bit-shifts with minimal overhead as well, though I have no idea if that's done in practice. Maybe only tangentially related, but I feel like this talk on Halide[0] is really illustrative of the general concepts.

As others have mentioned, for some operations it can also save you from loading whole columns that aren't relevant for your transformation. The compression point in the sibling comment is definitely also relevant, especially for serialization. A whole lot of reasons to use column vectors.

Using "column-major" here might've been terminology abuse; sorry for the confusion.

[0] https://www.youtube.com/watch?v=3uiEyEKji0M

T-R··on Apache Arrow 4.0
Data science does a lot of SQL-like and linear-algebra-like transformations over a lot of data, and needs it to be reasonably performant. This means you want to do things like minimize overhead of indexing into data, and use things like SIMD instructions/GPU or parallelize work. To do this, you generally want your data in column-major format - organized as objects of arrays, rather than arrays of objects. Dataframe libraries like Pandas (which uses optimized linear algebra libraries like BLAS/LAPACK under the hood, via numpy) and the Spark Dataframe API are for working with columnar data and getting performance via SIMD or parallelization, respectively.

Generally people start off by doing these computations in a series of batch jobs (an "ETL pipeline", orchestrated with something like Airflow), to transform data into whatever shape they ultimately want it in; streaming technologies like Spark Streaming and Kafka can help with incrementally adding new rows to your data, rather than recomputing the whole thing every batch-job run.

Whenever you want to involve multiple systems or multiple libraries in your dataframe transformations, there's potentially a lot of computational overhead in serializing the dataframes or just converting them between memory representations. Arrow is a standardized format, spearheaded by the person who wrote Pandas, that attempts to match the in-memory representation, so that whether you're passing the data between libraries in-memory or writing a file for some other system to read, no unnecessary transformations need to happen to work on the data.

T-R··on Haskell: The Bad Parts, part 2 (2020)
Facebook's spam filtering: https://engineering.fb.com/2015/06/26/security/fighting-spam...
T-R··on What’s Wrong With The Honeywell Humidifier
I don't have a problem getting my fingers down everywhere but the corners of the handle, so just pushing a paper towel down there works fine; I wouldn't be surprised if people with bigger hands than mine have more trouble, though. In practice, I've mostly just been throwing it in the dishwasher.
T-R··on What’s Wrong With The Honeywell Humidifier
I have one of these; the complaint about the fan being inaccessible is legitimate, but it's otherwise clean, and easy to clean - the tank and the base can be scrubbed by hand or just thrown in the dishwasher, unlike any vaporizing humidifier I've used. I think the other complaints may be regional - and the fact that the proposed solutions all seem to be vaporizing humidifiers (or boiling water) seems telling.

I got one because in Arizona, the water quality's terrible, but the air is bone-dry to the point it turns your skin to sandpaper. The hard water means that vaporizing humidifiers fill the air with white dust that coats everything, because they vaporize the minerals. Evaporative humidifiers don't; the minerals all end up in the filter. And, while an evaporative humidifier has no trouble going through a full tank of water overnight on the lowest settings in AZ, it phsyically can't oversaturate the air like a sauna in the way that a vaporizing humidifier does.

The air in the north east just doesn't get dry enough (maybe in winter, but then you don't have the AC fighting your humidifier), and if boiling a pot of water is what the author's looking for, an evaporative humidifier just doesn't do that.

T-R··on Math Keeps Changing
> Comparing floats for equality is not a good idea

This is very true, but the mention of data science is really significant here.

If you're trying to productionize a neural network or something similar, you ideally want to be able to pin down every source of noise to make sure your results are reproducible, so you can evaluate whether a change in your results reflects a change in the data, a change in the code, or just luck from your initial weights - with a big enough network, for all you know it decided that the 10th decimal place of some feature was predictive. I wouldn't be surprised if the author brings up tanh as an example specifically because it's a common activation function.

If you're pinning all your Javascript dependencies, but the native code it dynamically links to gets ripped out from under you, it could completely mislead you about what produces good results. Similarly, if you want to be able to ship a model to run client-side, it would be nice if you could just test it on Node with your Javascript dependencies pinned and be reasonably confident it'll run the same on the browser.

Of course, if you can't do that it's not the end of the world, since you can compensate by doing enough runs, and tests on the target platform, to convince yourself that it's consistent, but it's a lot nicer if things are just as deterministic as possible.

T-R··on Typedefs
It's for defining types for existing formats, with the goal of generating types for a lot of different languages.

A lot of formats are just defined with very simple types; the machine-readable descriptions don't capture information like "this field could contain a string or an integer", or "the length of this array is dependent on the value in another field". Idris' type system is precise enough to capture that information, so specifying the type in Idris' type system should be enough to generate (at least a first-pass of) types for anything from Java to Haskell by just dropping the information the target language doesn't understand.

At the very least, it would make for some really precise, concise documentation that could save people implementing libraries a lot of time digging through prose.

Edit: Looking at the examples, it looks like it's actually value-level Idris describing Algebraic types, not Idris types, so I'm not sure if it supports dependent quantification.

T-R··on Haskell Foundation
I don't think it's a bad thing. Like them or not, they have a somewhat controversial history; a lot of people worried that their projects would fork the ecosystem, or give them too much control of it - I wouldn't be surprised if they decided that it would be better for the foundation's goal of being a community effort to not be seen as having a heavy hand in it. Some people have expressed similar concerns about Facebook and IOHK as well, just from the financial influence they might have, even though their haskell projects haven't been as close to critical infrastructure as FPComplete's have.
T-R··on Japan’s lost generation is still jobless and living with their parents
Kanji are composed from a much smaller set of radicals, so you look it up by the radicals that compose it. Some dictionaries also divide characters up by stroke count. For the past 15+ years, electronic dictionaries with handwriting recognition have been popular, and now you can just use your phone keyboard.

In practice, sometimes you can also kind of guess at the pronunciation (since characters with the same main radical often have a similar pronunciation) and see what autocomplete lists; maybe that's less true outside of the joyo kanji, though.

T-R··on Japan’s lost generation is still jobless and living with their parents
While I don't think Japan should be judged solely on its fax machines (they're ahead in some places and behind in others), there is a difference with fax machine usage in Japan vs the US - they're a trailing indicator of another bottleneck. Fax machines are so widespread in Japan largely because they ease the process of physically stamping documents with a registered stamp (a hanko), which is used instead of a signature. So far they've failed to digitize them, and it's often cited as a huge cause of bureaucratic slowdown, especially in the pandemic. One of the goals of the new prime minister is to finally start phasing them out.
T-R··on Haskell GHC 9.0.1-alpha
My understanding is that the current implementation is only groundwork for possible future performance benefits, since there are no changes to the runtime system or garbage collector, and that their current intended use is mostly for allowing library authors to encode safe session/resource handling in the type system. So, new libraries can make it a type error to fail to properly close a connection or release a shared resource.

Simon Peyton Jones talk: https://www.youtube.com/watch?v=t0mhvd3-60Y

T-R··on Haskell GHC 9.0.1-alpha
> is Rust the motivating factor behind linear types cropping up lately?

Philip Wadler wrote "Linear Types Can Change the World!" while he was working on Haskell back in 1990. I vaguely remember there were some mentions of possibly adding it a year or two before Rust was publicly announced, but it seemed like it was going to be far off at the time. I wouldn't be surprised if Rust contributed to a lot of the interest in it - Simon Peyton Jones did specifically say that he had "Rust Envy" for shipping something similar to linear types in one of his talks.

Linear Types can Change the World!: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.31.5...

Page 1 of 14Next →