Hacker News new | past | comments | ask | show | jobs | submit login
Write Junior Code (parsonsmatt.org)
173 points by bibyte on Dec 29, 2019 | hide | past | favorite | 141 comments



This might be a little bit of a “hot-take”, but if idiomatic code written by a competent senior engineer remains inscrutable and unmodifiable after around 2 weeks of training an engineer who is at the beginning of their professional career, then that language is probably a bad choice to use in production. If lenses, MTL, fancy concurrency are “the way” to do things in Haskell, and those require an extraordinary amount of time to learn, then Haskell may not be a good choice for production code from a sociological POV.

I haven’t used Haskell professionally so I can’t actually weigh in on the veracity of the prominence of difficult-to-learn code. I do know about using another “weird” language though: Common Lisp. We at Rigetti took a bet with Common Lisp and it passes the “senior-written idiomatic code can be junior-learned and modified” smoke test with flying colors. Every group of interns and every new grad that has become involved in the tens of thousands of lines of code has been able to contribute substantially in less than two weeks.


As someone who works with a ton of Common Lisp and a bit of Haskell, sure Common Lisp may seem easier to read at a surface level but my god am I a lot more hesitant to make any changes to our code base. Unintended side effects are everywhere.

It takes a lot of discipline to write good Common Lisp code.

With Haskell, some of the code may be a bit harder to read at first, but I always have the type signatures to help me. Quite often I dont even need to read the code because the types give me enough of the story.

I am a lot more comfortable to modify code knowing that it just isnt going to break something I wasnt aware of. Far less discipline is required to write good Haskell code. As long as the project is set up so that you don't write everything in IO, you are pretty much good to go.

I would be a lot more confident throwing a junior at a Haskell code base than a Common Lisp one knowing that they would be far less likely to break things.

It does take a bit longer to learn Haskell, but in my opinion, once you get over the hump it is oh so worth it!


I think good mentorship, good code review, and good tests are critical to anything, including Common Lisp. We are very strict about that and we’ve had excellent results. Non-sense and language game-playing is quickly and earnestly called out, and everybody lives a better developer life.

I’ve been on Lisp teams where that wasn’t the case and it was a free-for-all with macros and other things. That was a disaster and the code base was a gigantic Rube Goldberg contraption. But bad hygiene is possible in any environment, though Lisp has the capacity to amplify it.


Part of the trouble with Haskell is the prevalence of syntactic sugar. It is incredibly easy to make code that is tiny and indecipherable because it relies upon functions which are compositions of higher-ordered functions with monads and obscure operators that no other language has. And on top of that Haskell is lazily evaluated, which complicates it further.

And don’t even start with point-free style, a style where parameters are left implicit. For a reader point-free is appalling and way too clever.

I would say junior Haskell should be unsophisticated. Leave out the higher order abstractions and write longer code which a reader at a lower level has a hope of deciphering.


> For a reader point-free is appalling and way too clever.

I used to believe this, but as I started to read more and more Haskell, I realized that I just had to get used to that style of writing Haskell. I now read it fine, in general. Learning to read point-free is a learnable skill.

Tons of non standard sigils, on the other hand, is a bane.


> I realized that I just had to get used to that style of writing Haskell. I now read it fine, in general.

The question of course is how true is this for the general engineering community?

Reminds me in some ways of survivor bias: those that stick successfully with Haskell tend to see the shortcomings perhaps as virtues.


I think it's true in general. Point free style is just different from other programming styles it's not particularly unreadable.

Haskell definitely has issues, but I agree with the parent on his opinion on point free style.


I don’t think Haskell will ever be a language used by the general software development community.

For the record, my primary languages now is OCaml (and to a much lesser extent, functional Scala). But I like reading Haskell code. There are a lot of good ideas there (and no, my Scala/OCaml isn’t loaded down with custom sigils).


There's definitely a vein of deliberate unreadability. Types and parameters are often labelled positionally instead of given meaningful names. For example the graph library fgl uses typenames 'a' and 'b' instead of useful names like 'NodeLabel' and 'EdgeLabel'.


OK, those are poor stylistic practices, but they’re orthogonal to point-free style.


It is related. In point-free style the "omitted" parameters have to be distinguished positionally; they have no names at all, not even 'a' or 'b'.


> In point-free style the "omitted" parameters have to be distinguished positionally; they have no names at all, not even 'a' or 'b

Yes, that's literally the definition of point-free style.

I was referring to labeling function parameters `a` and `b`.

Also, when I checked, that's actually not what the FGL library does. It labels type parameters with `a` and `b`. Labeling generics and type parameters with letters of the alphabet is pretty typical for a wide range of languages (ie, `T` in Java, etc.). Arguably, it's a bad idea, but it's by no means unique to Haskell.


^^^^^ This is extremely insightful, and now ranks as the most accurate and succinct explanation I've seen of why point-free is terrible.


It's not extremely insightful. It's literally the definition of point-free style.

Also, Haskell doesn't have labeled/keyword function parameters, regardless of whether or not you use point-free style. Sometimes, people pass in a record to achieve a similar effect, but in general, passing in a ton of parameters goes against idiomatic Haskell style.


Or, just don't hire junior developers :).

This reminds me of what my boss from my second-ever job told me when he tasked me with starting a new project: "I know I promised you you'll get to choose your own tech stack, but you're going to write it in PHP; I know you'd like something better, perhaps Ruby, but when it comes to hiring developers to help you, I'll have to pay a Ruby developer $X, and I can get PHP developers for $X/2".

I guess it makes business sense for bottom-feeder development. In my case, it burned me out completely.

Anyway, I work in Common Lisp professionally these days too, and we don't shy away from using both simple and moderately-complex macros. There's nothing magic in them. Sure, tracing a problem with the expansion of someone else's macros can be a cognitively taxing work, but you're going to have to do this kind of work somewhere anyway - if not here, then when trying to figure out a convoluted call graph of 20 functions or methods that macro would have abstracted away for you (which was a common thing to do at my previous job, involving Enterprise Java).


Not hiring junior developers makes sense when you are still unfolding, and have a really small engineering team (< 5-7 people).

As you grow, you will see that not all work is equally important, and you want your more senior guys work on harder problems. And you will also note a number of good, smart developers applying to you who do not yet have the experience of a senior engineer, but could quickly grow.

Then you can start hiring junior engineers, and nurture them.

[Edit: grammar]


I'm currently working for a company whose first 4 technical hires (other than the CTO, who wasn't always involved in day-to-day development) were juniors. This was a terrible idea, as there are basic architectural mistakes baked into the codebase all over the place, and lots of issues with data integrity and things that only work most of the time. They'd have been better off with a single senior.

Agree that it makes sense to hire juniors once you've grown though.


If no one hires junior developers then how will we make new developers?

If no one writers code that junior developers can understand, then how can we hire junior developers?


> If no one hires junior developers then how will we make new developers?

Independent of the serious answer given by BurningFrog, it's also useful to understand that what's best for the world at large is not necessarily best for your company.


The suggestion was to not hire junior for certain projects, where that makes sense.

This does not at all imply that no one should ever hire them. So get up from that slippery slope and start walking again!


> Or, just don't hire junior developers :).

1. You miss a lot of great talent this way.

2. Growing and developing juniors is part of what makes a "senior."

3. This isn't sustainable advice -- if noone is hiring juniors, then where are the new seniors coming from?


> 3. This isn't sustainable advice -- if noone is hiring juniors, then where are the new seniors coming from?

Well, as much as it is short sighted, the really is that letting some other company spend money training juniors is money you don’t have to spend.


If this is truly a profitable choice why are you assuming anyone will do it?


Because there are companies doing boring work that have huge developer infrastructure in place to help cheaper juniors do the mundane stuff (i.e. all of the big tech companies).


"Junior" might as well be an IQ classification.. How many people change their IQ?

The reality is something nobody really talks about.. When a company doesn't want to hire a "junior" it doesn't mean they don't want to hire somebody without X amount of experience in a tech stack or without X amount of experience in the industry.. They don't want to hire people bellow a certain aptitude.

Many "juniors" will never be "senior". Even if they get the title and salary. We all know "seniors" in salary and title only.

Some people are inexperienced "seniors" year one.


Viewing it from this lens, it still doesn't make sense to sacrifice productivity of people "with aptitude" for the sake of expanding your hiring pool to include people "without aptitude". Instead of bringing down everything to the lowest common denominator, one can try to assign different types of work to people with different levels of aptitude (or care).

(Personally, I feel what makes "forever juniors" isn't aptitude, but lack of care. I don't see it as flaw of character - the factors like structural anti-intellectualism in the industry, lack of enthusiasm due to work being meaningless, and that not everyone is into programming beyond it being a 9-to-5 job all contribute to some people not learning. This suggests to me that expecting people to learn and making learning part of their jobs - actual part, with paid time set aside for it - can overcome this problem.)


> anti-intellectualism in the industry

Could you expand upon this point?


A bit more here: https://news.ycombinator.com/item?id=21909816.

But the gist of it is: the industry prefers to work with dumbest tools possible and throw cheapest bodies they can get at a problem, instead of expecting people to keep learning, improving themselves, and applying these improvements to their work. This article is an example of that - it advocates discarding advanced tools given to the programmer by Haskell, in favor of writing dumb code that's easily digestible for beginners. Most of the advice about avoiding "clever code" is a form of that too.

I find it a penny-wise, pound-foolish approach, because when you do this trade-off, you're losing in two places. First of all, you handicap your skilled team members. But secondly, the cost (in terms of time, money and incidental complexity) of a project grows faster than linear with the number of people you get involved in it. It'll be O(n!) if you do it naively, O(n logn) if you arrange people into hierarchies, but the problems still grow faster than they're solved as you scale up.

The alternative approach I'm encouraging is to make responsible but full use of the power of the tools you have, and if this goes over the head of some new hire, then teach them until they understand and can make use of that power too. Embrace and encourage learning as a part of this job (actual, expected, and paid-for part). Push the industry upwards, instead of dragging it down.


> How many people change their IQ?

Literally all of them. However, if someone never progresses to "senior" they might've just lost interest in software development. People in general are remarkable at learning about subjects arousing their curiosity. This is an important fact, too, because environments can be designed specifically to foster just that. Not everything will be working for everybody, of course, and some might never develop an interest in that field again. But that's okay, too.


This isn't forbidden knowledge, it's just not correct. People learn and grow with experience. Some projects can't afford to support many, or any, people in that growth stage. But lots of projects can afford it, and the investment in their growth makes sense. I think it is probably also true that some people never become competent enough for many projects, but in my experience that is diminishingly rare, whereas junior developers who are still developing their skills are extremely common (unsurprisingly, since more and more people enter the field constantly).


Funny, I've done my best work in PHP. But, I have a semester of Scheme under my belt and have been doing object-oriented programming (or more precisely, moving away from it) for decades.

I really, really wish there was a rapid application development language like PHP or Go written over a functional language like LISP.

I've thought about writing one, and get all the way through it in my head until I come up to the inevitable problem with monads. I believe I can make it work by treating the monads as imaginary numbers with hidden contents and never really solve the problem of mutability, but I don't know what that would look like in practice yet. The language itself would basically be Javascript without mutable variables. There would be no async/await, instead, everything would be synchronous blocking with message-oriented streams in place of messages. Like the Actor model but executing immediately as a graph so it's fully deterministic.

Facebook kind of made one called Skip, although I feel like they might have been working on another one with a "z" in its name, or maybe someone else was, can't remember:

http://skiplang.com


> lenses, MTL, fancy concurrency are “the way” to do things in Haskell, and those require an extraordinary amount of time to learn, then Haskell may not be a good choice for production code from a sociological POV.

I don’t think this is the right take. Haskell is not Python—there isn’t a single way to do things such that you can say there’s one way, and it does or doesn’t work.

Haskell gives you a lot of flexibility to write code how you would like it. In my experience starting a company in Haskell, there is a subset of it which allows you to be very productive, and also get very high guarantees about the correctness of your code. I strongly recommend it.


> In my experience starting a company in Haskell

Since you have first hand knowledge of the problems outlined in the article, can you comment on how easy it is to hire non-senior Haskell engineers? And how quickly do they come up to speed in a senior devs codebase?


Sort of. So far most of our hires have Haskell experience. Haskell was the main reason programmers would want to join us, so they would be naturally experienced in Haskell.

I’ll have a better idea of how non-Haskellers, including very junior engineers, pick up Haskell later this year.

Based on our limited sample size so far, I think people become productive within 4–6 weeks or so.


Now, I'm not particularly a fan of Haskell, but I think it is disingenuous to compare it apple to apple. Most junior devs had multiple years of training in imperative OOP languages prior to joining a company. So if they join a place that uses C#, and they don't know C#, but they already know how every single feature and concept of C# works, well ya, they'll be proficient in two weeks.

Similarly, had they'd been thought functional programming, some category theory, and a bit more about type theory, etc., then they'd probably be able to ramp up to Haskell in two weeks as well.

On one hand, they come knowing all the concepts and only having to learn a syntax and tool-chain. On the other, they know none of the concepts, and have to learn them all as well as learning the tooling and syntax.


GP's comment talks about how Lisp passes the two week test. Not really apples to apples with Haskell, to be sure, but it still is significantly different from C#.


I agree that if our educational institutions changed with respect to what’s taught around programming fundamentals, advanced Haskell would be easier to digest more quickly for someone just coming out of school. My point is sort of that that’s not the case, which potentially makes Haskell a tougher sell for the graduates of today.


I think a problem might be that the jr. devs don't even get the chance to look at the code, because they're not getting hired based on their lack of experience (ref. the laundry list of advanced requirements).

It reminds me of the Manager - HR pipeline, where HR people get a description of technical stuff to input in their job listings, and then proceed to gatekeep applicants away from the positions, because they don't match x % of their technical laundry list.


Do you use On Lisp (hi, 'pg!) type stuff, with heavy macros? I don't believe a new hire can learn an On Lisp-esque custom embedded language in 2 weeks.


No. Good idiomatic Lisp code might use some macros for domain-specific applications, and if those aren’t increasing readability, then they have no place in production code. A few prominent macros we do use:

- A macro to define a string lexer (aka tokenizer) for a programming language

- A macro to define peephole optimizations of linear instruction code

- A macro to define friendly looking bindings to Fortran functions

- A macro to generate highly optimized complex double precision matrix arithmetic code for inner loops

We do use “standard” macros, mostly from the ALEXANDRIA library. But these are idiomatic and have been used since at least the 80s. (Some of these are macros that On Lisp defines from scratch.)


That sounds mostly like you'd want to be using something like functions, but Lisp ain't a strong enough language, so you have to use macros?

(Just for comparison, in Haskell you would probably do most of these without macros. They do have (something like) macros in Haskell, though. They are not quite as natural as in Lisp, mostly because of differences in syntax.)


It’s true that in a lazy functional language you can get by with fewer syntactic extensions for common programming patterns, but it doesn’t, at the end of the day, actually solve the problem of syntactic extension or code generation.


It's not just laziness, though that helps. It's also bunch of other idioms and features. (I've programmed in a strict variant of Haskell for a while.)

Ideally, a language like Haskell would actually be total by default; ie all computations are finite. For those computations, the compiler can freely choose evaluation strategies without affecting semantics.

For potentially infinite calculations, you'd go into a mode that's somewhat similar to how IO is segregated in existing Haskell.

(There's some more complications about things like infinite event-loops, that can be solved very similarly; and for some things you might want the user to provide proofs that they are indeed finite, instead of just overriding the compiler's judgement with an unsafe cast.)


Fixing your Lisp: (let ((pg! "Paul Graham!")) `(hi ,pg!))


Or, really `(hi ,pg!) but please capture that pg!


> then Haskell may not be a good choice

Or a Haskell-illiterate hire, or two weeks' max acceptable 'ramping up time', may not be a good choice.


I think one should write the code that's most suitable for a specific scenario. Senior vs junior has nothing to do with this (at least not in a categorical way). I have met many who call themselves seniors who really just aren't as good as they think they are.

The best engineer I've worked with sometimes wrote simpler code and sometimes wrote highly sophisticated code. If there's someone on the team who can't understand a particular piece of code, he should just ask and learn. There's no reason to weigh someone down just to make an inexperienced, incompetent programmer or unwilling to learn something happy.

Good code isn't junior code. Good code isn't senior code. Good code is just what works best in a specific scenario. And it takes experience and details of a problem to decide what that is exactly. Dogmatic, rigid views just don't work well in this line of work.


Agreed, I'm not a huge fan of junior/senior. What we should be talking about is inexpert/expert, which doesn't always map 1:1 with how long you've been working, or practicing. Though obviously, the longer, the more likely you learned and became more knowledgeable. But some people spend 10 years doing the same thing over and over, and that's not broadening and deepening their skills the slightest.

So in my opinion, there is two type of unreadable code, there is the code that's just plain bad, convoluted, overly complicated, like a Rube Goldberg machine. That one is often written by inexpert programmers, sometimes they are juniors, and sometimes they're senior as well. And then there is code that you personally can't read, but which is great code, simple, coherent, to the point, etc. You just haven't learned the vocabulary and grammar to understand it.


> If there's someone on the team who can't understand a particular piece of code, he should just ask and learn. There's no reason to weigh someone down just to make an inexperienced, incompetent programmer or unwilling to learn something happy.

Writing simple code isn't about allowing juniors to be "lazy". Simple code is a business decision. The easier it is to contribute to a codebase:

* the more of your existing staff are able to contribute

* the easier it is to hire people able to contribute

* the cheaper your staffing costs

* the quicker it is for new staff to contribute

* the more resistant you are to staff turnover

* the more resistant you are to changes in the relative popularity of programming languages and libraries

* the cheaper it is to pay off technical debt (because rewrites are easier)

Lowest-common denominator code should be the rule, not the exception. There is definitely a place for "fancy" code, but it should be very carefully considered.


I think the point of this article escapes anyone who doesn't know Haskell or a language with a similar type system. I don't want this to sound elitist, because it's not my goal, so I will clarify.

In most languages, the abstraction ladder is short. You have built-in types and user defined types. Those user-defined types are almost always product types (think of structs in Go, or classes in OOP languages). Haskell also has sum types, which allow you to express things like:

  data TrafficLight = Green | Yellow | Red
Other languages have interfaces and Haskell has type classes which work in the same way.

Then come advanced type-level features: data kinds, type families, generalised algebraic data types and many others. Here's the table of contents of a book that deals with type-level programming in Haskell: https://thinkingwithtypes.com/

These concepts allow you to encode more logic at the type level. But they come with the cost of needing to understand them.

You can make a choice to write Haskell without these advanced type features. Maybe you lose some type safety or maybe you repeat yourself more. But the upside is that you use fewer concepts that need to be understand by someone who needs to work on the codebase.

These are the juniors the author is referring to. I'm a junior as well in this regard, because I don't know all these type-level features, even though I've used Haskell professionally.


To be clear, one of the reasons I decided to focus on Python in the early 2000's was this reason, that I wanted to work within a computing language that was as accessible as possible to the widest variety of programmers, thus helping to ensure that the code I write would be maintainable by others, the libraries I create would be in high demand, and whatever code I write for employers would be maintain a high degree of transparency to everyone else at that employer.

My general impression of functional languages with Haskell at the top, is that these languages are inherently not for "junior" programmers of any stripe. That is, if you are a programmer writing Haskell code that actually works and does something useful, by definition you are nothing like a "junior" programmer. A "junior" programmer would be doing it in PHP, C#, or Python. Just the kind of motivation one would need to learn Haskell and actually be functional at it would take you out of the realm of the vast majority of "junior" programmers.

This is based on my own experience of working alongside many other programmers, many of whom were probably not necessarily "junior" however they were the kind of programmers that went home at 5 pm. These people could get a lot done but only in a language that did not require intensive conceptual visualization. Even in Python, parts of code that I would write that would get too clever / recursive / FP-ish would be lost on everyone else and I'd be stuck having to save everyone from that little clump every time something had to change with it.


Python has a sort of opposite problem. It’s so easy to write code that it can be difficult for engineers to know whether the code they’re writing is good or bad. And while bog-standard imperative code is easy to read, it gets hairy when people are slinging dicts and stashing things away in object attributes at runtime. It’s easy to say “well, no true Scotsman would write code like that!” except it appears in all corners of the Python world, professional, open source, and otherwise.

One example I like to cite is: What I’d you want to tee a stream in Python to two different substreams? Stack Overflow it and you see cargo culted duck-typed classes that don’t derive from any clear specification.


That's not a problem really. Good code is easy to read and mantainable.

Regarding the tee operator: it's a solved problem, you have ot import it from itertools[0], which should be where any experienced python programmer would strt.

[0] https://docs.python.org/2/library/itertools.html#itertools.t...


I mean something like an IO stream. Something like

    s = tee(stdout, myOutFile)


I don't know Haskell but I have worked in Scala. It's an adjustment to express yourself in functional idioms, but the payoff is that it's easier to reason about, test, and debug (not to mention parallelize) side effect-free pure functions. I often reach into this bag of tools even when working in other languages.


> That is, if you are a programmer writing Haskell code that actually works and does something useful, by definition you are nothing like a "junior" programmer.

So what do you call someone writing useful code in Haskell for their functional programming course during their second year of CS? (That is my little sister right now)


Well I was in CS about 30 years ago at this point so I am talking out of my old crusty butt but I recall that while we did fancy FP things in lisp and APL or whatever, the software we wrote was still on a very small scale compared to what you work on once you get into a job and inherit a huge codebase. I think the term "does something useful" is the point of contention on this. We wrote things like, take a document and word wrap all the paragraphs. That is useful. But that's not really a real-world enterprise level assignment.

Again, that's the crusty old perpsective informing me on this, your sister could be building out a self-driving car platform for all I know in Haskell, good on them !


One would expect that a language like Haskell abstracts away the need for junior programmers. In the same way that digital computers abstract away the need for pencil-and-paper calculators.


I've never seen a scenario like this and even with the ideal case of current abstraction frameworks there is always tedious grunt work that juniors get to cut their teeth on - doing minor adjustments to fine-tune requirements, adding minor functionality, etc. Stuff where you don't want to waste time and focus of senior devs.

The only scenarios I saw where juniors were not needed is overly complex systems where the barrier to entry was so high you needed a huge amount of upfront knowledge to do anything. Those were not good projects to work on.


Close, but more it abstracts away the need for non-PhD programmers (or non-"could have gotten a PhD if they wanted to" programmers), and once you get a research professor on board, it abstracts away the need for mere PhD programmers too.


I have the opposite view. Sure, don't go overboard with power for your own sake. If it ends up a convoluted mess, it's your fault and you should refactor - proper application of powerful language concepts result in clean interfaces hiding complexity that rarely, if ever, needs to be touched again.

From my personal experience, instead of writing dumbest possible code to make juniors productive from day one, just don't hire juniors. Or at least, not the absolute beginners. And if you do hire juniors, accept that they'll need more than few weeks to get up to speed.

Despite the perceived smartness, our industry has a weird anti-intellectualism deeply ingrained in it. Programming is a profession - you're supposed to get better over time. Learning is part of the job. And yet it seems to me that more and more people think that what they've learned prior to their first job is all they'll ever need to understand, and anything beyond it is "clever code" that needs to be expunged.

(Of course, keeping everything dumbed down makes sense if you're interested in penny-wise, pound-foolish optimization on the hiring side of the company, or otherwise like to have developers be replaceable cogs. But it's not in the best interest of the developer, and arguably it isn't in the product end-user's best interest either.)


One of the most enjoyable aspects of my career has been teaching and mentoring engineers with little experience. Everybody starts somewhere, and I would never want to suggest an organization I’m a part of ought to bar a healthy, tempered number of less experienced folk.

But, organizations aren’t charities, right? I might like mentoring, but that doesn’t mean it’s the best choice for a company to make. Fortunately, there are a plethora of incredibly bright and talented individuals who really can move the needle in your business in a short amount of time. You wouldn’t see it from number-of-years worked, but you’ll find out by investing your time and energy in them. With this attitude and managerial philosophy in mind, I find that you inevitably build stronger, more loyal, more flexible, and exceptionally competent teams this way.


Well, sure. My main point isn't that you shouldn't train hires (you can't really expect to not train new hires). It's that I don't think it's worth it to sacrifice your team's internal productivity for the sake of speeding up the onboarding process and/or making it cheaper - which is what IMO the article is essentially suggesting.

The article is about Haskell, but this advice - make code newbie-friendly, avoid "clever" code - keeps popping up again and again. The extent to what's considered "clever" varies between jobs, languages and programming communities.

As an example, when my old Java job was grudgingly upgrading from Java 7 to Java 8, my boss took me to the side, and said to me: "I knew you'll be happy about the transition, but if you could, perhaps consider refraining from using lambda expressions; I know you understand them, but some of your colleagues do not".

I didn't comply; instead, I've explained lambdas to people in my project. It took few minutes, they understood it almost immediately, and we continued using full feature set of Java 8 just fine. The practice eventually spread around, and the other day I saw a particular developer my boss was worried about, going around the office full of excitement, telling everyone about the cool things he's just learned about Java 8 (namely, lambdas and streams API). Soon thereafter, everyone was using these features, and they've lost their status as "clever code".

So, to put my point another way, perhaps instead of worrying about the code being friendly to experienced developers, one should take the time to teach the new employees - just like you are doing.


I fully agree with this. I haven't really seen code that is 1) actually useful but 2) so clever that you can't quickly explain why or how it works (or just let the person reading it figure it out or ask stackoverflow...). So I don't really even know what people mean by clever code.


In my experience, a big danger is when someone higher in the company hierarchy is a former developer who no longer writes code.

Such person will perceive the new language features or libraries as "too clever, and not really necessary", will voice their opinion loudly, and you can't make them learn and find out that it's actually simple and useful. (And, yes, lambdas in Java 8 were an example of that.)


Find good examples that you can defend well, and overwhelm them with practical and relevant examples.


In my opinion, Java wouldn't need to use lambdas as often if it had a more powerful object model. You'd define methods instead of pass lambda's as parameters. Lambda's are often a band-aid for a weak object model.

For example, if I want to define an "on click" GUI event handler for a given button, the more natural approach would be to define an "onClick" method for that button's object. But you can't do that in Java in a simple way the way you could in say SmallTalk. Thus, one typically has to send a lambda to a "listener" object, which is unnatural.


I think the two things you described are essentially identical. Defining a onClick method on an object is equivalent to giving the onClick method a function with implementation in it. +/- whatever shenanigans your object system plays with methods.

Java does indeed have a very weak object model, though it improves slowly over time. But rejecting to use those improvements because it's "clever code" is arguably just dumb.


I agree there is or can be a lot of overlap between OOP and aspects of functional. With some diligent language factoring, I suspect they can be blurred to be one in the same. That way one doesn't have to learn two different sets of rules and syntax. But, I'm not sure Java can fix such cleanly without breaking backward compatibility.


Lambdas shouldn’t be considered clever. What did these people learn in school?


Re: Despite the perceived smartness, our industry has a weird anti-intellectualism deeply ingrained in it. Programming is a profession - you're supposed to get better over time.

Unfortunately, programming is statistically a dead-end job. Ageism kicks in and one has to bail out of programming to get more money, because old programmers are on average not valued in the market-place. I'm just the messenger.

Part of it is the fad-driven nature of our industry that whips around what's "in" technology like the tail of a captured fish. Until Fad Cops clamp down on nonsense and excess, it's a young person's game.

Many companies stick with COBOL not because it's better, but because it's stable, like Latin, because it's a "dead language". Nobody comes along and gums it up with spaghetti "Design Patterns" or 7 layers of microservices because "the big boys are doing it!".

Warren Buffett has said part of his success is due to his ability to say "no" when everyone else is saying "yes". He willingly lets others be the guinea pig.

As far as functional programming, it takes a different mindset than imperative programming, and for many there is a relatively long learning curve until they match or exceed their imperative productivity. If programming is a dead-end career, per above, that curve may not be economical.

I personally find functional harder to debug because it's harder to "x-ray the pipes" of intermediate steps, and have not got over that difficulty yet. I personally find it easier to break up imperative code into smaller intermediate steps or parts, subject to inspection via debuggers or Write() statements. Somebody once said: "Functional makes it easier to express what you want, but imperative makes it easier to see what you actually have." That rang so true for me.


There is something to that, but it applies at the (predicted) end of one's career. But what I'm writing about applies at the beginning - the idea that being forever stuck at a level of skill only a tiny bit higher than what an average developer enters their first job with.

EDIT: RE debugging functional code, I don't find it in any way more difficult than imperative - but with a caveat that my functional programming experience is limited to typical for Lisp, Clojure and Erlang, and not e.g. Haskell. With functional code, you just hook up in some place in the pipeline and start leaking what's coming in and out of there to the outside world (stdout, REPL, debugger). Because it's functional, you can be sure you're not missing any hidden state. You can keep replying the calls manually, perhaps tweaking inputs, to identify the problem. After finding and fixing a bug, it's relatively trivial to turn your debugging work into a regression test.

It seems to me that your complaint is less about functional code and more about point-free style, which is arguably a bit more difficult to hook into, but only because debugging tools aren't really optimized for it.


One has to write code intended for a variety of readers having different skill levels. In practice you have to "dumb it down" to a degree.


To a degree. Not to the lowest possible level. You can and should have some expectations of the people who work on the project, and if new people don't meet those expectations, they should be trained on the job until they do. It's better than forgoing the leverage the more complex tools give you.

I mean, imagine if civil engineers started restricting their designs to work only with hand shovels, on the assumption that excavators are evil because a random construction worker won't know how to operate one.


It's a matter of economics of the labor pool. Companies often want high-skilled developers on the cheap, and then act surprised when they can't actually find any at their offer level (or they have poor people/team skills). Nor do they often want to pay and/or wait for training. Maybe there is a good reason they do that but from our techie perspective can't see. Unless you walk a mile in an owner's shoes, it's hard to say. Either way, they probably won't change this pattern, and thus one should code for a lower common denominator. We can't re-code the world.


> Either way, they probably won't change this pattern, and thus one should code for a lower common denominator.

Why? Unless they get push back from the HR/upper management, it's the tech team that makes this decision, in big part by setting expectations and minimum accepted competence level. It's thus the technical team that can change this pattern, by not making itself to code to the lowest common denominator, by setting standards, by embracing perpetual learning as part of the job.

Setting an appropriately high bar for both ourselves and our future teammates is part of the job too. Our non-technical bosses ain't gonna to do this for us - how could they? They don't have the necessary technical expertise; they depend on us to provide it.


There is the theory that 5 highly skilled developers can do the job of 10 average developers, but in practice this often doesn't pan out. I'm not saying it never works, but it just doesn't seem to be a sure-shot strategy.

Part of the reason is that "soft skills" such as getting along with the team, writing well, understanding likely user behavior and the domain, making smart trade-offs, etc. also matter, and often times the technically "elite" lack these. Software development should be viewed as making tools for humans, not really for machines, and understanding humans is part of this.

Regarding your debugging edit: If one wants to spread the use of functional, perhaps finding a making a good guide and tutorial on debugging it would be helpful. I seem to be applying my imperative debugging thinking to it, and it doesn't fit well. I need to overhaul my debugging mind, not just my code mind.


> And if you do hire juniors, accept that they'll need more than few weeks to get up to speed.

I get the impression that most companies would rather complain about shortage of talent for months instead of hiring someone who'll spend a few weeks learning.


Wish there was something similar to Elm, but designed with backend/networking/generality in mind, like Go. Take the Haskell core language without all the lang extensions, and accompany it with a solid stdlib. I want an FP ecosystem that's not rooted in research. Can have a more simplistic type system, etc. Basically a functional Go. Maybe an effect system, idk.


Have a look at OCaml, and also at its new spinoff, ReasonML. I think it may be what you’re looking for - compile-to-native systems language, great type system with 20 years of development behind it, and functional without being dogmatic and pure about it by easily allowing imperative mutable code as well.


ReasonML is more like 'Ocaml with a different syntax' than an actual spinoff.


I keep telling myself that this is the year I'm going to really learn F#; it has a lot of the nice parts of Haskell, but is a little more on the pragmatic end, and has access to the whole .NET world.

The problem I run into frequently on my abortive attempts is that when things get harder, it's too easy to just drop back and smash something out in C#.


Once you get used to / take for granted the additional compiler features in F#, you will find yourself weaning yourself off C# code more and more. Resistance is futile!

2019 ends my year of F# focus. In 2020, I will take a look at Rust since it is C-but-not-nearly-impossible-to-correct-insecure.

Between Get Programming with F# and Domain Modelling Made Functional, I am a convert for F# and will continue using it into the future. Why?

Quick example: I'm currently hacking together a program for the RPG system used in Star Wars Fantasy Flight Games, so that the game master can input possible player actions during their turns and keep track of player stats, generate NPC encounters etc. and so forth.

Writing the back-end in F# (stored to SQLite) really helps me keep the game rule book (business logic) straight, with compiler-based warnings if I try to, say, stick XP into a function that expects in-game cash.

On the front end, I'm using plain old Windows Forms in C#. The UI is mostly for data crunching and keyboard-first usage (I'm keeping in mind my FoxPro days and gui.cs / TUI / keyboard-first functionality from that twitter thread a few weeks back), and is mostly for gluing UI controls to the back-end itself (maybe this will be a mobile app, website, etc. in the future depending on the GM's needs).

For this program, I wouldn't write any game logic in C#, now that I'm used to F#. C# lets me get away with too many mistakes compared to F#, for the same reasons JavaScript lets me get away with (even more) too many mistakes compared to C#.


The problem I've run into is it feels like the language has been abandoned. It feels close to being perfect enough but I can't shake the feeling that the final push it needs will never happen.


F# is my most productive language - it's hard to be beat strong typing w/type-inference, modeling with DU's, pattern matching, indent syntax (by default, unless you don't want it), and the .Net(core) lib/runtime. It's especially nice now that VSCode/VStud have plug-in's that show the inferred type signatures.

The tooling is pretty good now, but there are still pain points (breakpointing/stepping pipes |>, inlines, and pattern matching expressions), but even w/that said the tooling is probably above avg. now. If you want the best refactoring and debugging experience (Resharper, OzCode, etc.) the C# is still the best there.


I would say it’s more “familiar”, not that it’s more “pragmatic”. Haskell is an entirely pragmatic language and is useful for solving very wide ranges of tasks. It’s just not very familiar to most people, in part because it doesn’t plug into a familiar ecosystem, like CLR/Java languages do.


I used to have the same wish as you but then I just decided to pick up Haskell. Haskell's hard part really is the type system. It's also what ultimately enables you to write very high abstraction level type safe code that does exactly what it's expected to do.

Btw you may want to keep an eye on this: https://wende.github.io/elchemy/

Honestly, I wish this would get more traction, it seems like an awesome intersection between type safety and practicality.


You cannot pitch Haskell to the average industry team. It's not so much about technical merit for me


Elixir is pretty great if you're not super hung up on type systems.


I should have added that I'd also prefer mandatory typing and natively compiled code.

Elixir/Erlang/BEAM has a lot going for it but it's not as general purpose as I'd like


There's at least one other way to read that comment.

Is there an 'Elixir' of Haskell? Maybe someone should write it. What would it look like if you stole the good bits from a framework/library/standard lib in another language and wrote them in Haskell?


There is an attempt at it, yes: https://wiki.haskell.org/Cloud_Haskell

Many languages have some variation of Erlang’s processes and/or distribution available as a library, but the key features like live inspection, code reload, or strong isolation between lightweight processes can’t really be done without language runtime support.


There's also some work pushing Haskell to perform better (more like Elixir) in networking/concurrency applications: http://www.well-typed.com/blog/2019/10/nonmoving-gc-merge/


You are basically describing ocaml


It sounds a lot like you're asking for the "junior haskell" (no higher kinded types, few language extensions, etc) that this article proposes and stack's toolchain. Am I missing something?


Rust gets pretty close to this.


Rust is for GC-free code, and it shows in how it deals with closures, higher-order functions, etc. Very different from Haskell or even something like F#/Ocaml/Kotlin and the like.


Elixir?


I don't know much about Haskell but I'd say in any language you shouldn't write code to show off your erudition. Doing that kind of thing in production code is hardly the mark of a truly senior engineer. If the code is the best way to solve the problem, though, why is it so inscrutable in this language?


This is why I advocate for popular frameworks in commercial projects, even though I don't prefer writing in them myself. Sure, it's easy to find people who know {Language}, but everyone has their own idioms, and they will need to spend time learning yours.

A framework has the benefit of having existing documentation you didn't have to write, an existing community of people solving problems the framework may have, and most importantly a slew of idioms that are literally codified and documented.

Do things "The {Framework} Way" and when you onboard someone familiar in {Framework} you should only have to help them with the domain space knowledge and any novelties of your specific implementation. You still get to use advanced concepts, but hiring and onboarding is significantly simplified.

For personal projects, I never use frameworks, it's not nearly as fun as writing greenfields code. But in a commercial project, long term viability of the project should probably come before fun.


“Why not write Go instead?” was the response from an audience member after a talk at Haskell eXchange 2019 that encouraged simple use of Haskell.

They had a good point — if a primary goal in your team is to make code accessible for juniors and maintainable for new hires without weeks or months of ramp-up, and you need to ban Haskell language features, extensions, and libraries to achieve that, perhaps there are better choices than Haskell?

And if the Haskell community needs a repeated rallying cry to “write simple Haskell”, maybe it's a sign that a new Haskell standard should be created that defines what “simple“ Haskell code means.

Juniors could then feel assured that learning the 2050 Haskell standard [or whatever] would be enough to help them get a job as a Haskell junior. And companies would have a target to move their codebases towards that's consistent across the industry.


I think that it’s important to hire people who are comfortable with the whole language that you are using.

I also think that “junior code” could mean the opposite of what the author means. Lots of smart juniors write code that abuses complex language features in noncanonical ways that confuse all of the people. Lots of senior devs stick to a known (and canonical to them) subset of features that proved themselves in battle for them.


> Lots of smart juniors write code that abuses complex language features in noncanonical ways that confuse all of the people.

Do you really see people do this in Haskell? I'm "junioring" my way through a problem set now (advent of code) and that does not describe my solutions at all. It is definitely not pro-level Haskell. I use direct recursion with an accumulator when I know there's gotta be some recursion scheme that fits the bill, very simple types, very few advanced combinators and symbols, etc.

It definitely could be better, but it suffers from a lack of fanciness rather than an excess. I've peeked at some expert solutions posted online and they are neat and super impressive... but by and large they confuse the shit out of me.

I have a hard time picturing being overly ambitious as a common beginner flaw in this space.


Different folks take different paths. In any language, you will find folks who start out by kinda overshooting before they learn to tone it down.

It’s not that all Juniors are this way. Maybe some get it right or maybe they even act too skidding. But I think of it as a Junior trait to sometimes overshoot on complexity.


>Let us grow Haskell in industry by writing simpler code and making room for the less experienced.

>Let’s not delete all of our fancy code - it serves a purpose! Let’s make it a small part of our codebase, preferably hidden in libraries with nice simple interfaces.

Says something about the Haskell culture that this isn't already standard practice, regardless of the need to train junior programmers.


It says something about the Haskell language -- that it has near infinite power for abstraction and Don't Repeat Yourself-ism. Most Haskell code is fancy because if anything else had a large amount of mass, it could be abstracted to higher-level Haskell. Haskell programs have size approximately "log(size of program in another language) ^ k" because anything repetitive can get squeezed out.


Almost every other language has a problem that it's already so dumbed-down to the point employees get treated as disposable/replaceable. There's a reason obscure languages pay very very well, but if you dumb them down, it's not hard to see what will happen to that advantage...

(I don't work in Haskell. Now I have mixed feelings about it.)


There are more $200K+/yr new-college-grad C++ and Java jobs at FAANG than all $200K+/yr Haskell jobs in the world.


> Employee writes a ton of really fancy Haskell, delivers fantastically and in about 1000 lines of code. Everyone is very impressed. The project grows in scope.

>

> Boss: It’s time to hire another Haskeller. What are the job requirements?

The job requirements should be the same as the ones the original engineer was hired for. Then they can mentor the newbie. After all, they're not an expert if they've done this, they're still able to explain why they pulled in a shit ton of libraries to do their work.

Asking that person what the new requirements are is doomed to fail because they moved the goal posts. Give them a junior and let them learn.


It's like when you're a kid and you think adults have everything figured out.

When you pull on the thread, you find that a lot of successful developers don't really have that clear a picture of what it is they did and how they did it. It gets covered with bluster and bluffs. Impugning the other person for not 'getting it' and getting defensive about the 'documentation' they wrote and how everything you need is in the code.

Fifteen years ago we were still talking about 'cave trolls', the people who work in isolation, and often on their own schedule. Most of those people still exist. They've just had makeovers.


I've been through that stage (grumpy defensive programmer). On the other side is a deep humbleness when as a programmer you realise you know nothing, you never really knew.

You just knew how to research and fix the problem really fast.


I take pride in making mistakes and misunderstanding, because it means I'm ready to accept something else, and I personally find power in exposing my flaws and my vulnerability. I don't have a problem with doing something wrong because I'm happy to own it and then ask questions.

Those managers or startups that expect perfection have got it all wrong. The people who fuck up and recover are more valuable.


Yeah it makes the interview cycle pretty damned awkward though.


If you are supremely confident that you've solved many problems with "no experience" you know you can solve many more.

It helps with bringing the interviewer with you also. It's actually important to stay humble and think out loud. Don't aim to have all the answers. Aim to find the answers "as a team" and give them a positive preview of how working with you is pleasant.

I've aced every interview, perhaps because I only interview for roles I am intellectually curious about and I do a lot of research beforehand "for fun."


That's the thing a lot of devs who hire don't get. You don't necessarily need someone who knows the language inside and out on day one.

What you need is someone smart and who can demonstrate that they can learn new languages quickly.

They understand the fundamentals of how to write software.

Language specific abstractions are just muscle memory.


No one learns Haskell quickly. "the fundamentals of how to write software" are very different in Haskell from other languages, even other functional or functional-ish languages.


Where are the job ads for someone like that?


One should code in Haskell as if it were a small language like Scheme (or Standard ML, or early OCaml, or..). It's a wonderful language for that. The deep stuff isn't actually necessary.


True, but the problem is that any production ready library you come across uses some pretty advanced type magic that you have to learn to understand. At least to some extent.

Servant, the currently dominant API library/framework alone is already based on type level programming.


I have a page saved with a load of quotes, some of which are about this very subject. Here's a selection, from http://quotes.cat-v.org/programming/:

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan and P. J. Plauger in The Elements of Programming Style


talking about haskell, i always get a laugh out of: http://steve-yegge.blogspot.com/2010/12/haskell-researchers-...


I enjoy Haskell and I use it for some of my own projects, but I rarely use it professionally. I also use a simpler style of writing Haskell: I try to make as much of my code as possible be pure, with no IO or touching the real world. I try to isolate impure code as much as possible.

Using a subset of Haskell makes coding fun and productive, but when I have to read and use other people’s code, it takes time to understand what they are doing.

I have made peace with using Haskell in a simple way, enjoy it, but frankly spend most of my time using Lisp languages like Hy, Common Lisp, and Racket. I have been using Lisp languages for 35 years and part time Haskell for about 7 years, so it is understandable why I have an easier time with Lisp.


Wow but he continues to recommend using Haskell at all...


I'm having a similar experience with Golang, 90% of job postings are senior level, everybody wants 3+ years experience in the field, etc.


If code written by a "Senior" Engineer is unreadable/unworkable by someone with less experience - that is 100% on the writer. Don't enable ego-fueled programming, best to either do as the article suggests or bring in some actual more senior engineers who have already learned that code is read 10x more than written, and should be optimized thusly.


Perfectly fine advice if you're solving a routine problem with well-understood solutions with no special reliability, productivity, or security requirements. Terrible advice if you're pushing the envelope. If you're using Haskell with half its functionality verboten, why use Haskell?


Which is, perhaps, why basically no one uses Haskell in production.


While you do it, find a business case for the language development. PL papers in the <Funny Title>:<Actual Description> format make a good read but a production language also needs more mundane input and work.


Do Haskell programming jobs really pay better than, say, a Java programming job?


Maybe working on the one internal system Facebook has written in Haskell. https://engineering.fb.com/security/fighting-spam-with-haske...

Otherwise, I don't think many jobs pay more than Java engineer at FAANG.


What I try to do in my projects is to allow complex code (if it serves a purpose), but only in libraries and try to make it as self-contained and well documented as possible. Application code should be simple.


I concur. We use Clojure, and the actual application logic is boring and simple. This is good because it is easy to navigate in code from any project (even from other teams).

Complexity is all at our common libraries. So most programmers don't even need to understand our abstractions, they just work. Of course, sometimes our abstractions are not enough so you need to get your hands dirty. However I would say it works well 99% of the time.


Or become senior and apply for that Haskell job in five years.


The same goes for Scala in my opinion. A company using Scala should restrict themselves to a easily comprehensible subset of Scala.


I feel like you could say some similar things about flutter.


That's why as much as I love Haskell as a language and a way of doing things, I will not choose it to implement anything in my place of work.


This post probably also applies to <insert name of programming language here>.


Just for another data point, I'm a "senior" developer (been programming for 30 years) and I tend to look more at frameworks than languages. I really think that starting from first principles allows one to end up with an elegant solution, regardless of whatever errata is present in a language. A good framework is the elucidation of an idea with the edge cases covered so that you don't have to reinvent the wheel. It should give you the tools you need to integrate with your code and then get out of the way.

Specifically, take something like Laravel. It's based on Ruby on Rails, which borrows heavily from .NET. Personally I think that Ruby is a decent language that gets a few things right, despite some early compromises to get closer to the metal which caused some unwieldiness down the road. However, Ruby on Rails has a brutal learning curve unmatched by just about any other framework that I've learned. And the end result unfortunately succumbs to being too opinionated due to emphasizing convention over configuration too much IMHO. I think that's why it fell from favor, and personally I wouldn't recommend it for new development.

Whereas Laravel does a lot of what Rails does, despite using the "hackier" PHP language and less syntactic sugar or magic under the hood. The companion Laracasts are mostly exceptional. I would even go so far as to say that if you want to learn Ruby on Rails, learn Laravel first. That way you can build on context and be comfortable with Rails in a few weeks rather than the months it would take to learn it from scratch. You'll also notice the inconsistencies in Rails more and be able to work around them so your code is conceptually correct, rather than evangelize why their existence is needed or that they're the "one true way" to do something.

What I'm trying to say is that after using Ruby on Rails, I'm still not sure what problem it's trying to solve half the time. It seems to be structured in a way that solves issues encountered late in a project, but it's never really clear why one path was chosen over another. Like I solve a problem in my head, then have to translate it to the Ruby on Rails way. I don't get that as much with less-opinionated frameworks like Laravel. I think the best approach is to provide as much functionality as possible with sane defaults but not force the user into a paradigm.

Simplifying from Angular to Vue is another example of this. There are many others, but to play on this, it's one of the reasons why I'm uncomfortable with stuff like Kubernetes. Or even Unity for that matter. The more monolithic/enterprisy/opinionated something is, the more I'm skeptical of it.

This idea of working from first principles (the way a new user might) is a way to think about how to go about writing code that junior developers can use, even if the concepts involved are senior-level. I practice this technique but it easily gets lost in translation and I find myself explaining the easy vs simple dichotomy a lot. It's probably even cost me jobs to be honest. But that doesn't mean it's wrong.


I'm kind of having this problem. I'm a senior engineer but junior Haskell programmer.

Can't find any jobs willing to hire me as a junior haskell programmer so I just stick with the popular languages.


> The project can’t really grow anymore. Maybe the original employee left, and now they have a legacy Haskell codebase that they can’t deal with.

This is the litmus test for a good language, in my opinion. Not the way it makes you “feel” when writing it, but how easily it can add in new people to the project and get them running.

This is why — even with all of its shortcomings — I prefer Go to almost every language now.


Not specific to Haskell but, I think code in general is becoming more complex than it needs to be without any added benefit other than adding an extra buzzword to the list of things you know.

In a non-functional programming language for instance, I firmly place lambdas in this category. I was working on a legacy Java 7 application the other day, and Netbeans was configured for using lang spec Java 8 (which I later switched over to 7). Every time I wrote an anonymous function, it could give me a yellow underlined hint saying I should convert this to a lambda. Why? Why should I do that? It doesn't make it more readable, it saves only a few characters of space (our target platform had more than enough disk space for this to be irrelevant), and it makes the code more difficult to skim. And it's an extra thing a junior may not know about that they get bogged down trying to understand. Given, it's a simple concept and isn't hard to understand. But this applies to all sorts of other silly syntactic sugar the internet has decided is cutting edge tech, that is making our code bases less readable.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: