Ask HN: What's the most simple thing you struggle to understand in programming?

41 points by betimd ↗ HN
Always there's something you struggle to understand and you're surprised w/ yourself

62 comments

[ 2.8 ms ] story [ 132 ms ] thread
Where to start, and what to make.
Just think of something cool that you'd like.
Every asynchronous programming API ever created.

My program spawns and runs in the main thread. It calls the main function. If it want it to run forever, I'll stick a while(1) loop in that function.

Now I want to have a particular thing go off and do some work in parallel with that main loop, and when it's done, I want the next iteration of the main loop to pick up on that fact.

Not once have I encountered a sane API for that use-case as easily expressed as those sentences of prose above.

I can think of a couple trivial ways to express this in Erlang/Elixir. You should take a look at them.
I concede I've not studied those languages yet - I do hear good things about them though, so I'll take a look next suitable chance I get!
You know I came from a procedural background and struggled and struggled to learn LISP to see what all the fuss was about. It was really hard because I think like you (not that that is bad or good). Anyways, it was not until I heard it explained that procedural code is about time and functional code is about space that it really clicked. It was explained to me as lightning striking, you don't care when the lightning strikes you just care where the lightning strikes, that really clicked for me, my mindset changed and I got it. I don't think it about as easy or simple, I think it is about how our minds are naturally geared. It took me a lot of mental process to get my head around a distance based thinking about code as opposed to a time based but once I did, I find reasoning about where code is called is easier that figuring about when I want it to be called. But it was not the way my mind naturally worked.
This sounds like an event loop to me. Have the main loop check a queue for events indicating completed tasks. If nothing is pending, do the default task.
Lambdas. Monads. Inner & outer joins. Tensorflow.
I think of a Lambda like you are creating a quick on the spot function/method to do something for you or for everything you have.
Lambdas are sorta just functions that you write inline and use as needed (at least in C++ land)
I compare Lambdas to anonymous functions in JS. Is that wrong? I only have experience with Lambdas in Python.
That's right, they're the same.
Thanks, I assumed so but I wasn't sure.
I have watched hours of youtube, heard hundreds of metaphors, I've read countless articles and followed even more tutorials. I am beginning to think grokking the Monad is beyond me.
I think of inner & outer joins like AND & OR. An inner join will only return a result if the value of key that the join is on exists in both tables. An outer join will give rows for all key values in both tables even if values are only in one or the other table with null results in the row for the columns of the opposite table. Inner joins are the default as you are generally interested in what matches between tables but you have the option of outer if you need to get the full messy picture or if you are doing ETL and want to ensure no badish records are missing.
My goodness. Thank you for this explanation. I've always googled the concept and forgot about it but this sticks.
This probably is ignorant, But I don't get why we need interface in java/c#. Why can't a base class with abstract methods be sufficient for that use case?
Interfaces were created because you can't extend multiple classes. It's the languages way of allowing polymorphism. You very well can create base classes and extend from them, but you will likely come across a time where you want to inherit from different things and interfaces are the only way to do that.
have almost same issue
It allows you to compose types and avoid the issues (complications not necessarily problems) of multiple inheritance.

For example, where in your hierarchy do you stick the AbstractSerializable class? If it's an interface like ISerializable you just implement it at whatever level you're at.

With multiple inheritance you don't need interfaces because you can just inherit from multiple base classes.

Sadly, ever since Java 8 added default methods to interfaces this distinction is even murkier. I'll add the reasons why I prefer interfaces though.

1. Interfaces can be used by any class no matter where they live in their class hierarchy. You can only extend a single (abstract) class in Java because the language designers saw the special rules and overrides other OOP languages like C++ used to avoid the 'diamond problem' and decided against it entirely. Note that in pre-Java 8 implementing multiple interfaces cannot lead to the diamond problem because if more than one interface exposes the same method signature then the compiler doesn't care because it doesnt have actual functionality that could be conflicting. So, because you are limited to only extending one superclass it is easier to bundle functionality in multiple smaller interfaces that can be implemented no matter where your implementing class lies in the class hierarchy.

2. Interfaces provide only a contract of functionality which in practice means they are easier to reason about and use. If you are implementing an interface you dont have to worry about calling a super constructor or any other garbage. Just implement the method signature. Of course you could ask "What if you only had abstract methods with no implementation in an abstract class", in which case I would refer you to point 1. A class that already has some existing superclass could not extend that abstract class.

I hope that clears some stuff up. Just in case you read that and thought the Java language designers were stupid for adding default methods in Java8 they had really good reasons to (mostly to support backwards compatibility with a large change called Lambdas). But having some functionality in interfaces could be argued for as well. In practice there are companion classes in the Java API that are just there to provide some base functionality that they could not do because of the previous limitations of interfaces. Also, the Java8 rules for fixing 'the diamond problem' with conflicting default methods in interfaces is pretty straightforward and rarely encountered.

Dependency injection.
I strongly suggest "Dependency Injection in .NET" by Mark Seemann, regardless of whether or not you're a .NET dev (if you understand basic OOP, C# syntax is generally pretty easy to pick up on).

Really the main reason it's "... in .NET" is because there are a few sections in the book that explain how to achieve DI in some common .NET frameworks, which you are free to ignore. The first half of the book is a fantastic explanation of both the problem that DI solves, and how DI solves it.

I felt like I didn't really understand OOP and the real point of object-oriented design patterns until I read it.

Right now, Reactive Streams and RxJS in particular, especially around errors.
Pointers
Every piece of data lives somewhere in memory. A pointer is a piece of data containing the address of another piece of data.

int a = 42; // puts the number 42 somewhere in memory

int* p = &a; // gives you the address where that 42 is stored

A function with a signature like

int f(int n);

takes an int as an argument. When you pass it the a above, it is copied in the stack before the function is called. If you change n inside the function, that only affects the value of n inside the function. But let's say you wanted to change what was passed in to it. In that case you would change f to

int f(int* p);

Now when you pass the a declared above (using &a) you actually have a pointer to a in f i.e. the address where a is stored. If you do

*p = 51;

inside f, you changed the value of a and that change is visible even after f returns.

Hope that helps.

A pointer is a value. That value is a memory location. That memory location should contain a value too. The value the pointer points to.

Pointers start to make more sense when you take the comment above about pass by value vs. pass by reference into consideration.

There's a great gif about this where a cup is used as the argument to a fillCup() function.

https://blog.penjee.com/wp-content/uploads/2015/02/pass-by-r...

In pass by value, a second cup is created just for that function and then filled with liquid. The original cup is left untouched. With pass by reference, the original cup is being filled, not a duplicate of it. How does the function know to fill the original instead of creating a duplicate for its own scope? A pointer.

When calling a function with arguments a few things are happening behind the scenes. Memory allocation for these arguments is one of those things. This is where the stack and heap come into play. Memory allocated for the function call is considered the stack while memory for the entire program (think global variables) is the heap. Things get even more interesting when you look into stack frames and step through the execution of a program with a debugger like gdb or lldb. You'll start to understand how recursion works.

Pointers are also used in certain data structures like a linked list. They allow you to easily keep a chain of values that contain data and a pointer which points to the next data, otherwise known as a linked list, or binary tree, or many other names depending on the structure.

I'm replying on my phone so I apologize in advanced for the formatting. Hope this helps.

Testing. I'm a very experienced programmer, and I have written plenty of unit tests -- but I've never worked in a place with a good testing culture and I've always struggled to develop a high level mentality of how and why I should be testing my software. When should I write a test for something and what is the best way to go about it? Does my code have to be more 'test friendly'? TDD seems very appealing to me as a way to outline my software before I 'dive in' but its hard for me to rationalize spending the extra time on building out the testing framework.
I struggle with this as well. I wouldn't say I'm very experienced, but I've been around the block for awhile. I use TDD successfully, but I always have problems with edge cases. I think of and try to guard against edge cases, but I feel like testing in practice is really just preventing regressions for test cases. Most the bugs I actually see in my software are of things I didn't think about (obviously).

Sometimes I wonder if I should only write a test when something unexpected happens (e.g. user has a bug report) instead of wasting time writing tests on the front end for core features and obvious edge cases.

I mostly work on scientific software, btw.

I don't follow TDD strictly, but I still think testing is important. I mostly see tests as help for future programmers (which includes the original authors), kind of like documentation. It's a rigid description of how the system should operate and therefore is usually helpful for refactoring or extending old code. I also don't doubt that many people go years without seeing utility in writing lots of tests. From my experience there's a lot of factors that go into how many tests you write before getting diminishing returns. Some off the top of my head: static v dynamically compiled lang, functional v procedural lang, age of project, etc.
My first job involved writing developing test suites for a platform (with few software layers). Each layer again had many levels of tests - unit(api), component(state machines), integration(2 layers), alpha, finally-a-swiss-knife test script. Doing at that time - I disliked my job (may be coz we did scripting in an obscure language). It was ridiculous the amount of effort we spent to develop whole suite. Level of tests was so stringent may be a healthcare product or aircraft software could only be more detailed or comprehensive.

As this complex product evolved at rapid pace - one of the key differentiating factors of this product and others were time to market and product reliability - benefit reaped was many folds from test suite. Many LOB directors realised that efficiency of our platform had to do with test suites. If I've to run a successful company - I would heavily rely on such a test suite.

Having said that - its hard to write detailed TDD in early stage startups. I didn't write TDD at my last startup work but I had a swiss-army-script to test. In this case, reliability of software is on a developer who developed it. But once a component is proven and it goes as core software of your startup - it should be baked with test suite.

Same here. I'm working on a software project (java) for years and I wrote zero testing code. The problem is that my java code is straightforward and pretty much a thin layer between complex slq-magic and external API's. Yes, I could test this 20% of the application written in java, but it would take a lot of time and I don't expect much gain from it. On the other hand it is hard to test the behaviour of an external API. Most of the business logic resides in sql-statements. Hence it would be much more valueable to test them. But the only idea I had how to test sql statements is to automatically recreate the whole database (which is big) on a test-server for each test-case. Is there a framework for this?
Basic OOP. What do I make a class?

Functional programming in general clicks with me, however.

The one sentence answer from a FP perspective, imo, is that if you are passing the same data (e.g. a record) between functions, doing stuff with it, and returning a new record then that should be a class that hangs on to that data with the methods being the functions that act on it.

This is especially true if your functions if you have a bunch of functions that do this and they aren't really useful for anything else.

> if you are passing the same data (e.g. a record) between functions, doing stuff with it, and returning a new record then that should be a class that hangs on to that data with the methods being the functions that act on it.

But what if I don't want to mutate the source data? I always wind up writing classes that are largely immutable, and it seems wrong from an OOP perspective. Is there a correct way to marry immutable data with OOP?

Values that get passed around together go together into a structure, making code that deals with them more succinct. The functions that care about that sort of structure go into a class, not because this is inherently better, but because following this convention solves the "where did I put that function?" problem for a wider variety of cases than most other conventions.

Values that are supposed to change at the same time (or care about each other, or would otherwise need to be in sync somehow) would normally have to be passed around together to maintain that property, but this can lead to errors because it's the sort of thing people forget. If you put them into a structure together then hide the ability to actually use them behind functions that maintain the property you want, you prevent that sort of error.

> The functions that care about that sort of structure go into a class, not because this is inherently better but because following this convention solves the "where did I put that function?"

So you attach functions that operate on a data structure to the data structure solely for organizational purposes?

Seems almost identical to defining a record and module of functions that operate on said record. Except that in OO, by attaching the functions you create new copies of each function with a bound reference to the instance of data structure. Are there other advantages?

> Seems almost identical to defining a record and module of functions that operate on said record.

OO is mostly that plus the ability to forbid people from using the record without using the functions.

> by attaching the functions you create new copies of each function with a bound reference to the instance of data structure.

Yes, but the compiler won't actually copy the function, it'll implement it as something closer to the record plus modules approach (for example a record plus a pointer to the module). This might not be true if you're trying to use OO in a functional language.

Thanks for taking the time to give these detailed responses.

One more question (restated from below):

Coming from FP where immutability is sacrosanct, I often wind up writing classes that are largely immutable, which doesn't seem like correct OOP. Is there a proper way to marry immutable data structures with OO?

The object-oriented world has been moving towards more immutable and mostly-immutable classes. This is sometimes awkward with large classes, so people use the builder pattern to get around it. (It's a case of languages having not caught up with practices.)
I guess this isn't "simple" but I'm in a place now where I'm responsible for "all-the-things" and I'm having a difficult time understanding what makes a good architecture for a web app.
Lisp continuations! From the name I swear they're just backing up the current scoped state and returning to it, but I've yet to find an example that clearly shows what's going on in what order. To be fair I haven't actually used lisp, just read through the beginnings of the scheme spec.
Start with escape continuations (using call/cc to make an early escape) and work onwards from there.

It didn't click for me until the third tutorial, and there are lots of resources out there

I just figured this out today but the pass by reference and pass by value thing in Python. I understood that Python variables in the same scope can refer to the same memory object, just the name is different. However when you pass the into classes and fuctions, mutable types are pass by reference and imutable types are pass by value. I just came to this realization after spending hours looking at gui code trying to figure out why some variables form a parent object we're not changing when passed into another class and modified.
Everything is passed by reference in Python. But there are no methods to modify values of immutable objects like strings or numbers.

(Variable assignment, as in foo = bar, always just assigns a reference, it doesn't mutate objects or copy values.)

call/cc. I understand what it is and what it does, but I don't understand how it's actually used in a practical way (all I've ever seen are synthetic/too abstract examples).
Check out srfi-125 and the make-coroutine-generator: https://srfi.schemers.org/srfi-121/srfi-121.html

That is implemented using call/cc, and even though it is mostly a convenience function, it shows how call/cc can be used.

It should be noted though, that call/cc is extremely expensive and prone to memory leaks. If you are using racket , scheme48 or guile you should use their delimited continuations instead.

I have a few (mostly silly ones):

• Async/await in C# (I have a pretty good mental model and use it quite often but honestly have never truly understood how it compares to c/C++ threads)

• async concepts in Java Android. Runnable, future, asynctask, threads arrghhhh. My head hurts.

• multitable SQL joins (inner vs outer). Someone gave a pretty good explanation in the comments.

• why do I need to use nginx for my web server? And what is a reverse proxy anyways?

• how come I can use any smpt client or something like mailchimp or sendgrid to send an email and specify any email in the "from" field. Can't I pose as someone I'm not?

I can answer your question on nginx. There are multiple reasons why you would want to use it as the entry point to what you want to serve. As a standalone web sever if offers a plethora of configuration settings that allow you to specify how you want to handle the incoming requests.

TLS is one aspect that comes to mind. A certificate and key is not enough. You must specify the correct parameters such as the cipher suites, DH parameters, HSTS, and more if you want a secure connection.

You mentioned a reverse proxy. This is the other aspect that comes to mind. Servers are bound to ports, like 80 and 443. How do you serve multiple applications on the same port on the same server? A proxy. More specifically a reverse proxy. It sits between the client and the services as a middleman to handle and direct requests. Multiple services can run on different ports but the reverse proxy allows for a single point of entry, such as HTTP requests on port 80. The proxy will inspect the request and based on certain values, say a hostname, it will be directed to the correct service. Otherwise you would have to tell your clients to specify the port the service is running on, ex: http://mydomain.com:8080

This is incredibly useful for performance. If the user is requesting an image or other static asset, your reverse proxy can detect this and skip making a request to the actual application. Likewise if you want to go to some sort of cache based on the type of request.
More 'ops' than 'programming', but: Email.

Email is a real 'devil in the details' thing. Anyone can follow a web how-to (when they're not stale) and set up a basic server, but it's crazy complex to understand properly. It's also weirdly both old-as-rocks and at the same time constantly changing (eg: deliverability scores, major vendors changing how they accept things, etc). Aliasing and mail flow and MUAs vs MTAs and mailbox formats and multiple arcane config files and blacklisting and DKIM and MX records (and TXT records) and etc etc etc... and if you're really crazy, GPG-signing. I've always got enough to just scrape by, but never actually comfortably grokked it all at any point. So many disparate spinning plates...

How to overcome imposter syndrome.
I'll risk looking like an idiot: Figuring out the architecture and different combinations of languages in modern web dev, and what a good solution choice is for a given need/problem.

From the basic options to set up a server, code deployment and maintenance using Github, nodejs and similar JS-everything, XML/JSON, how much of what should CSS take care of, DB access (and which one), structure of a modern webpage, CDNs, structure scaling, and requirements for browsers and platforms..

I used to use JS to change some text in html pages ftp'd to a server running a basic apache. Looking at it now is a big slap in the face. Nodeschool helps a bit, but it's all pretty blurred still. The more I dig, the deeper it gets without a sense of direction.

Every time I try web development I feel like I have lost every sense of control. It feels like abstractions upon abstractions that are governed by conventions hidden from me, and every time I step outside them things break in weird undocumented ways. And whenever I try any web frontend stuff it feels like I have taken everything I hate with UI development, squared.

I have however embraced the magic of "no idea how things work under the hood" and now build things for our intranet in seaside.

I am however a bitter old fart that envy people that can throw beautiful web pages together in 25 minutes. Most of my work these days are management and scheme coding,so that is probably.the way it is going to stay.

Clean code.

What makes some code better than other code? The understanding of this is probably a lifelong pursuit.

C++ pointer/reference syntax.

I understand the concepts, and pointers make perfect sense to me when I'm programming in assembly, but whenever I look at them in C++ it's like & and * mean the opposite things when used in variable declarations from what they mean when used as an operator and yet another thing when used to declare arguments. But I've never seen anyone else acknowledge that they're weird so I feel like there's some sort of logical connection I'm missing.

Initial setup to get started with programming and run a hello world kind of thing.

With things we do outside of an IDE.

Language specific functions and behaviour.

Why C++ doesn't have modules yet ;)