Hacker News new | past | comments | ask | show | jobs | submit | adamddev1's comments login

Yes, but sadly, people will increasingly start filling research and writing with AI generated material. And so we won't even know what's real and true anymore.


For what is worth, I could be the only real human here and all of you are generated.

So, staying ahead of LLMs using raw brain power is the only way out of this mess.

It is sad that it has come to this.


This is one of the most delightful things I've seen on the internet in a long while. I immediately started sharing it with my friends and we shared the cute little characters we made. Everyone loved it.


Thank you, I appreciate you sharing that!


But then we lose the ability to do anything offline. Offline web apps are still valuable. Some people want to turn their data off sometimes. Many people live in places where internet access is spotty.

I also love the simplicity of SSR/vanilla web for some things. But I say keep offline-first SPA/PWAs alive. Cross-plaftorm. Freedom from the app stores. Freedom from needing to be tied online all the time.


When you have a website that needs to be always accessed online, absolutely, give us an old-fashioned SSR vanilla web experience. Just give us a page WITH ALL THE DATA that loads in half a second. Don't make us wait for 5 seconds with spinners and placeholders while you make the client fetch data itself from different sources. This is insanity and torture! People are using client side rendering for the wrong things.

But there are good and powerful use cases for client side rendering.


> I say keep offline-first SPA/PWAs alive.

I have no problem with the properly offline-capable apps using standards compliant web technology. I've been championing the use of PWAs to circumvent the iOS App Store for years.

To be very specific, the problematic solutions in my view tend to be those right in the middle of pure SSR and pure client. They aren't sure if they are always online or offline by default and the actual customer use cases don't seem to have been fully explored.

Did you ask the customer if a ~150ms form post to us-east-1 from their main office would be something they'd even care about? Why not? You could save an unbelievable amount of frustration if they don't care. It would take 10 minutes to set up a demo they could try if you think it's going to be a deal breaker at delivery time.

I've not once worked with a B2B customer in the banking, insurance, finance, manufacturing or retail domains who raised a concern about any of this. Only nerds on the internet seem to fantasize about the customer getting outraged at a browser navigation event or a synthetic browser benchmark score.


I agree. I think it would nice to have basically two tracks, either fully SSR or fully client. I think if they researched what really irritated customers it would be the 2-4 seconds of staring at spinners and placeholders that has become normal. Now we've got instant feedback for browser events but brutally slow load times for our pages to fill out.


Freedom from browsers - I'd rather have an executable I can run.


Yeah, sites demanding a roundtrip for every small interaction can be a pain to use when traveling. The principle I'd wish more web devs would keep in mind is, "Just because the client managed to contact your server once doesn't mean it will have fast and easy access to it in perpetuity." Indeed, in my experience, too many roundtrips is the cause of most atrociously slow websites, regardless of how heavy or light a framework they use.


Bartosz Milewski argues that we can think of functions etc. as containers as well, if you check out his YouTube lectures on Category Theory for Programmers. Lists and functions "contain" a type.


A term's utility comes from its ability to separate things into different categories. A definition of "container" that includes everything is therefore useless, because if everything a container, there is no information in the statement that something is a container.

In Bartosz's case he's probably making the precise point that we can abstract out to that point and that at a super, super high level of category theory, there isn't anything that isn't a container. However, that's a didactic point, not a general truth. In general we programmers generally do mean something by the word "container", and functors can indeed include things that are therefore not containers.

Moreover, I would say it's not what the author was thinking. The author is not operating on that super high level of category theory.


A list [b] is a container for bs indexed by integers. A function a->b is a container for bs indexed by as.


[b] is more like a blueprint for a container, and a->b is more like an assembly line of containers.


> A function a->b is a container for bs

Anecdotally, this is one of those things that's trivially true to some people, but really hard for other people to internalize. I think it's why the "container" can lead people astray- if you haven't internalized the idea of functions as being indexed by their argument, it's a really mind twisting thing to try to make that leap.


One of the fun things about Clojure that reinforces this "trivially true" perspective is that maps and sets are functions:

    ;; "maps" the keys to the values
    (map {1 "a" 2 "b"} (take 5 (cycle 1 2))) ;;=> '("a" "b" "a" "b" "a")
    ;; acts as a predicate that tests for membership
    (filter #{"a" "b" "c"} ["a" "b" "c" "d" "e" "f"]) ;;=> '("a" "b" "c")
Once you get used to this idiom you naturally start thing of other functions (or applicative functors) the same way. The syntax sugar makes for some very concise and expressive code too.


If I give you a function "f(x) := 3 * x", is it really that useful to talk about it as a container of the natural numbers?

The reverse though is useful, a container looks like a function that takes one or more indices and returns a value or element.


I think that understanding the (moral) equivalence is useful in both directions. In particular, I think helping people understand the "function-as-container" analogy is a useful way for people to understand pure functions- another thing that's conceptually simple but a lot of people struggle to really wrap their mind around it.


Personally I would say it muddies the water for me, as "container" has strong connotations in other directions.

But then I've never thought the concept of a pure function to be particularly difficult, despite growing up on procedural languages.

It's other bits that I struggle with when it comes to functional programming.


I can recommend learnung some scala, where HashMap extends PartialFunction


I've never looked at scala, but that's really interesting. Do you find that's useful in practice?


> really hard [...] leap

Two stepping stones might be array getters (function that's array-ish), and arrays with an indexed default value function (array that's function-ish)?


I've recently started writing a series of blog posts (https://rebeccaskinner.net/posts/2024-10-18-dictionaries-are...) trying to explain the idea and my approach has been to explain the idea using comprehensions. I haven't had a lot of people review the post yet, and I still have at least one if not two more follow-ups before it's done, so I'm not yet sure how well the idea will land.


Nice introduction. Still not entirely sold that dictionaries are pure functions, though.

Will you be covering common dictionary operations like adding/removing elements and iterating over the dictionary keys?

I have some ideas on how one might frame it in a pure function setting but they all seem quite contorted in a similar way to your incrementDict, ie you'd never actually do that, so curious if there are better ways. Then maybe you'll sell me on the premise.


I'm really focusing less on the idea that Dict the data type with it's associated methods is like a function, and more on the idea that a dictionary in the general sense is a mapping of input values to output values, and you can think of functions that way.

That said, there are some pretty reasonable analogies to be made between common dictionary operations and functions.

For example, adding and removing items can be done with function composition so long as you are okay with partial lookups. Here's a really short example I put together:

  module Example where
  import Control.Applicative

  type Dict a b = Eq a => a -> Maybe b

  emptyDict :: Dict a b
  emptyDict = const Nothing

  singleton :: a -> b -> Dict a b
  singleton k v target
    | k == target = Just v
    | otherwise = Nothing

  unionDict :: Dict a b -> Dict a b -> Dict a b
  unionDict dict1 dict2 k = dict1 k <|> dict2 k

  insertDict :: a -> b -> Dict a b -> Dict a b
  insertDict k v dict = singleton k v `unionDict` dict

  removeDict :: a -> Dict a b -> Dict a b
  removeDict k dict target
    | k == target = Nothing
    | otherwise = dict k
This particular representation of dictionaries isn't necessarily something you'd really want to do, but the general approach can be quite useful when you start working with something like GADTs and you end up with things like:

  data Smaller a where
    SmallerInt :: Smaller Int
    SmallerBool :: Smaller Bool
  
  data Larger a where
    LargerInt :: Larger Int
    LargerBool :: Larger Bool
    LargerString :: Larger String
  
  someLarger :: Larger x -> x
  someLarger l =
    case l of
      LargerInt -> 5
      LargerBool -> True
      LargerString -> "foo"
  
  embedLarger ::
    (forall x. Larger x -> Smaller x) ->
    (forall smallerI. Smaller smallerI -> r) ->
    (forall largerI. Larger largerI) -> r
  embedLarger mapping fromSmaller larger = fromSmaller (mapping larger)
(I'm actually co-authoring a talk for zurihac this year on this pattern, so I have quite a bit more to say on it, but probably not ideal to cram all of that into this comment).


> and more on the idea that a dictionary in the general sense is a mapping of input values to output values, and you can think of functions that way.

So what's the difference between a map and a dictionary then?

> Here's a really short example I put together

Much appreciated. I don't really know Haskell (nor any other functional language), but I'm pretty sure I understood it.

> This particular representation of dictionaries isn't necessarily something you'd really want to do

Yeah that's pretty much what I had in mind, and yes it's possible but it feels forced. For one you're not actually removing an element, you just make it impossible to retrieve. A distinction that might seem moot until you try to use it, depending on the compiler magic available.

> I'm actually co-authoring a talk for zurihac this year on this pattern

Sounds interesting, will check it out when it's published.


> So what's the difference between a map and a dictionary then?

You're asking good questions and catching me being imprecise with my language. Let me try to explain what I'm thinking about more precisely without (hopefully) getting too formal.

When I say "a function is a mapping of values" I'm really trying to convey the idea of mathematical functions in the "value goes in, value comes out" sense. In a pure function, the same input always returns the same output. If you have a finite number of inputs, you could simply replace your function with a lookup table and it would behave the same way.

When I talk about dictionaries, I'm speaking a little loosely and sometimes I'm taking about particular values (or instances of a python Dict), and other times I'm being more abstract. In any case though, I'm generally trying to get across the idea that you have a similar relationship where for any key (input) you get a particular output.

(aside: Literally right now as I'm typing this comment, I also realize I've been implicitly assuming that I'm talking about an _immutable_ value, and I've been remiss in not mentioning that. I just want to say that I really appreciate this thread because, if nothing else, I'm going to edit my blog post to make that more clear.)

The main point is that dictionaries are made up of discrete keys and have, in Python at least, a finite number of keys. Neither of those constraints necessarily apply to functions, so we end up in an "all dictionaries are functions, but not all functions are dictionaries" situation.

> Yeah that's pretty much what I had in mind, and yes it's possible but it feels forced. For one you're not actually removing an element, you just make it impossible to retrieve. A distinction that might seem moot until you try to use it, depending on the compiler magic available.

This is a great example of the kind of thinking I'm trying to address in the article. You're completely right in a very mechanical "this is what memory is doing in the computer" sort of sense, but from the standpoint of reasoning about the problem space deleting an element and being unable to access the element are the same thing.

Of course in the real world we can't completely handwave away how much memory our program uses, or the fact that a function encoding of a dictionary turns a constant time lookup into a linear time lookup. Those are real concerns that you have to deal with for non-trival applications, even in a pure functional language.

The benefit you get, and I apologize because this is hard to explain- let alone prove, is that you can often end up with a much _better_ solution to problems when you start by handwaving away those details. It opens up the solution space to you. Transformations to your architecture and the way you think about your program can be applied regardless of the specific representation, and it's a really powerful way to think about programming in general.


Thanks for the detailed responses, highly appreciated.

I taught myself programming as a kid using QBasic, and quickly moved on to Turbo Pascal and assembly, so clearly my programming career was doomed from the start[1].

For one I do like to keep in mind how it will actually be executed. The times I've not done that it has usually come back to bite me. But that perhaps hampers me a bit when reading very abstract work.

That said I like going outside my comfortable box, as I often find useful things even though they might not be directly applicable to what I normally do. Like you say, often changing the point of view can help alot, something that can often be done in a general way.

Anyway, looking forward to the rest of the article series and the talk.

[1]: https://en.wikiquote.org/wiki/Edsger_W._Dijkstra#How_do_we_t...


Interesting. I liked how "Dictionaries are Pure Functions" set up currying as JSON nested dictionaries.

Curiously, I've a backburnered esolang idea of gathering up the rich variety of dict-associated tooling one never gets to have all in one place, and then making everything dict-like. Permitting say xpath sets across function compositions.


I want a MacBook Air with Linux running on it perfectly, audio, microphones everything. Would be so nice.


Yeah, especially if the touchpad worked flawlessly too. A distant dream


The way this article talks about using "recursive real arithmetic" (RRA) reminds me of an excellent discussion with Conal Elliot on the Type Theory For All podcast. He talked about moving from representing things discretely to representing things continually (and therefore more accurately). For instance, before, people represented fonts as blocks of pixels, (discrete.) They were rough approximations of what the font really was. But then they started to be recognized as lines/vectors (continual), no matter the size, they represented exactly what a font was.

Conal gave a beautiful case for how comp sci should be about pursuing truth like that, and not just learning the latest commercial tool. I see the same dogged pursuit of true, accurate representation in this beatiful story.

- https://www.typetheoryforall.com/episodes/the-lost-elegance-...

- https://www.typetheoryforall.com/episodes/denotational-desig...


Thanks, that's a lovely analogy and I'm excited to listen to that podcast.

I think the general idea of converting things from discrete and implementation-motivated representations to higher-level abstract descriptions (bitmaps to vectors, in your example) is great. It's actually something I'm very interested in, since the higher-level representations are usually much easier to do interesting transformations to. (Another example is going from meshes to SDFs for 3D models.)

You might get a kick out of the "responsive pixel art" HN post from 2015 which implements this idea in a unique way: https://news.ycombinator.com/item?id=11253649


Thanks, that "responsive pixel art" is very cool!


I have a friend who works on COBOL for banking. He said he saw someone working at a bank with a fancy new GUI, painfully poking around and trying to get something done. "Ah forget it," they said, closed the modern GUI and went to the old terminal interface. In a couple of seconds, it was done.

I also worked on an ancient terminal interface for a complex service business. It was amazing. Everything was instant, and after a short learning curve, we had incredible power at our fingertips. If I had to do that job on a modern web interface with 4-5 seconds of spinners on every page, productivity would plummet! How often do I stand in front a desk with someone eeking through a more modern system, wondering, what is taking so long?


This. COBOL is actually quite performant given what it has to deal with. To answer the OP’s question: when you have 2M lines of COBOL that processes thousands of medical records or financial transactions per second, is critically important to the business, and essentially never fails, there is absolutely no incentive to ever replace that code. It doesn’t matter if it’s “out of date” or in an uncool language.


I don’t understand at what point people started settling for anything less than instantaneous user interfaces

I believe it was Ted Nelson who stated, as a commandment, A computer must never make a human wait.


Microsoft Windows 3.1. I remember the moment very well.

We went from a focused instantaneous UI in DOS to a paradigm that most people cannot handle...multitasking.

Some have mentioned density but DOS business applications weren't terribly dense. There was only so much screen area and we had conventions for how to use it. The apps would flow between screens much like modern Windows UI or mobile apps. The move to Windows presented users with a slow dense mess. My grandmother could set the screen on fire using Lotus 123 keyboard commands and bang out financial statements all day long. I was maintaining Lotus on her machine for decades after the Windows switch so she could use her keyboard and get shit done.

Quicken was awesome on DOS. We ran our bookkeeping business on Lotus 123 and Quicken for years (1980s-1990s). Productivity crashed after that and never recovered.


It's damn near a crime against humanity that so many talented people are forced to deal with garbage software. Given the orders of magnitude hardware advances of the last few decades, we should be that much faster and more capable. But that's not what we see at all - it takes a special kind of incompetence to make software do even less while increasing the resource burn. You really have to try to do that bad. And we're trying hard. I weep for our industry.


I think you just described software that was developed by professionals vs software that was developed by warm bodies.


That's much scarier to me.


Okay, what if we just use Java instead of Java"script"?


Java applets were a thing. Emphasis on "were"

They were security nightmares.


Now you are just crazy


Great write-up and I must say a surprising conclusion and good case for OCaml. I also love Haskell but using it for a practical project I came up against some of the pain points mentioned here. If I could suggest some Emglish grammar fixes to this great article, the word order needs to be flipped around for the interrogative sentences.

Wait, why doesn't the standard library have docs at all for this version I use?

Instead of:

> Wait, why the standard library doesn't have docs at all for this version I use?

And

How can Haskellers live like that without these usability essentials?

Instead of:

> How Haskellers can live like that without these usability essentials?


Happy to hear you enjoyed the article!

Thanks for the suggestions! English is not my first language, so mistakes could happen. I hope it's not too off-putting, and people can still learn something interesting from articles.

I'll incorporate your suggestions :)


Your English is great and clear! Those kinds of mistakes (not reordering the word order in interrogative clauses) are so common that in the upcoming years that may become the new standard for English grammar! But for now I will keep trying to correct them. ;-)


> "A good way of seeing how a subject works is to examine the proof of a major result and see what previous results were used in it. Then trace these results back to earlier results used to prove them. Eventually you will work your way back to definitions"

I find the parallels between proofs and programs to be fascinating. We could write an analogous thing for programming:

"A good way of seeing how a sort of program works is to examine one of the popular programs/libraries and see what functions were used in it. Then look inside of those functions and see what functions are used inside of those. Eventually you will work your way back to the lower level primitives."


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: