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

Perhaps they meant censure instead of censor.


Note especially that in many languages "censor" and "censure" are the same word.


I did indeed.


My main complaint the last time I did this (2022) was the havoc it wreaked on my sleep schedule. Advent of Code is not kind to East Coast participants.

Every year except for one has been kind of the same pattern for me:

Day 1: this year, I'm just going to solve the problems. No futzing around.

Day 3: but it would be kind of neat to turn the solutions into a reusable AoC library. Just something minimal.

Day 5: and I should really add a CLI harness for retrieving the problems and parsing the input files.

Day 6: and testing of course.

Day 7: maybe I'll skip today's problem (just for today) and keep improving the framework.

Day 358: oh neat, Advent of Code is coming up.


I'm in CET so time-wise it can be ok - problems open at 6am meaning if I get up I have about an hour around before I need to walk my walk my dog and get ready for work. But switching on at that time is really hard, the amount of stupid off-by-one errors, or referring to since-renamed-but-still-present functions in my Jupyter Notebook is not even funny.

But I luckily managed to avoid the "reusable AoC library" problem around 2019 when a week beforehand I wrote down the sort of functions I wanted to have at my disposal (usually things around representing 2D/3D grids of unknown size and pathfinding/debugging therein, but a few other bits and pieces) and made a simple library that I will sometimes add things to after I'm done with the problem for the day.

I was tempted to some functions (similar to those your CLI harness provided) for retrieving test data and submitting answers but I managed to stop myself short of that! But I am sure you're far from the only one to end up down that road.


I'm in CET too, and 6:00 is not an hour where I’m awake, and if I were, my brain functions would definitely not be at a level where I would be capable of coding.

Midnight would be much more acceptable.


I think you'd be surprised - you'll definitely be capable of coding at that hour. But like me you'd just also be quite capable of making daft mistakes :D


What if you don't get up?


If it's a work day and I don't wake up on time, I'll pick away at it over the course of the day - usually I'll get a chance to think about it on my tram ride to work and complete it at lunchtime

If it's a weekend I'll just do it at my leisure at some point during the day when I have some time - maybe head to a nice cafe or something.

I'm nowhere near the top 100 - closest has been iirc top 200 a few years back - so it's not like I need to start at 6am.


I have trouble fitting this kind of thing in consistently. It's hard with work, chores, family, and then motivation to code after coding at work.


Stay out of my head!


It's not a matter of agreeing accidentally, they just plain agree with you.


+1. If a phone has a stopwatch, you can get your bpm at a given moment with a finger and a multiplication (or patience). Given the limited real estate on a mobile phone it's crazy to devote any space for something so trivial.


"Design patterns are really Band-Aids for missing language features" comes from a 1996 Peter Norvig presentation[0][1]:

> Some suggest that design patterns may be a sign that features are missing in a given programming language (Java or C++ for instance). Peter Norvig demonstrates that 16 out of the 23 patterns in the Design Patterns book (which is primarily focused on C++) are simplified or eliminated (via direct language support) in Lisp or Dylan.

[0]: https://en.wikipedia.org/wiki/Software_design_pattern#Critic...

[1]: slide 9 of PDF https://www.norvig.com/design-patterns/design-patterns.pdf


Local dealerships will take cash but if you show up with enough of it (even a very large down payment amount), it's not uncommon that the person you try to give it to isn't sure they are allowed to accept it and will disappear to run it up the chain to get permission.


Your account is dead but none of the posts in your history look bad. You can email hn@ycombinator.com and if there has been a mistake they'll sort it out.


Thanks I’m not sure what happened but it seems to be fixed…


When someone asks for examples of particularly bad actors, replying "everybody is a bad actor" is a pat non-answer that communicates nothing except the answerer's disillusionment.


True, but it’s useful disillusionement that conveys useful knowledge: companies must be monitored. Every industry, left to itself, will commit horrible acts for profit. There’s no such thing as an ethical business in the dark.


I disagree.

Think of the places where corruption is endemic. Having an outlook of "well, everybody is corrupt" normalizes the behavior and makes it easier to justify sliding into corruption oneself.

If the outlook were useful, it would help to fix the problem instead of doing the opposite.

The disillusionment doesn't offer any benefit that can't also be gained with a touch of common sense alone. But going from "some people are terrible, sometimes" to "everybody is terrible" forecloses any possibility of improvement.


Agree with your disagreement.

To ignore gradations of disappointment is to excuse everything via apathy.

One thing can be bad, while another is worse.


how does a company like Costco unleash a global propaganda campaign to shift blame for things like global warming to individuals? how many governments did they overthrow?

tell me about how netflix dumped millions upon millions of gallons of oil in the ocean and wiped out entire ecologies.

There are definitely egregiously-bad actors compared to those that aren't.


In the case of this tool, adding a failing test case looks trivial if you've got the URL of a page it fails on.

Provided the maintainer is willing to provide some minimal guidance to issue reporters who lack the necessary know-how, it even seems like a clever back door way of helping people learn to contribute to open source.


> Tight coupling is the basis of oop.

This is not what OOP people are talking about when they talk about tight or loose coupling though.

They are talking about the relationship between classes.


And that’s the fundamental problem. They fail to see that if they took those same methods and made it independent of state then those things are now called functions.

Functions can be moved to different scopes. Functions don’t rely on state to exist.

You can compose functions with other functions to build new functions.

And here’s the kicker. All of these functions did the same thing as the method.

Functions are more modular. A method, is a restricted function that is tightly bounded with state and all other sibling methods.


There's a trade-off.

Functional programs are easier to read, because the structure makes the state transitions and dependencies obvious - you see your dependencies in the arguments list. But it forces you to basically rewrite big parts of your program after even very simple changes.

You had the structure of the program so fine-tuned to the dependencies of every part of the code - that when any dependencies change you have to completely change that structure. It's rewrite-only programming style.

Imperative (and OO) programming idiomatically let you do a bigger mess with side effects, and you know less about the data dependencies just from looking at the function specifications - but it also allows you to do exploratory programming much faster (no need to pass a new argument down 20 levels of your call stack when some code deep down suddenly requires a new argument). And it allows you to modify the behaviour locally without refactoring the whole thing constantly.

If you have a for loop that filters out even numbers and suddenly you want to sum the numbers and find maximum and minimum too - most of the code stays the same.

If you have functional code doing the same and want to modify it in similar manner - it's a completely different code. Most people would just rewrite it from scratch.

And that's just a very small scale example. With larger programs the rewrite gets harder.

That's why big programs are almost never functional.


>If you have a for loop that filters out even numbers and suddenly you want to sum the numbers and find maximum and minimum too - most of the code stays the same.

   x = [1,2,3,4,5,6,7,8]
   even = [i for i in x if x%2 == 0]
   
Now I want to sum the numbers and add maximum and minimum too.

  s = max(x) + min(x)
  res = reduce(lambda acc, y: acc + y, x, 0) 
I achieved your desired goal without rewriting code? The thing is with functional programming all state is immutable, so you can always access intermediary state without modification of the program at all.

It's an improvement on imperative and OO. Because I only needed to add additional code to achieve the additional goal and those additions are modular and moveable. With imperative I would be changing the code and changing the nature of the original logic and none of it is modular and all of it is tightly integrated.


Sure, if you want to have 4 loops where 1 suffices.


The big oh is the same man. It just feels like it’s less efficient but it’s not. Think about it.

And it’s more modular with more loops. If you’re trying to shove 4 different operations into one loop you’re not programming modularly and you’re trying to take shortcuts.


Reduce FPS of your game from 60 to 15, tell players it's the same cause complexity haven't changed.

But it's not even mainly about performance. The structure of the code changes with every requirement change. In a non-artificial code you're doing stuff other than calculating the result, and all the associated state and dependencies now have to be passed to 4 different loops.

While in the ugly non-modular imperative code you add 3 local variables and you're done, everything outside that innermost loop stays exactly the same.

> you’re trying to take shortcuts

Yes, that's the point. I started by admiting FP code is more elegant. But shortcuts are not inherently worse than elegance. They are just the opposite sides of a trade-off.


tbh FP is so heavily modularized that even the concept of "looping" is modularized away from the logical operation itself. In haskell it looks like this:

   f = a . b . c . d
   a1 = map a 
   b1 = map b
   c1 = map c
   d1 = map d
   f2 = a1 . b1 . c1 . d1
Where f is the composition of operations on a single value and f2 operates on a list of values and returns the mapped list.


Gaming and applications that require extreme performance is the only application where Fp doesn’t work well.

> But it's not even mainly about performance. The structure of the code changes with every requirement change. In a non-artificial code you're doing stuff other than calculating the result, and all the associated state and dependencies now have to be passed to 4 different loops.

No. I’m saying the initial design needs to be one loop for every module. You then compose the modules to form higher level compositions.

If you want new operations then all you do is use the operations on intermediary state.

That’s how the design should be. Your primitive modules remain untouched through out the life cycle of your code and any additional requirements are simply new modules or new compositions of unchanged and solid design modules.


>Gaming and applications that require extreme performance is the only application where Fp doesn’t work well.

Sure, if you are considering pure functional programming. But neither pure functional OOP does work well in a performance context.

If you mix imperative/procedural and functional programming you can have clarity, ease of use, ease of change and some performance, too.


Each has its trade offs you lose clarity, ease of use, ease of change and performance even when you mix all styles.


>Functional programs are easier to read, because the structure makes the state transitions and dependencies obvious - you see your dependencies in the arguments list. But it forces you to basically rewrite big parts of your program after even very simple changes.

Disagree. Readability is opinionated so I won't address that but this is an example of functional:

   gg = (x) => x * x * x

   y = 1
   a = (x) => x + 1
   b = (x) => x * 2
   c = (x) => x * x
   d = (x) => x - 4
   f = a . b . c . d
   
   result = f(y)
OOP example:

   class Domain1
      constructor(n: int)
          this.x = n

      def gg()
         this.x *= this.x * this.x      

      def getX() -> int
         return this.x

   class Domain2:
      constructor(n: int)
          this.x = n
      
      def a:
          this.x += 1

      def b:
          this.x *= 2

      def c:
          this.x *= this.x

      def d:
          this.x -= 4

      def f:
         this.a()
         this.b()
         this.c()
         this.d()

      def getX() -> int
         return this.x

  state = Domain(1)
  state.f()
  result = state.getX()   

         
What if realize d and a fits better in Domain1 and I want to compose d and a with gg in the OOP program? I have to refactor Domain2 and Domain1. Or I create a Domain3 that includes a Domain1 and a Domain2

How do I do it in functional programming?

      domain3 = gg . d . a


      #Note I use the fucntion composition operator which means: a . b = (x) => b(a(x)) or a . b . c = (x) => c(b(a(x)))

Functions by nature with the right types are composable without modification. A can compose with B without A knowing about B or vice versa. The same cannot be said for objects.

Try achieving the same goal with OOP.... It will be a mess of instantiating state and objects within objects and refactoring your classes. OOP is NOT modular.

It's pretty clear. One style is more modular than the other. Objects tie methods to state such that the methods are tied to each other and can't be composed without instantiating state or doing complex Object compositions and rewriting the Objects themselves.

In programming you want legos. You want legos to compose. You don't want legos that don't fit such that you have to break the legos or glue them together.

>Imperative (and OO) programming idiomatically let you do a bigger mess with side effects, and you know less about the data dependencies just from looking at the function specifications - but it also allows you to do exploratory programming much faster (no need to pass a new argument down 20 levels of your call stack when some code deep down suddenly requires a new argument). And it allows you to modify the behaviour locally without refactoring the whole thing constantly.

you shouldn't be programming with state ever if you're doing FP. You need to segregate state away from your program as much as possible. State by nature is hard to modularize. State should be very simple generic mutation operations like getValue and setValue, it should rarely ever contain operational logic.


Your examples are inherently functional, and in practice people would do ABCDService that just returns the result. But ignoring that - how is the code changing when you need to call some external API from the c(x) function and handle the credentials, session, errors etc? Real world has external state and we do need to work with it.

> you shouldn't be programming with state ever if you're doing FP. You need to segregate state away from your program as much as possible. State by nature is hard to modularize. State should be very simple generic mutation operations like getValue and setValue, it should rarely ever contain operational logic.

This approach to state management is exactly what is causing the need to rewrite almost everything when requirements change in a functional program.

I like how clean FP code is when it's done. But I hate writing FP code when I'm not 100% sure what needs to be done and what might change in the future. If I could write imperative code with side effects and once I'm done have it transpiled into efficient, elegant, minimized state functional code - that would be great. Maybe it will happen at some point with AI getting better.


> Your examples are inherently functional, and in practice people would do ABCDService that just returns the result. But ignoring that - how is the code changing when you need to call some external API from the c(x) function and handle the credentials, session, errors etc? Real world has external state and we do need to work with it.

Abcdservice is bad because I want to use a b c and d in different contexts. You’re saying in the real world oop promotes a style where you can’t break down your code into legos. With oop you need to glue a b c and d together.

My code is not inherently functional. I literally picked the smallest possible logical operations and interpreted them as either functional or oop. And then I tried to compose the logical operations.

I mean look at it. A b and c are just one or two mathematical operators. If you’re saying this is inherently functional then your saying computing at its most primitive state is inherently functional.

> This approach to state management is exactly what is causing the need to rewrite almost everything when requirements change in a functional program.

Not true. Fp programs segregate state. Look at my code. All the code for fp is stateless. The only state is y=1.

> But I hate writing FP code when I'm not 100% sure what needs to be done and what might change in the future.

You hate what’s inherently better for the future. Fp is more modular and therefore more adaptable for the future. You hate it because you don’t get it.


> If you’re saying this is inherently functional then your saying computing at its most primitive state is inherently functional.

Sure. But computing isn't what most code does.

> Fp is more modular and therefore more adaptable for the future

That is wrong. It's cleaner to read, but it usually requires more lines of code to be changed when requirements change - so it's less adaptable.

Before changes

FP:

   y = 1
   a = (x) => x + 1
   b = (x) => x \* 2
   c = (x) => x \* x
   d = (x) => x - 4
   f = a . b . c . d

   result = f(y)
ugly imperative code:

    y = 1;
    void doABCD() {
       y += 1;
       y *= 2;
       y *= y;
       y -= 4;
    }
Now you want to count how many times you squared numbers larger than 1000.

FP:

   y = [1, 0]
   a = (x) => [x[0] + 1, x[1]]
   b = (x) => [x[0] \* 2, x[1]]
   c = (x) => [x[0] \* x, x>1000 ? x[1]+1 : x[1]]
   d = (x) => [x[0] - 4, x[1]]
   f = a . b . c . d

   result = f(y)
imperative:

    y = 1;
    count = 0;
    void doABCD() {
       y += 1;
       y *= 2;
       if (y > 1000)
           count ++;
       y *= y;
       y -= 4;
    }
Total lines changed in FP - all except 2. Total lines changed in imperative - 3.

Of course you can refactor the FP version to split the part that requires the new state from the other parts. But in any big program that refactor is going to be PITA.

Do you get my point now? I'm not saying imperative is better. It is ugly. But it's faster to adapt to the new requirements.


No way. Imperative is worse.

You're just not using FP correctly. You're trying to do something monadic which is something I would avoid unless we absolutely need an actual side effect.

   y = 1
   a = (x) => x + 1
   b = (x) => x \* 2
   c = (x) => x \* x
   d = (x) => x - 4
   f = a . b . c . d

   result = f(y)
You're doing the refactor wrong let me show you. You have to compose new pipelines that reveal the intermediate values.

   count = 0
   firstPart = a . b
   secondPart = c . d
   countIfGreaterThan1000 = (x, prevCount) => x > 1000 ? prevCount + 1 : prevCount

   n = firstPart(y)
   newCount = countIfGreaterThan1000(n, count)
   result = secondPart(n)
The key here isn't the amount of lines of code. The key here is to see that under FP the original code is like legos. If you want to reconfigure your fundamental primitives you just recompose it into something different. You don't have to modify your original library of primitives. With OOP you HAVE to modify it. doABCD() can't be reused. What if I want something additional (doABCD2) that does the EXACT same thing as doABCD() but now without counting the amount of times something was squared and greater than 1000 but now instead I want it for the amount of times the total was greater than 3?

You can't reconfigure the code. You have to duplicate the code now.

Basically you have to imagine functional programming as pipelines. If you want to add something in the middle of the pipeline, you cut the composition in half and split the pipe. One pipe goes towards the end result of what d outputs and the other pipe goes towards countGreaterThan1000


So your solution to changing 5 lines out of 7 was to do the refactor I wrote about and change 7 lines :)

I agree it's prettier. But it's objectively a larger change than the 3 lines you'd do in the imperative code. And it's pretty much how adapting to changes usually goes with FP. You constantly have to change the outermost structure of the program even if the change in the requirements is localized to one specific corner case.

> What if I want something that does the EXACT same thing as doABCD() but now without counting the amount of times something was squared and greater than 1000 but now instead I want it for the amount of times the total was greater than 3?

> You can't reconfigure the code. You have to duplicate the code now.

I could, but at this point refactoring is warranted.

    y = 1;
    y2 = 1;
    count = 0;
    _ = 0;
    count2 = 0;
    void doABCD(int &y, int &count, int &count2) {
       y += 1;
       y *= 2;
       if (y > 1000)
           count ++;
       y *= y;
       y -= 4;
       if (y > 3)
           count2 ++;
    }
    doABCD(y, count, _);
    doABCD(y2, _, count2);
8 changes. 11 in total for both modifications.

In FP you had 7 lines of code changed for the first refactor

   y = 1
   a = (x) => x + 1
   b = (x) => x \* 2
   c = (x) => x \* x
   d = (x) => x - 4
   count = 0;
   firstPart = a . b
   secondPart = c . d
   countIfGreaterThan1000= (x, prevCount) => x > 1000 ? prevCount + 1 : prevCount
   n = firstPart(y)
   newCount = countIfGreaterThan1000(n, count)
   result = secondPart(n)
and now you'd have sth like

   y = 1
   y2 = 1
   a = (x) => x + 1
   b = (x) => x \* 2
   c = (x) => x \* x
   d = (x) => x - 4
   count = 0
   count2 = 0
   firstPart = a . b
   secondPart = c . d
   countIfGreaterThan = (x, target, prevCount) => x > target ? prevCount + 1 : prevCount
   n = firstPart(y)
   newCount = countIfGreaterThan(n, 1000, count)
   result = secondPart(n)
   result2 = (firstPart . secondPart) (y2)
   newCount2 = countIfGreaterThan(result2, 3, count2)
That's 7 + 6 = 13 lines for 2 changes if I'm counting correctly.

What FP buys you is not deduplication (you can do that in any paradigm) - it's easier understanding of the code.


Let me emphasize it’s not about prettier. Prettier doesn’t matter.

The key is that the original code is untouched. I don’t have to modify the original code. Anytime you modify your original code it means you initially designed poor primitives. It means you made a mistake in the beginning and you didn’t design your code in a modular way. It’s a design problem. You designed your code wrong in the beginning so when a new change is introduced you have to modify your design. This is literally a form of technical debt.

Do you see what Fp solves? I am not redesigning my code. I made the perfect abstraction from the beginning. The design was already perfect such that I don’t have to change anything about the original primitives. That is the benefit of Fp.

Nirvana in programming is to find the ultimate design scheme such that you never need to do redesigns. Your code becomes so modular that you are simply reconfiguring modules or adding modules as new requirements are introduced. Any time you redesign it means there was technical debt in your design. Your design was not flexible enough to account for changing requirements.

Stop looking at lines. In the real world if you modify your code that usually cascades into thousands of changes on dependent code. In Fp I simply link modules together in a different way. The core primitives remain the same. The original design is solid enough that I don’t change code. I just add new features to the design.

Also for your example you misinterpreted what I said. I don’t want to change the original signature of doABCD because it’s already used everywhere in the application. I want a new doABCD2 that does exactly the same as the original. Remove the side effect from the original and add a new side effect to the new doABCD of counting something else.

Do it without duplicating code or refactoring because duplicate code is technical debt and refactoring old code is admission that old code was not the right design. Be mindful that refactoring the signature means changing all the thousands of other code that depends on doABCD. I don’t want to do that. I want new features to be added to an already perfect design.

FP in my opinion, ironically is actually harder to read.


> You designed your code wrong in the beginning so when a new change is introduced you have to modify your design. This is literally a form of technical debt.

Yes. And just like in real life if you want to do business - you have to accept some degree of debt to get anywhere. Trying to predict the future and make the perfect design upfront is almost always a mistake.

> Stop looking at lines

We can't communicate without establishing some objective measures. Otherways we'll just spew contradictory statements at each other. These toy examples are bad, obviously, but the fact that there's basically no big functional programs speaks for itself.

> refactoring old code is admission that old code was not the right design

And that's perfectly fine.

> I want a new doABCD2 that does exactly the same as the original. Remove the side effect from the original and add a new side effect to the new doABCD of counting something else.

According to your definition of "code changed" if I duplicate everything and leave the old lines there - no code was changed which means the design was perfect :)

I don't think we'll get to a point where we agree about this. One last thing I'd like to know is why do you think nobody writes big projects in functional languages?


>Yes. And just like in real life if you want to do business - you have to accept some degree of debt to get anywhere. Trying to predict the future and make the perfect design upfront is almost always a mistake.

And I'm saying FP offers a way to avoid this type of debt all together. You can accept it if you want. I'm just telling you of a methodology that avoids debt: A perfect initial design that doesn't need refactoring.

>We can't communicate without establishing some objective measures. Otherways we'll just spew contradictory statements at each other. These toy examples are bad, obviously, but the fact that there's basically no big functional programs speaks for itself.

Sure then I'm saying lines of code is not an objective measure. Let's establish another objective measure that's more "good": The amount of lines of structural changes made to the original design. It's universally accepted that lines of code aren't really a good measure but it's one of the few quantitative numbers. So I offer a new metric. How many lines of the original design did you change? In mine: 0.

I don't want to write the psuedocode for it, but let's say doABCD() is called in 1000 different places as well. Then in the imperative code you have 1000 lines of changes thanks to a structural change. Structural design changes leads to exponential changes in the rest of the code hence this is a better metric.

That's an objective measure showing how FP is better. I didn't take any jumps into intuition here and I am sticking with your definition of an "objective measure"

>And that's perfectly fine.

That's just opinion. Surely you see the benefit of a perfect initial design such that code never needs refactoring. It happens so often in business that it's normal to refactor code. But I'm saying here's a way where you perfect your design in the beginning. That's the whole point of modularity right? It's an attempt to anticipate future changes and minimize refactoring and FP offers this in a way Objectively better than imperative. If your always changing the design when a new feature was added what's the point of writing modular and well designed code? Just make it work and forget about everything else because it's "okay" to redesign it.

>According to your definition of "code changed" if I duplicate everything and leave the old lines there - no code was changed which means the design was perfect :)

But then you introduced more technical debt. You duplicated logic. What if I want to change the "a" operation. Now I have to change it for both doABCD and doABCD2. Let's assume I have doABCD3 and 4 and 5 and 6 all the way to 20 who all use operation "a" and now they all have to be changed because they all used duplicate code.

Let's not be pedantic. Refactoring code is a sign of technical debt from the past. But also obviously duplicating code is also known to be technical debt.

>I don't think we'll get to a point where we agree about this.

Sure but under objective measures FP has better metrics. Opinion wise we may never agree, but objectively if we use more detailed and comprehensive rules for the metric, FP is better.

>One last thing I'd like to know is why do you think nobody writes big projects in functional languages?

Part of the reason is because of people with your mentality who don't understand. It's the same reason why the US doesn't use metric. Old cultural habits on top of lack of understanding.


> And here’s the kicker. All of these functions did the same thing as the method.

My only question is if you have function x, y and z, how do you restrict function z so it can only be called from function x, but not from function y? If you have classes you can use access modifiers.


You don’t need to restrict functions.

You can still organize functions into groups via libraries or modules. But ultimately that’s just aesthetics.

    ModulaA.funcA
    ModuleB.funcB

FuncA cannot be called from ModuleB. This restriction is possible in terms of organizing code but you can see it’s just naming and ultimately has no effect outside of aesthetic appeal.


Why do you want that if there is no state involved?


I wasn't referring to a pure functional context. I was thinking of a context where you have functions and procedures but no objects.


You can still have modules, for example. They're a way of grouping functions and types.


Exactly. These functions are stateless and thus calling the functions ultimately doesn’t really do anything.


Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: