Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
How to Lose Functional Programming at Work (robertwpearce.com)
75 points by todsacerdoti on Jan 30, 2023 | hide | past | favorite | 105 comments


I find the constant conversation around code style extremely tiring.

I can't claim it isn't important. I do get the impression we think it is more important than it is.

I also think we frame things wrong. We push fp as being a declarative counter to imperative code. Feels more akin to switching coordinate systems. With so many examples of "look how easy this perfect sphere is in polar coordinates" when so many of us are working with rectangles. Or, at least, not perfect spheres.


It can be so.

But depending on the topic it's an absolute nuclear weapon. I've seen people accumulate pages of imperative code. Side effects everywhere, no reuse of small logical bits. 80% waste, averaging 4-6h per bugfix.

On the other hand short immutable code is more readable (yes even with a single letter vars), is linearly ordered, and often reusing the same combinators.


I mean, you aren't wrong. Bad code is indeed bad code. I've also seen mountains of abstractions in a "declarative" style that was just as impossible to deal with. I think I've been responsible for some of it.

Edit to add: The example I used to use was to look at the imperative version of the Koch snowflake. Then look at a version that is not. Indeed, even the examples you will find in Haskell are typically reaching for a much more imperative style than typical Haskell programs.


Immutable data does not necessarily let you answer the "what state does this value hold" question easier than mutable data.

Imagine you've got an immutable List. x.append(blah) does not mutate x but instead it returns a new list with blah at the end. If you want to know the state of this new list, you need to know the state of x. You still chase a chain of operations to figure out what your data can look like at some program point.

You get the advantage that mutation cannot be hidden inside of functions, but this is achieved just fine with "const" or whatever.


If your logic is about differentials then code around differentials.

Which is also one value of FP. This is, IMO, what lead to timetravelling stuff like redux.


But you do know absolutely for sure that you aren’t accidentally changing some other reference to x.


I feel this is an overblown problem. The number of times this has been the cause of a bug for me in the last 5 years is zero. Avoiding shared state mutation isn't especially hard in imperative languages: when it happens it's because you're dealing with another constraint - i.e. memory size or wanting to avoid copying due to speed constraints.

What is a constant problem is when it turns out you have the wrong value going into the wrong thing: and that's either a logic bug where your inputs are all fine, or a function which is returning the wrong thing. Debugging this problem isn't helped by avoiding mutation really: you're still going to be stepping through every line and checking function results (and a lot of functional styles are really annoying like this - z(y(x(a))) x(a) y z if you're allowed a point free style might save some space, but usually a debugger has no idea how to show you the intermediate results).


Accidental mutation doesn’t happen very often. But when the type system doesn’t distinguish between mutable and immutable values, it becomes hard to reason locally about the code. You never know for sure if that value that looks like it shouldn’t be mutated isn’t maybe mutated after all under some circumstance five function calls away. Or, conversely, if you add a mutation somewhere, it can be difficult to exclude the possibility that some code elsewhere relies on that value not changing. Instead of being able to prove the correctness of some local piece of code, or some locally applied change, it remains merely a good guess, unless you maybe do full-program analysis. That for me is the actual benefit of immutable types — you can sensibly reason about them without having to defensively copy them all the time.

I’m in favor of some statefulness, by the way, not a fan of pure FP languages.


> But when the type system doesn’t distinguish between mutable and immutable values, it becomes hard to reason locally about the code. You never know for sure if that value that looks like it shouldn’t be mutated isn’t maybe mutated after all under some circumstance five function calls away.

I don't think that mutability has to be an inherent property of a value: it's really a description of how a value can be accessed. Personally, I'm partial to the model used by Rust, where you can either have shared immutable access to a value, or exclusive mutable access to a value, and you can temporarily loan out your access to another function with a strict deadline (a "lifetime"). Together, this means that you can call a function granting it mutable access to your value, and be confident that the called function is the only thing in the world that could have modified the value.

I'm surprised by how this "controlled mutability" solution never seems to come up in discussions of imperative vs. functional programming. Perhaps it has to do with how mind-boggling the borrow checker can appear coming from most other languages.


> Avoiding shared state mutation isn't especially hard in imperative languages

You're right, it isn't, and I wasn't trying to claim it was a particularly big problem. What it is is another little you don't even have to think about which I found to be a pretty delightful feeling. Cloning isn't a consideration in mutable languages and that's a nice a little win. Though as a sibling comment mentions, if you're working with a bunch of inexperienced folk, these things are a lot more likely to slip through.

As for the debugging bit, I made the sin of comment before I read the article and realized it was using JS. No matter what anyone wants to tell you, JS is very good as a functional language. It can be ok, but it's not great. "Most functional styles" is otherwise a very unfair statement as most functional languages come with pipes or infix application operators that make debugging really easy as you can throw an `inspect` into a pipeline.

Getting a bit off the rails just as I was excited about this, but Elixir recently introduced a `dbg` function which, if used at the end of a pipeline, outputs every line of it which is crazy nice (unsure if this was borrowed from another language or not):

    "hello there"
    |> String.split()
    |> IO.inspect()
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(" ")
    |> dbg()
produces:

    [file_name.ex:8: ModuleName.function_name/0]
    "hello there" #=> "hello there"
    |> String.split() #=> ["hello", "there"]
    |> IO.inspect() #=> ["hello", "there"]
    |> Enum.map(&String.capitalize/1) #=> ["Hello", "There"]
    |> Enum.join(" ") #=> "Hello There"
So that's really nice.


edit: of course I meant that cloning isn't a consideration in IMmutable languages and that JS is NOT very good as a functional language... oh boy.

Annnd another nice thing is consider code like this:

   perform_some_action_with_object(user)

   user.i_hope_user_hasnt_changed()

In mutable languages we can't be sure that `perform_some_action_with_object` doesn't mutate `user` so we have to check. This isn't even a consideration in immutable languages. Again, not the biggest problems in the world, but it really helps when you want to quickly scan through a codebase you are unfamiliar with. You could also say "don't write code like that" and I tend to agree, but I have seen a lot of code like that in OO codebases in my time.

I made sure to double check my spelling this time :D


now all we need is an accumulative dbg so you can inspect data flow between different runs :)


You may have worked on better codebases that I have. I have seen plenty of "let's write that stuff to a global or thread-local variable because" which makes code harder to understand, test setups harder and may even lead to subtle bugs.


> Immutable data does not necessarily let you answer the "what state does this value hold" question easier than mutable data.

Yes, it does. You're right that you still have a chain of operations to figure out first. But with mutable data you need to do that, as well as know when you're looking at the data, and what else is running in the system that has access to that data.

It doesn't grant superhuman insight into program state, but it is orders of magnitude simpler than mutable data (perhaps several orders depending on the scope of the data).


It's a tool to be used in parts where it makes sense. "Writing functional" (or any other paradigm really) code should never be a target


The target, of course, is writing correct code.

I find recursive functions over immutable values easier to prove to be correct than iterative loops over mutable storage.


The target is frankly to get paid.

Sometimes you're making a prototype. Sometimes you're making one off get some info out of data and never use it again.

Provably correct app that now costs 10x and is essentially yet another CRM or CRUD app is a failure because competition already delivered theirs 2 years ago and ate your market.

Go too far in another direction and you're living in house of cards with tech debt killing all velocity you had and users leaving over app having bugs or just being slow, while you're drowning in rotten spaghetti.

I do think that "doing the minimum to do the job at hand" is wrong approach for near everything that will live more than two or three months, but there is definitely threshold on other side of that where you're essentially just gilding the lilly and wasting time.

The key is to pick your battles, time is resource that thing that every other component of your infrastructure is going to use probably could use more of that time than random feature someone up top invented on a whim.


I find that I can prototype most quickly when I'm not wasting time debugging, and I tend to have the fewest bugs when writing recursive functions over immutable data and the result is either going to be correct or it's not going to pass the type checker.


It’s hard to do FP in any language that isn’t designed for it. The reason for that is on a reasonably sized team you won’t be able to get consensus on adopting an FP style. There will always be the counter-argument that FP makes the code look weird, unmaintainable, unapproachable etc.

I think the only way to adopt it is to either use a language where it’s the default or get consensus from the team.

Otherwise you can sneak in bits here and there in a functional style, just don’t start implementing monads, functors, and Applicative. Anything beyond map and basic higher-order functions will scare the locals.


Stateless functions can save a lot of headaches enabling one to pay more attention to state in fewer places, create good tests and so on.


Too true!

Having gone through the difficulty of learning what a functor is, what a monoid is, semigroup, monad, etc... I'm better for it. I can see why they're so useful in programming. I can see how they can help me write simple programs. However there was a time when I thought these concepts were complicated and obtuse. I couldn't see their benefit. I'm glad to have relieved myself of such pesky illusions.

The problem I'm alluding to is that on a team of developers with mixed levels of experience and backgrounds it is hard work to find consensus in general. If you've gone through the trouble of picking a programming language to work in and you have established that the style of programming will be OO, coming in and suggesting that the style should change to FP is going to be a hard sell.

You can tell them about all the benefits... but without having been through the process you won't get good feedback beyond anything superficial or downright ignorant.

And if you try to shovel it on them anyway in your little corner of the code you'll risk causing quite a bit of controversy! You can get away with an acceptable amount of functional programming style on most teams these days... but going beyond the ones I mentioned will usually draw such unwanted attention.

The other facet of it is that programming in a functional programming style, beyond the very fundamental ideas, in a language that isn't suited for it poses a difficult proposition: you have to maintain the rules yourself. If you break one, use an escape hatch just once, let a bit of something sneak in; you've broken all of your promises and the usefulness of it is gone.

In a language designed for functional programming first you don't have to waste this mental energy on ensuring functions are pure, that values are immutable, that equality is maintained, that the laws are preserved, that side-effects are limited to a scope; etc.

So definitely, take these ideas from functional programming if they help you write better programs, or help you understand your programs better!

If you're not using an FP-first language be aware of the trade offs and difficulties.


They're pretty hard to use consistently in languages which aren't accomodating for them though, such as Haskel or Scala. In Java, C++ etc. you can't really program without majority of your functions having side effects - or at least I haven't yet seen anyone doing it.


It’s true that people don’t do it, because of language conventions, but there is no barrier to entry when it comes to writing functions (or methods in these languages) that operate solely as transformations of inputs to outputs. You then orchestrate these “pure” functions with simpler side-effectful methods that handle the external dependencies (data fetching/writing etc).

There’s no pushback because of “strange” FP patterns, code looks just like normal imperative code, but the tricky logic can be isolated in pure functions. The push back is only because of the strong OOP conventions.


> but the tricky logic can be isolated in pure functions

That's not really FP though. In FP, you only have pure functions and not a mix of pure and impure.


Perfect is the enemy of good. I’ll take any percentage of pure functions that I can get.


creating and transforming immutable data structures can be a big pain in some languages though, and that's it's own barrier. java is a good example but it's getting better with record types etc


> It’s hard to do FP in any language that isn’t designed for it.

I don't see why this is true, and perhaps it's because I don't have a full enough concept of functional programming.

In any language, why can't we write functions that don't modify the arguments, offer the same return for the same arguments, and modify no internal state when called? And then compose those functions together to form a full working program?


> In any language, why can't we write functions that don't modify the arguments, offer the same return for the same arguments, and modify no internal state when called? And then compose those functions together to form a full working program?

Here here. I am consistently baffled as to why this approach and OOP are apparently mutually exclusive, and why functional programming must be more than the approach quoted here.

Functions/methods as small/deterministic as they can be, judicious type checking, separation of concerns...maybe I'm dumb and/or inexperienced, but this approach seems perfectly fine to me, at least for the domains in which I work.


They're not mutually exclusive!

OCaml has an OOP system in place. C++ has lambdas, some "monads," etc. You can do functional programming in C#, TypeScript, even Java these days. People have even written libraries in Haskell that let you do OOP if you want.

Jack of all trades, master of none, is how I think of it. It becomes complicated trying to be a language that appeases all programmers. The C++ specifications, ECMAScript, etc are all enormous.

The Haskell 2010 report is quite small by comparison. The R6RS specification is smaller. When you have fewer things to consider and a smaller audience to appease you can do a whole lot more with much less.

When you go all in with a language that is built on functional programming principles first and foremost, you end up with a very different language than if you start with procedures and statements. You can emulate functional programming in C but it requires discipline and you don't get any feedback from the type system, compiler, or tools unless you write them yourself. When you write code in Haskell you get all kinds of feedback and the mental energy you used to spend on discipline can be used elsewhere.


You quoted only part of the comment, it mentioned that some basic techniques can be applied but most of the intermediate-to-advanced FP patterns should not.


> Otherwise you can sneak in bits here and there in a functional style, just don’t start implementing monads, functors, and Applicative. Anything beyond map and basic higher-order functions will scare the locals.

10 years ago you would have said the same thing about map and those "basic higher-order functions" that you say about monads, functors, and applicative.


It's a slow process. Haskell, OCaml, friends, ancestors, and descendants have been the inspiration for plenty of language features in the mainstream. When their programs get hard to write and maintain they reluctantly pluck from the fruit of functional programming.



I grimace whenever I think about the time where I went a bit ham on FP as a junior engineer. I genuinely feel sorry for whoever is maintaining that piece of code.


At least pure functions are amenable to gradual refactoring. Now an overzealous OOP junior... That’s dangerous!


I don't think one is easier to refactor than the other


Haskell programs are so phenomenally easy to refactor in deep and complex ways it's unreal. I have been astonished over and over again. (That said the state of automated, trivial refactorings (like renaming identifiers) is probably much better in other languages.)


I'll take an over-engineered enterprisey Java monolith over a Haskell codebase written by type astronauts any time of the week.

I mean, at least the former will be somewhat working and with proper IDE support.


Are you saying you cant write bad code in functional style? Because then you have drank too deeply of the cool-aid. No fad, tool or paradigm will prevent inexperienced developers from writing bad code. Education, mentoring and code review can help though.


No - maybe read my comment again?


People were saying the same thing about structured programming and object oriented programming back in the day.

Lets say you have a huge overly-convoluted Haskell program. Somewhere deep down a call hierachy of pure functions you need to print something to the console. That is not easy to refactor.

Or vice-versa you have a huge convoluted program where everything happens inside an IO monad because at some point something is written to the console. Now you realize you dont need to write to the console.

Pure functions are great, but they are not a panacea.


> Lets say you have a huge overly-convoluted Haskell program. Somewhere deep down a call hierachy of pure functions you need to print something to the console. That is not easy to refactor.

> Or vice-versa you have a huge convoluted program where everything happens inside an IO monad because at some point something is written to the console. Now you realize you dont need to write to the console.

These problems are essentially completely resolved these days by a modern effect system like effectful. Basically, they allow you to do arbitrary effects deep down a call stack with minimal plumbing (you still have adjust the types, as you should: that's the point of effect tracking!) and also to remove effects, so you can easily convert between pure code and "effectful code that just so happens to do no effects".

https://github.com/haskell-effectful/effectful


So until this library solved all problems, it was possible to create messy and hard-to-refactor code in Haskell.


It's still possible to do that! It's just that particular rough edge (which was indeed a problem, just not a particularly important one) has been smoothed off.


That’s happening with OOP. I remember I picked up this codebase circa 2008 and this guy who worked on it before was learning OOP. Every little thing was a class, lots of pointless inheritance everywhere and nothing made much sense at all. Luckily the project was easy to scrap and rebuild.


I still think it's funny that half of the OOP tutorials are "imagine that hierarchy of real things unrelated to anything programming and pretend for a second what we make is sensible" instead of giving actual real useful use cases.


Yes, it's actually a huge red flag that you never see useful examples of inheritance.


The useful example is UI control hierachies. GUI’s was one of the early sucesses of OOP. Those “Dog descend from Animal and has a bark() method” just confuses the issue.

Inplementation inheritance is a useful tool when you need it, but it’s not that central to OOP.


OOP is successful in GUI app is because states are at everywhere in GUI app.

Many other apps don't have many states to manage, OOP is bad for them.


I would like to see one. Ideally one that has the functional programmer rewrite things. I don't have any bias besides being completely ignorant about classes. The tutorials do talk to me about cars being a sub class of vehicles and the prevention of rewriting things. My mind goes like: "Hierarchies? seems bad?: You have buildings and vehicles with cars belonging to vehicles and habitats belonging to buildings. Where put the RV? Are writers children of nationality or is nationality a child of writer?" iow the example is probably to unrelated to programming to be a sensible one.


> Are writers children of nationality or is nationality a child of writer?

Wouldnt that be a has-a relationship? Represented by composition rater than inheritance.


>Where put the RV?

Both. You can have a RVVehicle class and a RVHabitat class. Nothing is forcing you to have 1 class. If it makes more sense to be just one class you can also make it a subclass of 2 classes.

>Are writers children of nationality or is nationality a child of writer?

Nationality would be a child of writer.


I think that's the idea though. It's for domain modeling. What's wrong with OOP as an abstraction over your FP foundation?


Generally, OOP makes it very difficult to reason about what is happening. Sub-classes can override methods in subtly different ways, internal state can be updated, super classes require changes to implement certain things in sub-classes, etc. Composition over inheritance has been much more robust in my experience.


Sounds like most of your complaints are about things that even OOP advocates recommend against these days. Multiple inheritance is rare in practice, and even rare in language support. Some newer OO languages, like Pony for example, don't support sub-classing at all and instead uses interfaces to represent that sort of information.


..the idea your code contains apples and oranges? I'm talking about the tutorials that wish the examples out of some random everyday items instead of having example app that uses the inheritance in useful way on code that someone with actual problem might write.


Once a bad idea is introduced it’s hard to stop. Tutorials are copying from eachother and more and more are ingraining those ideas.


Ha, I did the same thing, and in Perl nonetheless! My poor boss. I guess he forgave me since we’re still friends!

Edit: A good book: https://en.m.wikipedia.org/wiki/Higher-Order_Perl


I’m my opinion, how to win would be to push for more logic in stateless functions, full stop.

This can be done in any language without introducing “foreign” FP patterns and instantly makes code easier to understand and test.


Thanks for stating this so clearly. I am always confused why data flow frameworks, DSLs, or other syntactic sugar inevitably gets entangled with functional programming. Once more logic is in stateless functions and easy to test and compose (as you stated), one can gradually introduce layers, sugar, and abstraction (should the team choose)


On the contrary, that is exactly what those patterns are for, indeed it's the only legitimate justification for using them! If you can make your functions simple and stateless without using "fancy" patterns you should do so. The point of things like monads is to make it possible to do that with functions that do otherwise effectful things.


If your functions are "stateless" (by which I assume you mean they don't depend on or modify outside state), then they are pure functions so... that's exactly FP?


That’s my point. FP can be as simple as that, and applied to any codebase. It’s all the extra stuff (which is good) that typically “defines” FP that scares people away, and keeps them from seeing the utility of a simple, pure function.


I'm with you


The linked website's CSS is unusably broken (at least in Chrome on Android): most of the text in example code snippets is white-on-white, thus invisible; and remains white-on-white even if selected.

Fortunately, there is a toggle to change theme in the upper right corner which at least makes things readable.


Author here: I broke the CSS last night on light mode while making a few changes. Sorry about that!


Is it just me, or does the bad advice format of the article make it hard to see tell the good advice is?

In my early days teaching LaTeX classes, I used to have an example of incorrect input in the first day’s material (forgetting to leave a space after a command). Even though it said before the example that it was incorrect, invariably, at least one person in each class would type it into their computer and have the error returned to them. I removed the example because it only hindered the teaching process. I feel like articles like this which use opposite-speak in making their point do the same thing.


It's a bit more analog, but one piece of advice I got in a teaching seminar was to only ever write true things on the board. This is important because students often zone out at just the wrong time and when they come back and realize they missed a bit of class, they'll glance at the board and furiously start copying down whatever you wrote without knowing the context. If it turns out what you wrote wasn't true, well now a whole bunch of students copied down the wrong thing and are going to be extremely confused why the example they copied verbatim from class isn't working.


That's an interesting point. When I taught, I took this same approach — thanks for reminding me of it.


I agree, it’s confusing. Plus, it looked like negative and positive advice was mixed, when referencing a talk.


It's 100% a culture thing. If you're not hiring for FP roles multiple things are going to happen.

1. Nobody who knows F paradigms is joining. Also these people most likely won't want to learn either.

2. There will be a constant battle for all PRs. "What is this, what is a filter doing, why are we mapping when we could just for loop and break"

If you have even an inkling to do FP, your only option (besides building your own team) is finding a company that respects it


The article's code formatting is making some words invisible for me, as seen here: https://imgur.com/a/gv3FDGY


Same here, I was super confused. As a workaround you can put the site into dark mode at the top right.


Should be fixed now. Sorry about that


Sorry about that :(


All good—figured it's better to report it so you could fix it. Thanks.


The idea of writing point-free code in Javascript seems absurd to me.

On Haskell, the odds of a point-free rewriting increasing legibility are about as large of it reducing. And Haskell has all the syntax features that favors it. Javascript has none of those features.


Haskell level FP in TS is absurd on the whole. I respect the idea of fp-ts, and it's hardly the library's fault, but it is remarkably ugly. Type safety is only half the appeal of FP; it's also usually concise and pleasant to read. You lose that trying to hammer the concepts into a javascript shaped hole.


My general rule with point-free code is that it can be more readable when it's meant to convey that what's going on is a composition of components with no additional functionality.


These negative logic type blogs are such an annoying style of writing and very click baity. Obviously no one would want to lose functional programming at work on purpose, so why not just write a straight “How to Win…” article instead? Why make me have the cognitive overhead of reading the opposite of what you say?


This originated as a fun talk at the Auckland Functional Programming meetup. Given there is so much "here's how to get your team into FP" content out there already, this felt like an engaging approach and was a nice talk to share with folx.

I disagree that it's annoying and click baity — else I wouldn't have went to all the effort — but if it's not for you, it's not for you.


Obviously my original comment was meant to be read as the opposite of what I said, given the tone. Since there’s so many other comments out there heaping praise this felt like a more engaging approach. Though I guess if you’re not paying close attention you’d dismiss it as negative intent.


Ah, I missed it — probably because I haven't seen any of these "heaping praise" comments yet


I enjoyed reading this post. To me it is a mix of sarcasm and roasting.


I didn't know what "point-free style programming" was so:

> Tacit programming (point-free programming) is a programming paradigm in which a function definition does not include information regarding its arguments, using combinators and function composition [...] instead of variables.

from https://stackoverflow.com/questions/944446/what-is-point-fre...

I guess it's the fact that in programming languages like OCaml you write functions like that:

  let some_fn arg1 arg2 = ...
and so you can partially apply them:

* using `some_fn` without any argument is manipulating a function with two arguments

* using `some_fn arg1` returns a new function that only takes one argument

* using `some_fn arg1 arg2` applies the entire function


Note that in OCaml, you can't get too screwy with point-free programming because of the value restriction. It is possible to compose functions in a point-free manner, but those functions themselves have to have points if you want them to be generic. Standard example:

    let last xs = List.fold_left (fun x y -> Some y) None xs
This is of type

    last : 'a list -> 'a option = <fun>
Neat, `'a` is generic. Let's η-reduce out the `xs` and make the function point-free (ignoring the lambda):

    let last = List.fold_left (fun x y -> Some y) None
This doesn't work the way that we want:

    last : '_weak1 list -> '_weak1 option = <fun>
The moment that we call this weakly polymorphic function, its type is fixed and is no longer generic. In the toplevel:

    # last [1;2;3];;
    - : int option = Some 3
    # last['x';'y';'z'];;
    Error: This expression has type char but an expression was expected of type
             int
Haskell, of course, is totally happy to let you do point-free mania with Kleisli arrows and all of the rest of it.


You’ll know you’ve lost people when normally thorough PR reviews now look like, “<thumbs up>”.


I like FP - I think that engineering and writing code in a way that would also let it be written in all the craziness of full FP style means you found the right abstraction - but I would never write it that way.

A nice example from the article:

  const processData = composeP(syncWithBackend, cleansePII, validateData)


  async function processData(data) {
    await validateData(data)
    const cleansedData = cleansePII(data)
    await syncWithBackend(cleansedData)
    return data
  }

  // or for the Promise-chainers…

  const processData = data =>
    validateData(data)
      .then(cleansePII)
      .then(syncWithBackend)
      .then(() => data)
I would always write this as the 2nd option - it's always going to be the easiest to debug[0] and the abstractions it leverages are equivalent to the composed version in the first example or third example.

When I write code that leverages FP principles (pure functions, composition, immutable data structures, etc) I know I've found the right abstraction - but there's never a need to actually abstract it all the way. That's the job of a good compiler.

I'm nearly always writing code for other people to later debug, fix, extend and understand and rarely for the computer - don't make your future self's job harder than it needs to be.

[0] https://sambernheim.com/blog/debug-driven-development


> You're probably bulking at the 3 repeated loops

“balking”?


On Chrome/Edge the page seems to open as expected and the PDF is previewed inline. On Firefox, a download immediately starts. Not a big issue or anything, just a slight difference in how browsers handle the page.

The HTML on the page responsible for this:

  <iframe class="pdf" loading="lazy" src="/pdfs/2023-01-24-how-to-lose-fp-at-work.pdf" title="How to Lose Functional Programming at Work - PDF">
  </iframe>
Here's approximately what I've used myself in the past as an alternative:

  <object data="/files/my-document.pdf#zoom=110" type="application/pdf" class="my-style-class" title="My PDF document">
    <!-- Fallback for older browsers that do not support the object tag -->
    <embed src="/files/my-document.pdf#zoom=110" type="application/pdf">
      <!-- Fallback in case PDF display of any sort is not supported -->
      <noembed>
        <span class="my-warning-class">
          It seems like your browser does not support displaying PDF files.
          You can download the file separately <a href="/files/my-document.pdf" download>here</a>.
        </span>
      </noembed>
    </embed>
  </object>
(not too clean of a solution, but seems to work okay in most cases)

The presentation/article itself is pretty nice, though. I really like the mentions of the human aspect of it all, how getting others onboard is quite important!


That's really weird, and I'm sorry that happened. I'm on Firefox Developer Edition on macOS, and it displays inline for me, but that could be my settings.

I'll take a look at using an embed tonight. Thanks for the feedback!


> I'm on Firefox Developer Edition on macOS, and it displays inline for me, but that could be my settings.

I looked into it and it indeed is the settings!

To reproduce the behavior, you'd go to General > Applications, then find PDF and if it's set to "Open in Firefox" then it will render inline, whereas if it's set to any of the other options, then it won't:

  - Open in Firefox: will show the PDF inline, which is probably your setting
  - Always ask: opening the webpage will result in a prompt for what to do with the PDF
  - Save file: will always download it
  - Use OS default application: will download it and then open it
This doesn't seem to be the case with the alternative approach, which just renders it inline always (when the browser in question supports the functionality), though this is definitely a bit of interesting behavior otherwise!


The fix for this is going out right now, and I gave you and your original comment credit in the article. You rock!


I'm doing it wrong! Thanks for looking into this. I will 100% fix this.


well, embedded pdf on iOS looks like a single image of the first page with no ability to scroll or interact in any way, not even select text.

so a text-link outstide of noembed would be a nice alternative anyways, i bet it will be better for screenreaders too


Since this was originally posted, the inline PDF is hidden an replaced with a link on smaller screen sizes.


It’s always a terrible idea to go against the ergonomics and idioms in a language. Shoehorning JavaScript into something purely function is just as bad of an idea as trying to do object-orientation in Haskell. If you believe in certain patterns, pick the right tool—in this case a compile-to-JS language—for the job. Either make the case for rewriting certain modules, or find a job/team that supports your goals and principles.


I don’t think I’ve ever thought a blog post—not its author, just the post itself—is a jerk until now. I read most of it but bailed because it felt like being bullied for no apparent reason.


Wow, I'm horrified that the post was received this way. How did it make you feel bullied? Was this because of it using imperatives with "do" and "don't"?

edit: the real-talk takeaways at the end are worth reading, I think (I'm biased, though)


I don’t think it’s reasonable to treat TS and Elm as equivalent.


TS and Elm were included in a "Don't have static type checking" section, alongside Flow, ReasonML, and a blank space for all the others. The point I was going for was that there are numerous options for static type checking for the web out there.


Sure, but I still don’t think they’re equivalent. Not all type systems are created equal, and TS has to work under a wildly different set of constraints (like escape hatches) than Elm does.


It's hard for me to tell if your account is a joke or not because of the "yakshaving" part, so I'll respond as if it isn't.

I totally agree with you! I can't say that conversation belongs alongside this post, though.


I appreciate that you assumed good faith. For what it’s worth, “yak shaving” is an analogy to what programmers often find themselves spending their time doing.

https://seths.blog/2005/03/dont_shave_that/


go easy on the combinators in JS land




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: