off-topic

gtrak 2025-11-04T18:59:09.252269Z

I like to ideate by just arguing with ChatGPT sometimes, it got me here today: What if there was a clojure-like with a type system that treated data constructed by the program itself (eg a map literal, or an output from a core function given only closed data) separately from dynamic runtime data sourced from the edges and actually being conveyed around the system?

;; REPL: create-user
(defn create-user [{:keys [id name email]}]
  (store-user {:id id :name name :email email}))

;; inference:
create-user :: Closed{ id:Int, name:String, email:String } -> Result
store-user param :: Closed{ id:Int, name:String, email:String } -> Void

;; safe assoc (replace email):
(assoc user :email (sanitize-email s)) ;; returns Closed{...}

;; adding an extra field:
(assoc user :last-seen (now)) ;; returns Open (must widen or be explicit)
;; => REPL/Compiler warns: assoc introduced new key :last-seen; consider widen! or annotating
(widen! user) ;; now further assocs preserve Closed with row-var

;; ingest external JSON:
(defn handle-webhook [event]  ;; event : Open
  (let [user (coerce<User> (:payload event))] ;; runtime-validated
    (create-user user))) ;; user is now Closed<User>
Why this design is attractive • Conservative: closed-by-default preserves invariants where you actually need them (APIs, internal args). • Practical: you can keep untyped openness for true foreign data. • Explicit: opening is an explicit act (`open!`), so accidental contamination is unlikely. • Ergonomic: destructuring + inference gets you low ceremony for the common case (named args → structs). • Composable: row polymorphism + widen! gives controlled extension when needed. All maps start out closed. They only become open when you explicitly open them. Core functions respect that distinction automatically. You still get Clojure’s data-centric style — just with compile-time awareness of when your data is yours vs someone else’s.

respatialized 2025-11-04T20:53:56.234939Z

What if "someone else" is also me, but assoc-ing in to one of these maps from a different namespace or when I'm calling this as library code? Now I have to remember namespace or library specific rules for what I can and can't add to a map, or take the extra step of calling open! each time. What do I get in return?

gtrak 2025-11-04T21:02:35.173629Z

I haven't thought this through at all, but it feels interesting. If something coming out of a library function is open, or not statically determined to be closed, same thing, it stays open unless you do some runtime parsing eg with spec or malli, or build a map from static parts (literal with keywords) vs just transforming the open data.

gtrak 2025-11-04T21:06:33.689469Z

I think the closed maps and the open data being passed around would stay separate generally. Maybe it's useful to have to call open before you rarely combine them

gtrak 2025-11-04T21:12:06.744749Z

I think associng closed to closed should stay closed, so that hallucinated example of associng a new field with (now) value should keep it closed. If the value is more complicated than a timestamp and derived from open data, then you have to open it

respatialized 2025-11-04T22:45:00.632929Z

You've explained (some of) the potential mechanics but not the value proposition. I don't understand the benefits here - at most I am automatically protected from unsafe serialization of data on the wire, but that's far from an everyday problem that needs a solution at the language level. I think you should try to understand why Clojure doesn't do it this way (explained a bit https://dl.acm.org/doi/pdf/10.1145/3386321) before having ChatGPT argue that this is an improvement.

gtrak 2025-11-04T22:49:09.197989Z

Not sure I've read that yet, but not trying to rehash types vs dynamic languages. I was wondering if there's a lightweight middle ground, specifically around most of the program data being small and statically verifiable and separate from the data that the program actually handles at the edges

gtrak 2025-11-04T22:52:00.822809Z

It's not an opinion given to me by chatgpt.

gtrak 2025-11-04T22:55:05.721019Z

The value proposition is that if you keep the information-oriented nature of clojure and add a little safety by default, isn't that better?

gtrak 2025-11-04T22:57:47.586649Z

Maybe it could take another form, but hard to believe there doesn't exist a stricter set of defaults that might be worth it

gtrak 2025-11-04T22:58:54.365299Z

For example, we have warn-on-reflection and errors on calling missing vars, and we won't want to turn that off

gtrak 2025-11-05T02:24:46.740179Z

That history of Clojure still sounds weirdly persuasive, but it's hard to reconcile with my own experiences. I did RDF work in clojure, too, and while it was a clear win over java, I'm not sure I've ever seen keywords used as context-free attributes in practice, or rarely if ever. Namespaced keywords specifically I think are a good example of tacking on information you 'own' to a map you didn't construct yourself, eg it came from JSON over the wire, thus would fit with my 'open' map usage, which matches Hickey's original intent. My point is the vast majority of real code has small, closed maps, and that could be decoupled conceptually from the actual data a program processes.

gtrak 2025-11-05T02:25:30.706609Z

> A key question was: one map or two? There are two very distinct use cases for maps: potentially > large, homogeneous, collection-like maps, e.g., mapping everyone’s username to their site visit > count, or comparatively small heterogeneous information-maps, e.g., a bunch of things we know > about a particular user in a particular context, like the email address, password, userid etc. I wanted > only a single map literal syntax and library for maps.

gtrak 2025-11-05T02:25:58.423499Z

So, I guess it was a real design concern to him, too

gtrak 2025-11-05T02:31:46.732919Z

I did data engineering in ocaml for a while, too, and there were only a handful of actual hashmaps required to basically build an RDB entirely in memory before streaming it out. A lot of lists, a lot of structs/records, and a handful of push-based streams. It was fine.

gtrak 2025-11-05T02:37:53.782909Z

Occasionally it would have been nice to have utility types for things like merging, but type systems have gotten better. Typescript has them, for example.

gtrak 2025-11-05T02:43:21.703039Z

I guess I'm glad clojure exists and he didn't just decide to do F#

gtrak 2025-11-05T02:56:54.250539Z

I'm looking at a large-ish clojure codebase right now and most of the namespaced keywords are just standalone labels, not 'attributes'

gtrak 2025-11-05T02:57:49.447299Z

One interesting exception is we use them for ex-data

2025-11-05T03:14:21.346849Z

I believe that's what typescript does

2025-11-05T03:15:32.630569Z

It can track all assoc/dessoc on maps as they get passed around when its statistically possible. And when not you can declare an interface for it

john 2025-12-14T20:21:53.801069Z

Sounds similar to Typed Clojure

john 2025-12-14T20:22:09.336179Z

It infers similar things

2025-12-15T21:18:20.597159Z

I don't think so. I had asked that to Ambrose a while back. You can define maps at every function input/output with a specific type and such. But it cannot automatically follow a map as it moves around and determine what keys were possibly added/removed as it flows through.

2025-12-15T21:18:42.784349Z

And to be honest, now that I think about it. Not sure typescript can either.

john 2025-12-15T21:37:38.162159Z

Could build those with wrap maps, easy ;)

2025-11-04T07:41:06.555069Z

i'm looking for episode 4 of tim baldridge's video series on zippers. episodes 1-3 and 5-7 seem available on youtube, but i'm not having much luck finding the fourth episode. does anyone know if that can be viewed somewhere?

lread 2025-11-04T14:44:15.912969Z

Oh drats. Those episodes used to be hosted on pivotshare and accessible for a modest fee. But pivotshare is no longer around.

💀 2
lread 2025-11-04T15:15:28.487109Z

Hmmm... maybe they are still available for a fee? From https://www.youtube.com/@ClojureProgrammingTutorials description: > A collection programming tutorials for Clojure, covering logic programming, transducers, core.async, program optimization, and many more topics. > > Q) These videos aren't available in my country! > A) Videos are also available via Dropbox and Google Drive: (payment options below) same price, billed once a month. Users get access to raw .mp4 files. This is a manual process, so expect a 1-2 day delay, but this method should work better for some users. If you don't mind the Youtube experience, using this site will most likely be more satisfactory > > Paypal: http://goo.gl/xgTq0j > Bitcoin: http://goo.gl/TUk79e > > Q) Why do you charge for videos? Isn't Youtube free? > A) Unfortunately, in order to get any sort of income from Youtube, videos must have a very high view count. Somewhere in the range of 100k views per video. Getting that sort of viewing of tutorials related to programming in any programming language would be hard. Alas, no. Those payment URLs now 404.

lread 2025-11-04T15:21:58.267579Z

I do remember paying the pivotshare fee to access Tim's videos... I don't remember if episode 4 existed there!

lread 2025-11-04T15:30:48.259119Z

Sadly, I don't think Tim is part of the Clojure community anymore. I'll fire off an email to him and see if he responds.

🔥 1
🙏 1
lread 2025-11-04T16:26:43.316239Z

https://github.com/clj-commons/rewrite-clj/pull/412, so thanks for the heads up @sogaiu!

❤️ 1