1,225 karma · joined January 30, 2022
Its a bit like the media cycle. The more jacked in you are, the more behind you feel. I’m less certain there will be winners as much as losers, but for sure the time investment on staying up to date on these things will not pay dividends to the average hn reader
I think if one were to graph the progress of technology on a graph, the trend line would look pretty linear — except for a massive dip around 2014-2022.
Google searches got better and better until they suddenly started getting worse and worse. Websites started getting better and better until they suddenly got worse. Same goes for content, connection, services, developer experience, prices, etc.
I struggle to see LLMs as a major revolution, or any sort of step function change, but very easily see them as a (temporary) (partial) reset to trendline.
(recur (subvec coll 1) …)
If I needed to maintain the vector throughout the loop. However you need to change your base case and there’s no reason to have a vector at all in this case.There is a reason most of the Clojure core functions return lists and not vectors. When you start learning Clojure, you use vectors for everything — after a while you realize how sufficient a list is for 90% of programming.
Also, I believe you could still get the performance guarantees and dynanicism with a dotimes and a volatile. You can definitely get it using nth on the remaining collection with a conditional if you don’t mind a bit of verbosity. Regardless of the goalposts, I promise you there is an ergonomic solution ;-)
These examples are incredibly easy to grok. It takes me a lot longer to grok any index based solution ;-) Especially handling nils, missing indices, checking bounds... you quickly get buried in obvious issues that aren't a concern in clojure.
If it's more complicated, I use comments like the first example. I would say that when grokking more complicated loops, you definitely read it slower on a per character basis, but as I've stated elsewhere I don't think you're grokking the functionality any slower.
> What's the nested loop in the first solution that you've avoided? The `reduce`? the `count`?
Correct, reduce is a loop
> `conj` feels very Lispy (I mean in contrast to Clojure, not C++) .. Isn't it going to have to run down the list every time to add an item?
No, vectors are closer to hash-maps than lists. There are two main sequential collections in clojure, lists and vectors. Lists are O(n) to append, vectors are O(1). Lists ore O(1) to prepend, I'm not actually sure the perf characteristics of prepending to a vector.
> My outstanding concerns are what I think are the constant coercion to lists/vectors.
There is only one coercion from a vector to a list in the code (the first time `rest` is called on the input vector). That's if your input was a vector to begin with. Chances are it will be a list. Also, I'm not sure it's even technically coercion in the computer science sense.
> You also in effect know the result's size, but your runtime/compiler doesn't know that. So you aren't preallocating `memo` and it feels .. wrong
This is not a concern in JavaScript (it always preallocates a bunch of memory for every vector IIRC) and I'm not sure the Java implications, but given the performance people get out of clojure I'm sure it's not an issue ;-)
> The current way of dealing with array is always a bit unwieldy in Clojure. And everything coerced to lists. Working with vectors, with mapv filterv etc is helpful, but they don't have random access so it's not always the solution you want.
I can count on one hand the number of times I use nth in my project. To me it's crazy that people build solutions based on random access XD
That being said random access in O(1) on vectors, including those returned from filterv and mapv (technically O(log32n))
(loop [coll [1 2 3 4 5 6 7 8 9] memo []]
(if (< (count coll) 5) memo ;; Stop once there are less than 5 items
(->> (take 5 coll) ;; Take the first 5 days
(reduce +) ;; Sum them
(* 0.20) ;; Divide by 5
(conj memo) ;; Append to the end of the memo
(recur (rest coll)) ;; Recur for remaining elements
,,,))))
Realistically if performance was a consideration I would probably do: (loop [[a b c d e :as coll] [1 2 3 4 5 6 7 8 9] memo []]
(if-not e memo
(recur (rest coll) (conj memo (/ (+ a b c d e) 5))))))
Should be ~15 times faster to avoid the nested loop. If you want to change the min size it's still pretty clean: (loop [[a b c d e :as coll] [1 2 3 4 5 6 7 8 9] memo []]
(if-not c memo
(recur (rest coll) (conj memo (/ (+ a b c d e)
(cond e 5 d 4 c 3))))))))I did not know these were in the cards, that makes jank even more exciting!
(defn report [date]
(let [[d w m q y] (-> (comp tier* recall* (partial c/shift date :day))
(map [1 7 30 90 365]))]
(reduce (fn [memo {:keys [card code]}]
(cond-> memo
true (update code (fnil update [0 0 0 0 0 0 0 0 0 0]) (q card) inc)
(<= 4 (d card)) (update-in [code 6] inc)
(<= 4 (w card)) (update-in [code 7] inc)
(<= 4 (m card)) (update-in [code 8] inc)
(<= 4 (y card)) (update-in [code 9] inc)))
{}
(k/index :intels)))))
The elixir code I was able to condense down into: def report(facets, intels, day) do
[d, w, m, q, y] = for x <- [1, 7, 30, 90, 365], do: Date.shift(day, day: x)
Enum.reduce(intels, %{}, fn intel, acc ->
facet = Map.get(facets, intel.uuid, :zero)
[q0, q1, q2, q3, q4, q5, d4, w4, m4, y4] =
acc[intel.code] || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
quarterly_tier = tier(facet, q)
Map.put(acc, intel.code, [
if(quarterly_tier == 0, do: q0 + 1, else: q0),
if(quarterly_tier == 1, do: q1 + 1, else: q1),
if(quarterly_tier == 2, do: q2 + 1, else: q2),
if(quarterly_tier == 3, do: q3 + 1, else: q3),
if(quarterly_tier == 4, do: q4 + 1, else: q4),
if(quarterly_tier == 5, do: q5 + 1, else: q5),
if(tier(facet, d) >= 4, do: d4 + 1, else: d4),
if(tier(facet, w) >= 4, do: w4 + 1, else: w4),
if(tier(facet, m) >= 4, do: m4 + 1, else: m4),
if(tier(facet, y) >= 4, do: y4 + 1, else: y4),
])
end)
end
It was much longer prior to writing this comment (I originally used multiple arity helper functions), but it was only fair I tried my best to get the elixir version as concise as possible before sharing. Still 2x the lines of effective code, substantially more verbose imho, and required dedicated (minor) golfing to get it this far.Replacing this report function (12 lines) + one other function (6 lines) + execution code (18 lines) is now spread across 3 modules in Elixir, each over 100 lines. It's not entirely apples to oranges, but trying to provide as much context as possible.
This is all just to say that the high effort in reading it is normally a result of information density, not complexity or syntax. There are real advantages to being able to see your entire problem space on a single page.
(loop [[a b c & more] coll] (recur (apply list b c more)))
There’s also partition if you're working with transducers/threads/list comprehension (partition 3 1 coll)
Or if you need to apply more complicated transformations to the neighbors/cycle the neighbors (->> coll cycle rest (map xform) (map f coll))
Using map-indexed to look up related indices is something I don’t think I do anywhere in my codebase. Agreed that it’s not ergonomicEDIT: those Fibonacci functions are insane, even I don’t understand most of them. They’re far from the Clojure I would advocate for, most likely written for funsies with a very specific technical constraint in mind
The 1:20+ is definitely not hyperbole though. Using transducers to stream lazy reductions of nested sequences; using case, cond-> and condp->; anywhere where you can lean on the clojure.core library. I don’t know how to give specific examples without giving a whole blog post of context, but 4 or 5 examples from the past year spring to mind.
It’s also often the case that optimizing my clojure code results in a significant reduction of lines of code, whereas optimizing Python code always resulted in an explosion of LoC
Personally I find Python particularly egregious. No map/filter/reduce, black formatting, no safe nested property access. File length was genuinely one of the reasons I stopped using it. The ratio would not be so high with some languages, ie JavaScript
Even with Elixir though, many solutions require 5-10 times the amount of lines for the same thing thing in Clojure. I just converted two functions yesterday that were 6 & 12 lines respectively in Clojure, and they are both 2 pages in Elixir (and would have been much longer in Python)
Also writing Clojure can be incredibly terse, resulting in quite high-effort when reading. Conversely, a lot of time I can condense hundreds of lines of equivalent python into 5 or 6 lines of Clojure. Having all of this functionality condensed into something you can fit in a tweet really helps for grokking larger parts of the dataflow or even larger system. So there are tradeoffs
Plus structural editing and the repl really help with the “reading” experience (reading in quotes because it’s much more interactive than reading)
(Edited for tone)
I’m going to be honest with you: I want to be the best in the world at what I do. I’m not there yet, but I know I am willing to put in the hours, and fail as many times as it takes for me to get there.
For that reason, Phrasing to me is more than just a language learning application. It’s a chance to do something different. A chance to build a product that pushes humanity forward. A chance to build experiences that are genuinely delightful. A chance to build a company that can inspire.
I’ve built the current version entirely myself (not even a single like of AI generated code). On the good side: the product is working, I’m using it every day to genuinely learn 18 languages, and the user base is growing. On the bad side: the onboarding flow needs a lot of work, the pipelines keep breaking, and the maintenance burden is too much for me to handle alone and make progress on the product at the same time.
I’m looking for an engineer to join in a co-founding capacity and help me build something truly great. You would assume responsibility of the backend Elixir application, while I spearhead the various frontends (web, mobile, design, marketing). You’d make sure every task succeeds, keep queries times minimal, and optimize the crap out of the data and the infra. On the product & leadership side—we would collaborate on everything. I believe strongly that the best ideas are born of two minds.
And to tie it all together, I’m looking for someone else that wants to be great. Someone who wants to do things differently, someone who isn’t afraid to chase their dream and take their shot.
No experience for this role is necessary. All I care about is that you’re competent, passionate and hungry.
If you feel like this is you, then send a well written introduction to hn [at] phrasing [dot] app. Any AI applications will be ignored and the senders blocked.
Learn more at https://phrasing.app/blog
It sounds like they have stopped taking applications, I do not think they have suddenly shut down the program over the weekend.
(Please correct me if I’m wrong OP)
I got the demo video produced, and a blog set up and seeded. You can see some of the science behind learning multiple languages at https://phrasing.app/blog/multiple-languages or follow my progress using Phrasing to learn 18+ languages at https://phrasing.app/blog/language-log-000
Now I’m working on the onboarding process, which I’m very excited about on both a product and a technical level. On the product level, it dovetails nicely into most of the shortcomings of the app. One solution to a dozen problems.
On the technical level, I’m starting to migrate away from reagent (ClojureScript react wrapper). The first step was adapting preact/signals-react to support r/atom, r/cursor, and r/reaction. This has worked beautifully so far and the whole module, with helpers, is less than 100 LoC. I’m irrationally excited about it, and every time I use any method it brings me a stupid amount of joy… especially since it’s exactly the same API as reagent.
For those curious, the next steps in the migration will be: upgrading to React 19 support once reagent ships with it (in alpha currently), then replacing the leaf components with hsx and working my way up the tree. No real code changes, just a lot of testing needed. Maybe at the end of it all, I can switch the whole app over to preact — will be interesting to test the performance differences.
As far as ideas I’m thinking about, I’m currently planning the next task in my head. This will be an (internal) clojure library that will hopefully have ClojErl (erlang), ClojureScript (js), and jank (C) interfaces, which means I’ll be able to write clojure once, and run on the server, browser, and mobile — all in their native environment. Needless to say, being able to write isomorphic clojure without running JavaScript everywhere has me almost as excited as my signals wrapper :D
It _is_ fixable though. It took me about a week, but I have yet to find a mistaken reading now. This also seems to just be the case with Japanese - most tonal languages seem to have the correct tones (I’m not qualified to comment on how natural the tones sound, but I have yet to find a mismatch like in Japanese)
Would you like to hear more about topics, teams, and organizations? If no, please explain using at least 1000 characters (required)
[ ] yes [ ] no
Using AI is kind of like having a Monika Closet. You just push all the stuff you don’t know to the side until it’s out of view. You then think everything is clean, and can fool yourself into thinking so for a while.
But then you need to find something in that closet and just weep for days.
I would still prefer 5x the blur; I really, really, really hate the shapes of the tab switchers; and they use space so inefficiently I feel like I’m using an iPhone SE… but the liquid glass is ok. Gimmicky and ugly but it is mostly usable
The only reason I ever use google maps is to search somewhere and copy paste the address into Apple Maps.
Can’t speak towards Japan or Taiwan specifically but it’s been fine in extremely rural Africa, India, Brazil, Indonesia, Bosnia, Australia, etc. Much better than Google Maps in most of Western Europe and America these days.
Every feature every variant ever possible configuration and all future potential states. Don’t care about how it looks or how it feels just put it all there. Build out as much of it as possible, as fast as possible, knowing it will be thrown away.
Then, whittle away. Combine, drop, group, reorganize, hide, delete, add. About halfway through this step it becomes clear what I really should have been striving for the whole time—and invariably, it’s a mile away from what I started out to build.
One I have that, then I think step three stays about the same.
This isn’t really a critique of lean development, but after a decade of trying to do things leanly, I’ve just accepted that it’s not how my brain works
Maybe it didn’t out itself, but you could also make the argument that that’s failing in an of itself