HN user

camdez

64 karma
Posts4
Comments30
View on HN

I don't disagree with anything you've said here, I just don't see how it applies to the situation.

With `datomic.api/with`, you can apply new datoms and get back a new DB value. Repeat this process as many times as you want, in as many directions as you want, switching as you choose. You're building a tree of immutable DB values—seems clearly like branching to me.

If by "read only" you mean that they're not persisted to disk, then that's an important point, but it surely doesn't obviate the utility of the functionality. It's useful in a number of cases, and especially testing scenarios like the Xata article describes.

If you built an immutable database that persists the branches, that is very cool and sounds useful—kudos! That said, I also don't want to downplay the utility of what Datomic does; it's a major help to me.

Wait, this statement seems way too strong.

The linked article points out that Datomic doesn't support branching from the past. It absolutely does support branching, and I've built entire test suites that way.

From a cursory glance, I'd say Datomic does exactly what the original parent article is discussing. It works great and it's super convenient.

Ok, I like this game / question, but I daresay I'm confused about some of these (or have not even fathomed some of the implied pronunciations).

`seq`, `Eq`, `prev` I pronounce like the beginnings of the underlying words, so I'd say pronounce-then-abbreviate and abbreviate-then-pronounce yield the same result.

I guess this implies people are saying "SECK" (`seq`) and "ECK" (`Eq`), rhyming `prev` with "rev", and pronouncing `id` like Freud's id? (Eek.)

In fairness, I'll confess I pronounce `enum` "E-NUM", not "E-NOOM".

The exact semantics of the problem aren't clear (do we need to maintain order? Do we need to use the initial count? Why do we have counts on subsequent elements if we only add 1?), but the logical Clojure solution would just be:

  (frequencies (map second v))
  ;; => {"one" 2, "two" 2, "three" 1, "four" 1}

  ;; or:

  (update-vals (group-by second v) count)
  ;; => {"one" 2, "two" 2, "three" 1, "four" 1}

  ;; +(#:'=v[;1];?v[;1])
  ;; flip(count each group v[;1];unique v[;1])
So, shorter than the readable K version in a Lisp.

What did we learn here? Probably nothing.

K is great for sequence operations--not sure what we're trying to imply about Lisps.

The Janet Language 3 years ago

Emacs Lisp actually has threading macros natively (seemingly since at least 2016), but they're (IMHO) obnoxiously named `thread-first` and `thread-last`.

The Janet Language 3 years ago

I specifically called out the tertiary operator in my text so as to be fair, but tried to keep things simple in my example. Perhaps too simple.

Of course you can do that simple case with a tertiary operator but:

1. It's a construct that really has no reason to exist (I argue) as distinct from `if`.

2. It doesn't compose with statements.

This duality is primarily what I'm arguing against.

A better example would have been a case statement inside of the `println`:

  (println
   "Log in by"
   (case user-id
     0 "root"
     1 "local admin"
     (format "regular user (id: %d)" user-id)))
In C, you have to introduce a variable for no good reason (or do some non-idiomatic, ugly, nested tertiary operators that get uglier the more cases we have).

And even then, you can't just say

  user_name = switch { ... }
Because switch is an statement.
The Janet Language 3 years ago

I actually agree with that entirely, and the point of my jest was just to say that we should look deeper for the important ideas, not refuse to read the book because we don't like the color of the cover.

Lispers don't feel positive about Lisp because of parentheses; change them to curly braces or brackets or ^ and $—that's really not what matters. Lisps with brackets go all the way back to the beginning (https://en.wikipedia.org/wiki/M-expression). Indentation-based Lisps have been done too (https://readable.sourceforge.io/).

The point is an expression-based syntax that directly models the code tree, is written in the data structures of language, and is convenient for meta-programming. It's a fundamentally different approach that yields massive benefits (see my other comment in the thread if you want to hear that spelled out in more detail).

But we don't see that when we just stop at unfamiliar syntax.

Lispers have been structurally-editing code as a matter of course since at least 1970. Most of the rest of the world only got a taste of that when tree-sitter came out circa 2018 (I know I'm rounding the edges here, but the point stands). Half a century later! Why is that? It's not just curly braces vs parens—something deeper is happening here.

I do apologize if I came off rude. I'm just so frustrated at hearing this same line year after year after year from people who are missing out some of the most powerful ideas in programming because they prefer this ASCII glyph over that one. It's nothing more than parochialism.

It just makes me want to scream (perhaps uncharitably) "surely you're not a serious engineer who works on serious problems if your biggest concern while coding is which character is used to group code?!" I want tools to help me think more clearly, ways to operate at higher levels of abstraction, better concurrency semantics—surface characteristics be damned. Sure, I have my preferences about orthography, but the tail doesn't wag the dog.

Look deeper! Learn what each language has to teach you! Then keep the parts that move our craft forward and use whatever glyphs you want. But don't reject the automobile because it doesn't have handlebars.

Moreover, the things that look familiar probably have the least to teach you.

I believe we have the ability to do so much better as an industry, but it's not going to happen if we reject the unfamiliar just for being so.

The Janet Language 3 years ago

I certainly don't fault you for having that reaction, but I would encourage you to look deeper and not dismiss one of the most interesting families of programming languages on the basis that it looks weird. Far from atrophying your skills in other languages, many people say that learning Lisp makes them better programmers even when they're coding in other languages.

Lisps don't arbitrarily look weird—there's a deep, principled, elegant reason for it; Lisp code represents how the code will be evaluated in the most direct way, without relying on (some would say needlessly) complex parsing / precedence rules. There are no surprises and no arbitrary rules to learn. There are no useless semicolons to forget, and you'll never have to wonder if `+=` returns the RHS or the result of the operation (or does it even have a return value?).

You don't have this meaningless distinction where you can't directly reduce with `+` because—ugh—it's not a function, it's an operator. You just say `(reduce + [1 2 3])`.

You never have to do this ugly Ruby stuff...

  words.map &:length
  # or
  words.map { |w| w.length }
...because methods are really just polymorphic functions, but language designers chose syntax that doesn't compose elegantly.

You don't have this useless distinction between statements and expressions that limits how you can compose code. You never have to drop down to some ugly, limited tertiary expression form (`COND ? X : Y`) of `if` because—whoops—`if` is a "statement". You just write:

  (println (if me? "me" "you"))
Because, duh, we wanted an `if`.

What do we gain by adding all of this noise?:

  if (is_me) {
    println("me");
  } else {
    println("you");
  }
Absolutely nothing. The parens on the conditional, the curly braces, the semicolons, the `else` keyword—they're essentially meaningless incantations to appease the compiler. And we've introduced an undesirable opportunity for the two branches to accidentally diverge over time.

But most importantly, our code is written in the data structures of our language. Code as data means we can manipulate code as easily as we manipulate data, which means we can trivially (re-)write code with code (i.e. macros). And not shitty string generating macros, or macros that can only do a handful of sanctioned things—we can write our own control structures in couple lines of code. We can add new abstractions to the programming language from user space.

Wish the language had an `if-not` construct? You can add it with, like, 3 lines of code. Wish functions could have optional parameters? Add it. Wish it had a pattern matching functions like SML or Erlang? Cool. Java-style annotations? Logging that is fully removed when running in high performance mode? A different OO model? Multi-line string literals? String interpolation? A graph of dependent calculations that only get run when used? A more convenient way to load dependencies? It's all easily doable.

I've coded in Lisps (and a dozen other languages) for at least 20 years, and every time I have to use a non-Lisp syntax I just think "wow, these people really missed the boat". It's like having to write math in Roman numerals (would you rather calculate "D + L + IX" or "500 + 50 + 9"?); there's a better way, and that better way has elegant, recursive, underlying design principles that make the ergonomics way better.

But, yeah, it doesn't look like C code. And people seem to be really attached to their C syntax.

The Janet Language 3 years ago

Agreed.

This is why I don't do any math I can't do on my fingers.

Parentheses are just too scary, and there's no way that parenthesis math junk actually has any useful ideas.

Expanding on this a bit, Clojure, as a language, is fundamentally against the idea of ORMs in its design.

Objects / classes are not so much the problem, per se—it's specifically that ORMs fundamentally involve scattering uncoordinated mutable state throughout the application. A foundational thesis of Clojure is that mutation is a tremendous source of bugs, and should be avoided / thoughtfully limited.

Once you let the unmanaged mutation genie out of the bottle, it's almost impossible to put back in.

More concretely, I used to work extensively with Rails; I loved ActiveRecord (ORM) when I first started out—it makes basic things so easy.

Later I worked on a large Rails app supporting millions of users...we used ActiveRecord extensively, and had a very talented team. ActiveRecord worked fine most of the time, but I have bad memories of spending hours or even days tracking down user-reported bugs.

I'd try to figure out how to recreate the user's state locally, even cloning pieces of the production database to work with their exact DB data, but whatever state the program had gotten into was a large graph of (all!) mutable objects. How was that flag getting set? What code could have done it? When? And the answer is basically ANYTHING at ANY TIME that could possibly get a reference to the object. And web applications are far from the worst offenders in this space because the request / response cycle is (usually) fairly globally stateless.

Clojure is the exact opposite of that experience.

The state of a Clojure program will most likely comprised of data literals (think JSON data types, if you don't have experience with Clojure / Lisp data). Printable data literals. Connect to the errant server, serialize the state, read it from your machine, and you're there. It's coherent, stable, serializable, transmittable, simple.

Who can mutate your data? No one. You have an immutable reference (maybe others do too, but reading is a fundamentally safe operation). How does it change? Only along the explicit path of computations you're working through (it doesn't change, actually, you choose to hold onto a new reference to derived data when you want to).

Or, if you really need a mutable escape hatch (like, say, you're holding a handle to a database), every type of mutation in (core) Clojure has defined (and thoughtfully so) concurrency semantics. You won't see a bunch of notes in Clojure API docs that say things like "not thread safe" like you see in JavaDocs.

TLDR: Clojure will happily give you object-like read-only views into your database (like Datomic's `datomic.api/entity`), or help you write queries with a knowledge of your database schema, but most Clojure persistence solutions will explicitly coordinate mutation into a single 'site' because that's the only way maintain a coherent view of state-over-time. And that single-mutation-site story is the opposite of what ORMs (as commonly defined) do.

I struggle to see how this "doesn't matter" while also agreeing with you that it's a super cool property. Especially for someone coming from an alphabetic language background.

Logograms allow you to understand (some) meaning without understanding pronunciation.

Syllabaries and alphabets allow you to understand pronunciation (to varying degrees of success depending on spelling consistency) without necessarily understanding meaning.

It's all tradeoffs.

These days I read Chinese much better than Japanese, and it's definitely fun that I can look at a page full of Japanese and understand the meaning of many words just from knowing the (largely parallel) meaning of the kanji / Hanzi from Chinese.

Conversely, I can read (the sounds of) Hangul, but I rightly know about 20 Korean words, so it's all just sounds to me.

But I can read the name off of a Korean hotel sign and communicate it to a Korean taxi driver. I can't do the same in Japan if I don't know the pronunciation, even if I know exactly what the sign means. If it affects your ability to use the language effectively to achieve what you want then I think it matters.

Readable Clojure 9 years ago

+1 That thing is gross. I'd edit so much more slowly if I had to go back to a messy, complex syntax. At least without a very clever / specialized editor effectively undoing the change for me.

Infix is occasionally more readable for math, but I'd rather have a macro to transform a delimited section of infix code than to mess with the language.

GNU Guile 2.2.0 9 years ago

If it were to just be "foo bar 1", you couldn't tell the difference between (C style again for clarity): foo(bar(1)) and foo(bar, 1).

Or `foo(bar)(1)`.

Very cool. The grammatical basics seem a lot like Chinese, with similar word order, and a couple Japanese-esque particles thrown in (li ≈ が, e ≈ を). Lacking tense, conjugation / declension, subject / verb agreement, grammatical gender, etc., I've always felt the basics of Chinese make a pretty good foundation for a simple language.

I'm curious about the decision to include the grammatical particles, and why it seemed necessary...anyone have a full enough understanding of the grammar to know why the decision was made to allow dropping the "li" particle with "mi" and "sina", but not getting rid of it in general? Chinese similarly lacks a 'to be' copula, and gets by quite well without a subject marker.

c.f.

  EN: I (am) good
  TP: mi pona
  ZH: 我好

  EN: Water is good
  TP: telo >li< pona
  ZH: 水好
Japanese explicitly demarcates the subject / topic, but seems to allow a bit more variety at the beginning of the sentence + uses that demarcation to add a connotation of emphasis or contrast with a previous topic + isn't a conlang. Anyone have a feeling for what it adds here?
Quality Is Fractal 12 years ago

I don’t find this to be an accurate description.

Yes, quality (often) has this one bad apple spoils the whole bunch property. If we have to have a math metaphor we might say that quality is multiplicative, in the sense that one low value in a sequence still impacts the entire product. Or we might say that quality has an absorbing element, by which we mean that any zero value kills the whole set (100 * 100 * 100 * 0 still equals 0). But fractal means that we see self-similarity at all levels. That hardly seems to be the case.

According to the original argument, bad software implies bad programmers which implies a bad company. That seems not only incorrect but also non-constructive. It ignores (e.g.) the idea that good employees could release bad products under bad management. Likewise, great programmers can write applications which are terrible to use. There are other skills involved in that process (interaction design, for instance). The criteria for evaluating programmers and software are vastly different and thus it doesn’t make sense to say that there’s a fractal relationship between these two vastly different kinds of entities.

Well put.

The consequences of that are where things get interesting:

When violence is systemized we consider it OK. When something stands out we consider it wrong. Authority is presumed to be benevolent (or, at least, extra-moral).

I just noticed that you said "your state orders you", oops. That's not the situation I'm talking about. The morality of soldiers is a complex and interesting topic, but what my comment is about is the responsibility (or lack there of) borne by all citizens, given that their government (presumably) represents them in its actions.

Choosing to kill with non-governmental violence does not mean that the decision is made alone. In fact, the particular assassination in question was a plot involving multiple individuals. Killing via the government is a group decision involving more individuals. What is the magic number of individuals at which we no longer have to evaluate the morality of the situation? And, if such a number exists, what defines it?

And yes, the individuals perpetrating the action bear responsibility. But if the action is the correct one, then they bear responsibility for doing the right thing, no? Surely statehood is not an inherent justification for violence; the underlying action always has to be the correct one, regardless of the perpetrator.

I completely agree that state-ordered violence is complicated, but you’ve only begged the question that it cannot be compared to individual (or smaller-group) violence, not given a reason why it cannot.

Allow me to play devil's advocate for a moment...

Most of us, for fairly obvious reasons, decry the use of violence by individuals, yet we are all complicit in the use of violence by the state. Our democratic governments imprison and kill in our names both in war and for the enforcement of our laws. Sometimes (often?) wrongfully.

Why is our collective violence de rigueur but individual violence is immediately presumed to be evil and wrong?

Presumably use of violence by the state is intended to be "justified"; i.e. it is not random, emotional, or for gain (except, perhaps a societal gain of increased safety), but either for the cause of "justice", or for safety--and these calculated, functional properties with an aim of public good make the action acceptable.

The obvious difficulty of individual violence is the tendency for it to be emotional or for personal gain, or for a lack of rigor in establishment of guilt in the presumed crime of the victim. But if guilt has been firmly established, if the target of violence has, beyond a shadow of a doubt, committed heinous acts, then what is the fundamental difference? Isn't the elimination of a destructive force by an individual precisely the same in outcome as the elimination of the same force by a collection of individuals?

And in a thoroughly corrupt system which promotes, protects, and encourages these destructive forces--or a system which feigns to protect and represent its citizens (but doesn't actually do so)--mustn't the responsibilities of protecting and shepherding at some point eventually fall to the citizens themselves? Surely evil carrying your banner is still evil?

Now don't get me wrong, I'm no proponent of violence. I think (even) our government should commit considerably less violence. I think we generally need to scorn violence by the individual because of the likelihood that it be unrigorous, emotional, or selfish. But I don't know that the difference in emotional weight between "kill" and "murder"--that is to say "unlawfully kill"--should be so great. It seems to me that the philosophical basis for the two should be the same, rather than scorning one and supporting the other because it carries a badge.