Conversation
| @@ -0,0 +1 @@ | |||
| # Option | |||
There was a problem hiding this comment.
@JamieB-gu I might need your help writing a readme for these modules... do you have time some time? 🙏
There was a problem hiding this comment.
Sure, happy to! How would you like to do this? I can push to this PR, I can create a PR into this PR, or we can pair?
|
Before I go much further with this migration, I'd like to question whether this is a good addition to I'm going to make the case that code in this repo should represent The Guardian P&E's recommended way of doing things. Having a repo that is the go-to starting point for solving common problems is extremely valuable for onboarding, and for delivering new products and features quickly. Having a single recommended approach to solving common problems, where possible, is more valuable than having multiple options to choose from. Offering multiple solutions for a single problem introduces more work for consumers who must evaluate each of those solutions. This is inefficient, and inefficiency comes with an opportunity cost. There will always be trade-offs between options and those trade-offs should be made clear. There is nothing wrong with using a non-recommended approach if the trade-offs of the recommended approach are too great. Does this sound convincing / useful? I'd love to hear your thoughts |
|
Assuming you are convinced and you now believe that this repo should contain The Guardian P&E's recommended way of doing things, we can evaluate whether the Option
I must admit I'm not used to working with types like this, so I'm struggling with the way it is being presented. I'm going to attempt to unpack what the API is doing to help my understanding. If I have anything wrong, please let me know.
|
| it('produces None when Result is Err', () => { | ||
| expect(pipe2(mockErr, toOption, withDefault(6))).toBe(6); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Would you like me to write some tests for Option too to keep coveralls happy?
There was a problem hiding this comment.
Thanks for offering, I'd say maybe in the future. But I think before we commit any more time to this, it would be useful to address some of the points in my earlier comments. Would you be able to take a look?
There was a problem hiding this comment.
Was in the middle of writing when you commented this 🙂. See the essay I shared below 🙄.
|
Thanks for doing this @SiAdcock! So quick too 🚀. I appreciate you trying to gather some feedback on the API - I'll try to do my best to describe the thinking behind it below. Apologies for the essay 😬.
Yeah I think the API docs can mostly be copy-pasted from JSDoc. The JSDocs are there mainly for Intellisense convenience (they pop up whenever you hover over the types/functions from this lib).
I don't think they need to live in their own isolated directory, but I don't feel too strongly. It's not going to make much difference to consumers 🤷♂️.
Fair point, I think that's valuable too. I guess the counter-argument to that is that there isn't always a one-size-fits all solution, and some parts of this library will be useful to a subset of projects in the department. For example, the Likewise the Perhaps. Or perhaps that's fine, and the problem is that we haven't proliferated the modules in this repo across enough of our projects yet and we should be focusing on that? What do you think?
Based on what you've said and the examples I think your understanding matches the intent 🙂. Only thing I'd mention is that
I'd argue that it provides a more readable API for new developers coming from a number of languages, not just functional ones; for example, Scala, Rust and Swift all have concepts of Its main value is actually what you mentioned in the first two sentences above. It avoids dealing with const name: string | null | undefined = 'CP Scott'
const byline = name || 'Jane Smith'There's a fair bit of complexity hidden in this pattern for assigning defaults. First off, at first glance surely this doesn't make sense? You've got a boolean operator, but neither side is working with boolean values? Oh wait, it turns out that JavaScript will implicitly convert these values to booleans under the hood. Ok... So what is the boolean equivalent of a const credit: string | null | undefined = 'CP Scott'
const result = credit ? `Photograph: ${credit}` : nullI won't go through the whole self-indulgent story again for this one, but I think it suffers from many of the same problems. Boolean type coercions, empty strings, The point is, I don't disagree that these are common patterns in JavaScript. My argument is they're common patterns with a lot of problems and that I think we can do better. Particularly if, as you mentioned, we'd like to make it easier for less experienced/junior developers to contribute. |
|
@JamieB-gu Thanks for responding, these are really great arguments.
I agree. Trade-offs of any recommendations (and alternative solutions if the trade-offs are unbearable) should be made clear, so that people can make the right decision for their use case. If we're going to provide recommendations, we should by default recommend using the language to solve the problem. This means we have something to fall back on, that we don't have to provide specific recommendations for every line of code that needs to be written. It also helps to ensure that we're always using the right language for the job, we're not just picking up the familiar tool. What we're boiling down to is whether TypeScript (and really, the JavaScript syntax that it has inherited) is adequate enough at communicating the intention of developers using it. In what situations are we not convinced that it is communicating clearly enough? Where the language falls short in some way, or where it isn't designed to deliver a particular feature, then we can provide recommendations for those scenarios. And I think I've also used these patterns correctly hundreds of times, and there's no question that this style of programming can be fast to read, write and review, and delivers (mostly) adequate results. Reading the relatively short JavaScript: The Good Parts will get new developers up to speed, and there may be better resources out there today (please ping me if you know of any). Personally I'd like to see more discovery work in this area before we try to patch over this particular language feature. Are new devs finding type coercion a source of confusion? Are we getting tripped up time and again by falsy? Is the complexity slowing down reviews, or introducing corner cases that let bugs slip through the cracks? It would be useful to see some examples or track down data that shows where the From the other end, we ought to consider the opportunity cost of introducing this approach. A case could be made that the approach is unfamiliar to client side devs, existing or future, from associate to senior. There is a ramp-up cost to them learning this, which could otherwise be spent solving problems for the business or reducing technical debt. It would be useful to understand how quickly we can get unfamiliar devs up to speed with This is my 10 pennies' worth, I'm still up for reading yours and other people's opinions. But really I think we need to demonstrate the business case for this approach, if we are seriously going to consider recommending its use across the department. Please let me know if I can help with this in any way. |
|
That all sounds very sensible to me - I like the approach you're taking to this ⭐️. I think I've mostly made my case so I won't give other people reading this yet another essay to wade through 🙈. I'll just address a couple of specific points you made.
I've seen many examples of both confusion/review overhead and bugs introduced over the years. For example, this PR yesterday fixed a subtle bug caused by
I think another way to look at this would be to argue that you actually save time in the long run. That's if you assume that, as a software developer, JavaScript won't be the only language you use in your career. Admittedly for some people it may be, but many are either coming to it from another language, or would like to experience working in other languages in the future (we developers are a naturally curious group of people 🙂). For example, just within the department it's common to be expected to work in at least two languages: TypeScript and Scala (two and a half if you consider that JS and TS can look quite different). If you accept that premise, then I think it's valuable to spend your time learning concepts that are common across different languages and environments. Speaking from my own experience, I originally learnt some of these concepts from Elm and Haskell, and was then able to trivially extend that knowledge to understand them in Scala, Rust, Swift, TypeScript and so on. I see the alternative as spending your time learning the specific quirks and patterns of a language that's notorious for having a lot of quirks, and then being able to re-use very little of that knowledge in other environments and languages. |
|
Today I remembered that there were some interesting takes when we had this same discussion a year ago in guardian/types#5 |
|
As @SiAdcock suggests earlier, union types and the ternary are idiomatic, not verbose, and work well. As much as I like Option etc. in Scala, I'm not convinced this is a problem in TS that needs solving. The benefits will be less too than for Scala etc, because the pattern is not widespread - e.g. there are more 'edges' to coerce into the pattern than in Scala etc where at least libraries will already be using wrapper types like this - and because of the lack of equivalent pattern matching, which hampers readability when branching. |
i think we should be optimising our code for developers who know the language it's written in before ones who don't. and further, i'd argue familiarity with the language of the code and its common practices ought to be sufficient: the less you need to know to be useful, the more you can do. bespoke interventions like these have both financial and opportunity costs (you have to pay someone to learn an unfamiliar idiom, and that time is then not spent doing something else). so then the question for me is: can the problem this addresses be handled without paying that cost? if there is a provable case for the benefit here (or even majority support) then i would say something like but until then, since this is implemented in TS, it means it's possible to achieve the same outcome in 'native' TS. which means this is more a question of taste (both this and a more imperative, idiomatic approach work). imo, such questions should defer to our (current and future developers) ability to write, ship, fix and delete code i.e. build products. to me, this feels like it prioritises DX (for some people) over the ability of people who know TS (from a little to inside out) but have never seen this model to contribute. this is not to say that js (and so ts) doesn't come with footguns, but i'd argue that having the computer point out where you've not handled one ( having to grok a new mental model in order to elegantly skirt round a shortcoming is the 20%, but it comes at the cost to the 80%... |
|
This is a very thoughtful tread, and I appreciate how carefully and clearly people have put their thoughts together. I'm reading it today for the first time, and I'm loath to say anything because I fear I've not yet put in as much thought as you have. But I will anyway. I'm definitely more client-side (and a lot of my server-side experience was JS too), and much more comfortable in TS than I am in Scala, so my instinct is not to like this. I personally think we should write each language following the standard patterns and best practices of that language, and that makes for a codebase that's easier to maintain and especially for new developers to learn. As always we should be writing code that's easy to read (as opposed to easy to write). If someone comes in in knowing TS they should be able to read and understand how the TS code works. The code shouldn't do things that are easy to do in TS in unusual ways. Using Simon's examples of what I'd consider idiomatic TS, I really prefer them (with their known, well understood and documented limitations) over this specific abstraction. There is no doubt learning new languages has enriched and improved my ability to write code. And we should be encouraging TS devs to pick up scala. But I'm not sure that means I should be writing TS like it's scala. |
|
After a lot of consideration, we have formalised and generalised the conclusions of the discussions around this PR in https://github.com/guardian/recommendations/blob/master/coding-with-empathy.md. Our conclusion is this is not a pattern we want to formally adopt in our TS codebases, because it currently diverges too far from we might reasonably expect a TS developer to know. That being said, there is clear support for something along these lines. Hopefully a similar pattern will make its way into TS itself (can we PR this upstream?). |
|
Fair enough. We may have to rethink deprecating |
|
Please can you expand on that a little @JamieB-gu? What would be the impact of deprecating |
|
Well based on @sndrs summary above, we've decided not to migrate That said, in order to deprecate |
|
i think it's only apps-rendering and image-rendering that use them – can they be inlined there? do they need to be in a library? also, should it really be used in an library like |
Given the conclusions above, I'm inclined to think not. My main concern here is that |
They're also used in
I think, given they're used in at least three different projects, a library is the best place for them. I don't think it's ideal to make several PRs for every bugfix or new feature - that's kind of the point of a library 🙂. As it is there are a couple of new features implemented in AR that should really live in the library - they were just there for now while we were waiting for the migration.
Yeah I think this is reasonable. In this case I don't feel the API is too onerous for the consumer: it's just a case of using the So, in summary, we can remove them from the public API if you think that's best 👍. Note that this won't stop |
What does this change?
Migrates the Option and Result types and associated logic from
@guardian/typesto@guardian/libsWhy?
See guardian/types#136
TODO
Option/typesfolder?