HNHacker News
TopNewBestAskShowJobs

T-R

1,361 karma · joined June 14, 2010

submissionscomments
T-R··on Bad Character: If Chinese Were Phonetic
> First, literacy isn't completely related to the writing system. Look at Spanish speaking countries, where the alphabet is more phonetic than the English alphabet.

Perhaps more to the point, Japan, which uses Chinese characters for nouns, adjectives, adverbs, and verb-roots, has a very high literacy rate. [1]*

[1] https://nces.ed.gov/pubs2014/2014008.pdf

* Not trying to cherry-pick for Japan being at the top, but Japan's not listed in the 2015 UNESCO report that's referenced all over the place. This US DoE report from 2013 seems legitimate enough.

T-R··on Bad Character: If Chinese Were Phonetic
Practically speaking, things are more nuanced than the article would imply, though - they're not 3000 completely unique characters, and at least for a lot of nouns, the radicals do serve to broadly categorize things as, e.g. "related to water", which can serve as a reading aid similar to how root words do. The author further claims "With a phonetic writing system like an alphabet or a syllabary, you need only learn a few dozen symbols and you can read most everything printed in a newspaper.", but that's only accurate insofar as reading means "pronouncing"; it's a tradeoff with being able to infer meaning.
T-R··on Nintendo to Double Production of Switch Console
They seem to be restocking in small batches and selling out immediately, at least online. According to nowinstock.net, in the US, Best Buy's been getting shipments most consistently, and received one as recently as last week. Some people find out what days they get shipments and just wait outside the door for them to open.
T-R··on Nevertheless, She Coded
It's certainly reasonable for a studies across fields with hourly pay or, e.g., including both part-time and full-time (such as the BLS survey cited by the Forbes article), but I'm not sure we can make that assumption for software development - programmer productivity in a given time period is, on its own, somewhat mythically considered to vary by orders of magnitude.
T-R··on Nevertheless, She Coded
> they didn't control for hours worked

Are the programmers in question being paid by the hour? Does programmer pay within each gender correlate closely to hours worked?

T-R··on An introduction to Japanese
Sure, Korean is phonetic, and so learning it phonetically makes sense, since you won't be forced to re-learn it again to be able to use your knowledge. Speaking from experience studying Korean, though, attaining a useable vocabulary through rote memorization is dramatically harder than if you're familiar with the 한자/漢字 words are derived from, for exactly that reason - even though hanja's not used directly so much in korean, we started studying it in the third semester of Korean class, and it was helpful, though I'd agree maybe a bit excessive there.

Games that use a strict hiragana character set are readable by natives because they're already fluent in speaking; they're unnecessarily difficult for non-native speakers, particularly since they tend to only have the bare minimum of separators between words. After living in 4 prefectures, working for a small Japanese company, and dating a native speaker for roughly a decade, I still find those games hard to read - it's just hard to identify the shapes of words, like reading English with no spaces; you have no choice but to guess where the boundaries are and sound everything out syllable by syllable, because everything's written differently from how you'd see it anywhere else.

If some people want to put off kanji or memorize phrases from a travel guide, that's their prerogative, but not teaching the compositional aspects of kanji, and leaving students to fend for themselves after selling the idea that it's all rote memorization (and seemingly to push this implausible ethnocentric dream of the language one day being exclusively romanized) is just a disservice to the students, as it was to me.

T-R··on An introduction to Japanese
They're not reasonably separable. Memorizing romaji, or even strings of hiragana, removes the associations between root-words that are related, like those radicals that let you identify menu items, and removes the visual aid for identifying root words in context. Memorizing associations between arbitrary groups of sounds without the radicals is actually harder, it just sounds easier to those intimidated by the idea of learning characters.

Given, much to Mair's displeasure, that actual native media is written with kanji, they're immediately necessary, and not studying kanji effectively bars students from any learning outside the classroom until they have - which is completely counter to giving students either the context necessary for immersion or the reinforcement for traditional study to work.

Maybe it's merited for Chinese classes, with its larger character set, but Mair's approach just sacrifices students' ability to learn on their own in favor of lowering attrition rates of intro-level classes. It's actively harmful to students aiming for fluency to build up the characters as this insurmountable mountain in the distance before they can actually use their knowledge, rather than the useful compositional blocks and visual aids that they are.

T-R··on An introduction to Japanese
> pay little or no attention to memorizing characters

I'm not usually one to speak bluntly, but, at least for Japanese, speaking from a lot of experience, this is flat out the wrong approach.

Older books like the romanzied "Japanese for Busy People" took this approach. I started with this book, it felt easy at the time, but it was immensely impractical - you don't build up enough of a base to learn from any other material. It's completely useless. After over a year, in a formal setting, I ultimately decided to just completely start over.

Currently Japanese courses mostly just focus on Hiragana and Katakana, since material for children usually has pronunciation guides over the kanji anyway. This is workable, but it just gives you no leverage where you could have some - you end up memorizing a thousand indiviaul words, some of which maybe kind of sound similar in parts, when you could instead learn a dozen charcters, and know that they share root words. Phoenetic approaches work for children who are in a situation where they can memorize from immersion with native speakers, and have additional clues like context and intonation to emphasize root words, to pick up new vocabulary.

When I started studying Chinese, we instead went with a "radicals-first" approach, and after just a short time of that, my Japanese vocabulary exploded - I could suddenly pick out all the seafood on a menu, and I started asking people mid-conversation "oh, does that use these characters?", and I could understand what it meant without them having to struggle to translate to English: Heart+Electric+Map - it's probably an electrocardiogram, especially if we're talking about hospitals. If you haven't learned the kanji, you don't even know if you can make that association from the sounds, much less how to ask that question.

That's how you get to fluency, by building up a foundation that lets you learn without active study. You can't get there by avoiding the basis of word composition in the language. Apologies if my frustration with characters-last approaches came through a little strong - they wasted a lot of my time.

T-R··on An introduction to Japanese
"standard 12pt font" is biased for latin characters, though - at the larger font size, the Chinese characters are still getting much better information compression than the latin characters, which need to expand horizontally by using several characters per unit of meaning, plus separators (spacing and hyphens) to even be readable. In practice, even adjusting for font size, paragraphs of Chinese and Japanese are almost always shorter than latin-language equivalents.

Of course, the real winner of this argument is probably Korean.

T-R··on An introduction to Japanese
Kanji isn't so bad - most of the more complex characters are built up out of smallers ones, and even the smaller ones, as the article breifly hints at, have a primary radical that roughly categorizes it - learn the radical for "water", and you can identify all the seafood on a menu, even if you can't read a single character. They're actually really helpful for both comprehension and reading speed, like learning latin root words would be for English, but a lot more consistently applicable, so it gives you a lot of leverage.

The stroke order seems annoying at first, but it's pretty consistent, and actually really helpful for memorizing. The different pronounciations also seem intimidating at first, but for the most part:

- One's for composing with other characters

- Others are mostly variations on that one for fluidity, where people will still understand you if you get it wrong

- There may be one for when it's used on its own, that gets used so often you quicly think of it as basic

- Anything else is probably an edge case that you'll never see

tl;dr: Don't let the Kanji intimidate you

T-R··on Show HN: Learn Functional Programming Using Haskell
Thanks for the link. I'm actually under the impression that the situation in Haskell will be changing in the near future with the project called "Backpack"[1][2], which Simon Peyton Jones and Simon Marlow are both involved with.

[1] http://plv.mpi-sws.org/backpack/ [2] https://ghc.haskell.org/trac/ghc/wiki/Backpack

T-R··on Distributed Systems in Haskell
Distributed Programming was one of the things flagged "Immature" in State of the Haskell Ecosystem [1]. Just out of curiosity, was that a motivation behind the project, or do you have any other thoughts on that post-project?

[1] https://github.com/Gabriel439/post-rfc/blob/master/sotu.md#d...

T-R··on Show HN: Assembly to C code Decompiler
That's more or less it. Some things you can do:

- Dataflow analysis, to get an idea of the types/range of values you're working with

- Pattern matching to try to identify higher level constructs from math to loops to jump tables.

- Compiler-specific pattern matching to identify things like function entry points

- Signature pattern matching common functions (like C standard library calls), along the lines of what's used in High Level Emulation.

T-R··on The Path to Rust
This is maybe true, given that Rust is intentionally multi-paradigm, but it's a pretty unfair comparison: My FP-style version deliberately made almost no changes to the original code aside from adding names - I even stated in the comment that I don't even know Rust (haven't touched it beyond knowing what "Affine types" means, and once having gotten someone else's "hello world" to compile for ARM). The imperative version you're comparing it to completely changes the semantics of the program, and is written by someone very familiar with the language.
T-R··on The Path to Rust
Oh, it definitely pops up a fair bit, but it really is just not-well-factored code. It's a direct parallel to having long boolean expressions, or long equations without breaking out any sub-expressions and storing them in named variables - after all, functions are just sub-expressions - which is something Code Complete specifically advocated against in procedural/OOP code.

Some people seem to fall back into it a bit when they discover point-free style (and that seems to make up a lot of what you see in mixed-paradigm code). I don't think it'd be controversial to say it's bad practice in any paradigm, so the association of it with FP is kind of like judging web programming by late 90's beginner PHP code (which, at one point in time, did describe a lot of web programming, but it was never good, and we'd like to put that behind us).

T-R··on The Path to Rust
I think the issue with procedural loops (not to be critical, just in general) is that there's no abstraction - it's not clear what's getting mutated, or what the result is (or its type), it's harder to look at the individual steps, and it's harder to refactor.

The nice thing about functions like "map", "filter", "fold", "flatmap", etc., is that they describe intent - when you see a "map", you know it's not aggregating things, just applying a function: "map" has a distinct purpose from "fold" (and depending on how pure things are, you may not even be allowed to do anything crazy).

Aside from that, the higher-order functions have pretty intuitive algebraic laws for refactoring (like with "compose" mentioned in my other comment) - It's not clear how you'd factor code out of a nested loop without understanding the whole thing, whereas "flatmap" (a.k.a. "bind" for the list monad) has laws for refactoring it.

T-R··on The Path to Rust
> a closure works fine, so `|fname| to_buff_reader(check_err(open_file(fname)))` would work

Ah, yes it would. Clearly I'm a bit too tired to be writing code, if I've overlooked function application. I suppose it doesn't have to be point-free. =)

Pretty cool, though - thanks for the info.

T-R··on The Path to Rust
Please, please, please don't associate inlining everything with functional programming. Lambda-lift and name those functions. If you name them, you can ditch most of those comments, too (by moving that information into the names). Not too familiar with Rust syntax, but (to rewrite without changing semantics) something like:

    let open_file = |fname| (fname.as_str(), fs::File::open(fname.as_str()) );

    let check_err = |(fname, f)| {
      f.and_then(|f| Ok((fname, f)))
        .expect(&format!("input file {} could not be opened", fname)) };

    let to_buff_reader = |(fname, f)| (fname, io::BufReader::new(f));

    let line_to_words = |line| {
      line.unwrap()
          .split_whitespace()
          .map(|w| w.to_string())
          .collect::<Vec<_>>().into_iter() };

    let get_file_words = |(f, file)| {
      file.lines()
          .flat_map(line_to_words)
          .collect::<HashSet<_>>().into_iter() // prune duplicates
          .map(move |word| (word, f)) };       // emit inverted index entry

    let add_to_index = |mut idx, (word, f)| {
      idx.entry(word).or_insert(Vec::new()).push(f);
      idx };

    let idx = args.iter()
      .map( compose(to_buff_reader, check_err, open_file) ) //[1]
      .flat_map(get_file_words)
      .fold(HashMap::new(), add_to_index);
[1] Does Rust have a compose function? I hope so. If not, you may need 3 successive "map"s. And stream fusion.
T-R··on Why does Haskell, in your opinion, suck?
The grandparent brought up the concept of a community problem where members from a community actively seek out and mock members of another, completely unprovoked. I won't argue whether members of the Haskell community do it or not - I haven't seen it, maybe I've just missed it, but it's terrible if they do. Whether you think it's a particularly good example or not, I'm merely pointing out that it happens a fair bit in the other direction here on HN, and the community either supports or turns a blind eye to it.

That's what's obnoxious about that comment - it's completely unprovoked trolling, and yet it was heavily upvoted. Of course the first reply disagrees with it - If I go to a thread about a Ruby library and insult people for wanting to use Ruby, you'd expect the top reply to disagree. What you'd hope not to see is for that trolling comment to be overwhelmingly upvoted to the top, and for the community to be making excuses as to why in this instance it's ok.

If someone's not interested in some content, or disagrees with it, that's one thing, but to actively find, mock, and seek community support in ostracizing people who are interested in learning it because of that interest - I'd argue that's anti-intellectual by definition. But whatever you want to call it, it's, as the grandparent comment stated, a community problem. Sadly for the author of that comment, the HN community excuses that sort of behavior - it's just different from the wonderful folks in whatever language community they're used to.

T-R··on Why does Haskell, in your opinion, suck?
I think that's changing. A lot of the over-proliferation of operators came from missing functions (like Data.Function.(&)), missing typeclasses (Like with Data.Lens or other generic operators), or missing sugar (like Applicative Do or Idris' bang notation).There's definitely some cleanup work since the ecosystem's changed so much, though.
T-R··on Why does Haskell, in your opinion, suck?
Lazy IO's an issue, but Lazy Evaluation? It's not that different from writing SQL, and the places where it is, it's a lot more consistent, predictable, and tuneable. It's usually just bad defaults like Lazy IO, or String instead of Text that get you, but there's some consensus to move away from that.

There are definitely some toxic assholes, as in any community, and it's really shitty if they're going to, e.g., /r/java and harassing people. I feel pretty strongly that the opposite is a more common problem, though, particularly on HN: the anti-intellectualism against functional programmers, and Haskell users in particular, is rampant and gets pretty offensive. Example off the top of my head - https://news.ycombinator.com/item?id=10527745 - article simply explaining a math concept gets, as what was the top comment at the time, a condescending comment calling it all "mental masturbation". I've stopped reading HN so much because it's pretty clear that people trying to learn abstract algebra and apply it to code aren't welcome here.

T-R··on Regarding JavaScript trailing commas
I like that Data.Sequence bypasses this whole issue by just not doing anything special:

    empty
      |> ("one"  , 1)
      |> ("two"  , 2)
      |> ("three", 3)
Edit: It's interesting that, when we have things that are supposed to commute, we use infix operators that create a layout that makes it difficult to reorder them.
T-R··on Building a Better Custom Haskell Prelude
Satisfying theorems is generally for the purpose of friendliness, by ensuring consistency. In this case, they had two options:

(a) be consistent as if the language had type-level literals, which resulted in nicer code in the usual case, but some unsafety on unchecked edge cases, or

(b) give more safety, but force everyone to check for the empty list even when they don't need to.

They went with (a) - it's the type of decision Haskell is (I'd argue incorrectly) criticized for not doing - people complain about having to prove things to their compiler. Of course, in hindsight, and given the language ecosystem now (and that option (b) is instead consistent with all the monad-centric libraries, etc.), (b) seems like a better choice. Which one is better could just as easily change again if empty lists became distinguished at the type-level in Prelude in the future. This is also something that would either crash, or be similarly inconsistent (just return null, causing eventual crashes instead) in any other language. It's notable, though, that the community is making effort to fix these tiny inconsistencies, rather than punting on it for backwards compatibility, as so many other ecosystems do.

T-R··on Why hasn't functional programming taken over yet?
Explicit use of recursion in functional programming is widely considered to be low level and bad form, like writing all your loops with gotos in a procedural/OOP language (as was the primary argument of the Bananas, Lenses, Envelopes, and Barbed Wire paper), it's just commonly used in intro material in a "this is how you would do this by hand" fashion. For things that fall outside of that, you always have the option of monadic loops if it makes the code clearer.
T-R··on 216 postive words not in the English language
Looking at the Japanese and Korean, it seems there's some over-romanticization here.It's a little odd seeing the Japanese "nakama", the word Monkey D. Luffy constantly throws around to refer to his crew, described like it has some deep philosophy behind it. Similar goes for Korean "Sarang", which is probably the first word every Korean student learns, and the most common word said by any K-Pop star - if its usage isn't in any way distinct from the English word for "love", it shouldn't be described like some alien concept. On the other hand, Japanese "Ai" would actually be slightly distinct, since people use "Suki" for anything more casual.
T-R··on Immutability Changes Everything
I've been working on a functional/event sourced postgresql database for the past few years. The approach in general is great, but we're currently investigating alternatives for implementation (whether going to something like datomic, or writing a DSL to generate the SQL). It's doable on postgres, particularly with recent JSON support, but SQL is just too low level, so it's a lot of work:

- Because you tend to be doing a lot of different aggregations over the same or similar data sets, the things you most want to re-use, like "group by" and "order by" clauses or a set of columns for projections or joins, happen to be the things SQL as a language doesn't give you the ability to abstract over.

- A lot of what you want to do is a fold over the event stream, followed by a map into some format, then ultimately filtering down to just the data you want. This is an easy enough query to write, but to get reasonable performance, you need to be able to adjust the evaluation time of parts of this process/materialize intermediate results: you almost never want to do an aggregation over all of history on every read, you usually want to at least do the aggregation on write, and maybe do the `map` lazily on read. In SQL, this turns out to be a significant amount of manual work/code for something that, conceptually, should basically just be a strictness annotation. What you'd really want, at the very least, are materialized views with the ability to automatically materialize on a per-row basis when dependencies change. The recent support for Upsert will make this slightly less painful, but even so, it's a lot of work, and a lot of space for bugs / stale de-normalized data.

- What you need for playback to be performant is for your database to understand which events, or parts of events, commute with each other, so it can play them back in parallel, or at least optimize the query beyond just running a volatile stored procedure in a loop. There's no way to communicate this to postgres, so you have to model this explicitly yourself - you end up having to explicitly write 'live' code (e.g., an imperative function to update an account) that needs to produce the exact same results as 'playback' code (e.g., a materializeable query that produces the state of the whole accounts table from all user-related events).

To generalize, there's just no way to tell the database what constraints you're adhering to, so it can't do any of the heavy lifting for you - either generating code or query optimizations - and it won't do anything to make sure you won't break those constraints, either. Something that does know how you're using it, on the other hand, should at least theoretically be able to do all of this work for you and a whole lot more.

T-R··on The category design pattern
Sure, we can have a perfectly reasonable debate as to whether or not lazy-by-default is the best choice for a language. I personally like the laziness, but if Scala, or Closure, or ML, or Elm, or maybe by some act of god COBOL "wins" by taking the good parts, that's fine.

But here, we have an article trying to make some concept approachable - some math, not tied to a specific language. Maybe it's hard for most to follow, since it assumes the readers knows what a monad is. That's fair, but it'd be the same if it were Scala.

The top comment here - and a significant number of the upvoted comments here, and on every similar article - are just baseless attacks against the idea that people should learn this. Not questions or technical discussion, just attempts to ostracize the part of the community that finds it valuable. It's anti-intellectualism, plain and simple. Maybe it's the terminology, or the syntax, or because articles didn't show the lock before giving the key, but it is what it is.

- Re: Hiring - JP Morgan may be doing layoffs somewhere, but I mentioned them because they made a "Hiring Haskell Devs" post within the last week.

Edit: Seriously, go re-read that top comment, from the condescending reference down to the assertion that it's all mental masturbation. In response to a post by a knowledgeable person trying to teach something useful to people who are interested. That's the top comment. Embarrassing.

T-R··on The category design pattern
The theory is for getting consistent/composable APIs, and trust in the code - trust that you can optimize it, parallelize it, or just that it behaves the way it's supposed to, without having to read every line of it. You don't need to think in theory to benefit from that.

While it's certainly cute that I can `gem install` a CMS, the Ruby ecosystem is infamous for its magical, non-composable, monolithic frameworks. The functional ecosystems are working toward fixing the all-too-familiar problem where you have to either start over, or hack apart some giant stateful mess anytime your exact usecase hasn't already been built as a library. It's easy to throw a nice looking house together with Lincoln Logs, but I'd take Lego instead any day.

Edit: Also, just because someone hasn't built the tallest building in your neighborhood yet doesn't mean they're not building anything. JP Morgan, Kapersky, Facebook, and IMVU are hiring Haskell devs, just off the top of my head.

T-R··on Opaleye’s sugar on top: SQL in the type system
It's definitely relevant that my complaints are from a Haskell perspective (as this is a Haskell thread). If I'm working in just about any procedural/OOP language, sum types are a completely foreign concept. If I'm working in PHP, I don't expect a consistent API. If I'm working in a strict language, I don't expect to be able to abstract my data model away from how I loop over it in memory. If I'm used to anything without some support for dependent types, I just assume that an empty list, or a list with too many entries couldn't possibly be detected at compile time. The high level constructs it does have are strictly less powerful, largely as a function of not being composable/consistent and certainly not as polymorphic as the kinds of things in Foldable/Traversable, though certainly better than writing your own for-loops.

The thing is, these are practically all you do in SQL, so it's pretty frustrating that, when working with it, so much time is dedicated to dealing with these solved problems. They're the reason you can't import a "votes" or "comments" package to your database, and why being a database expert is such a niche, specialized, and lucrative position - because so much knowledge isn't captured in the language, or the compiler, or libraries (even though it quite feasibly could be), but just spread by books and blog articles. It's why building complex things on relational databases is so hard - the level you're working at is strictly limited to "what keys do I join on to get the result set I want" and "how do I make this only loop once" - it's not inherent in the relational model/relational algebra, it's strictly a problem with SQL.

To add, as a declarative query language, it's worth also comparing to things like Prolog/Datalog. Prolog certainly doesn't get you all of the things I mentioned, either (e.g., no dependent types), but it is a much higher level of abstraction - you can describe things like "this is a one-to-one relationship" at a high level. The flipside is that, to write performant queries, you need to understand the Warren Abstract Machine, but because of its consistency, that's a significantly lower barrier to entry than trying to understand a specific database's optimizer implementation, or the arbitrary decisions in the SQL spec, and the knowledge there is actually transferrable.

T-R··on Opaleye’s sugar on top: SQL in the type system
SQL is incredibly low level

- Modelling Sum Types requires giving up correctness guarantees, performance, or both.

- It's not expressive enough to get performance or abstraction - I can't communicate "this is a one-to-one relationship", "this is append-only", "this is associative", or "these things commute" in anything but comments. And my co-workers need to remember to make those joins, and think about how they can use that information to hand-optimize, because the optimizer sure as hell isn't going to do it for them.

- There's no abstraction/composition of anything at lower than a full query granularity, so everything needs to be inlined and repeated everywhere - this makes conceptually small changes, like going from single- to multi-column primary keys or vice-versa, require rewriting practically every view/query.

- Some optimizer WATs are required by the spec ('where' clauses can be propagated down the AST even past another clause that does type refinement; CTEs always materialize the result set and act as optimization walls), others may be implementation dependent (At least in Postgres, "Union All", instead of acting like a simple stream concatenation, more often than not materializes the whole result set and pages it out to disk - your 0.2ms query now takes 2 seconds). SQL is "Lazy when the stars align" and optimized by "this won't break things in the trivial case" - most effort in non-trivial queries is spent trying to convince the optimizer not to hurt you on a whim. Don't get me wrong, the devs do absolutely impressive stuff with what they've got - these quirks are clearly what needs to be done to compensate for the spec, and the spec can't change for compatibility resons - but it doesn't make it easy to work with.

- Things like missing a column in a join clause (like when changing that primary key) cause significant bugs that don't actually throw an exception/type error anywhere - they just change the size of your result set. This could be completely fixed with type-level literals.

- Types are internally inconsistent, and supplied functions are pretty much ad-hoc "we thought this might be useful". Type conversions regularly involve first converting to a string or wrapping in a nested query.

- The syntax is pants-on-head ridiculous for anything non-trivial, making education difficult and maintenance tedious. (especially window functions - SELECT a, b, LAST_VALUE(b) OVER (ORDER BY c ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS x).

The problem, though, isn't just the amount of code and maintenance issues this creates - it's the sheer amount of information that everyone on your team who touches the database needs to know and not overlook just to not create subtle bugs and significant performance issues. And all this knowledge is completely non-transferrable.

SQL is so bad that standard practice is just to not do anything on the database unless you have to; eating the connection overhead, adding yet another cache, and having a layer of wrapper functions are just considered the cost of doing business, leading a lot of people to think SQL is perfectly sufficient for what it does.

← PreviousPage 3 of 14Next →