[{"data":1,"prerenderedAt":6200},["ShallowReactive",2],{"article_list_programming_":3},[4,776,2478,2812,3496,3805,5894],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"tags":11,"publishDate":16,"excerpt":10,"image":17,"body":18,"_type":767,"_id":768,"_source":769,"_file":770,"_stem":771,"_extension":772,"author":773},"/phendry/2023-11-06/frontendframeworksin2024","2023-11-06",false,"","Frontend Frameworks in 2024: React, Svelte and Vue","Several years ago, Art+Logic settled on Vue.js as our preferred frontend Web framework. Now, in 2024, we feel it's time to revisit the frontend framework landscape to see how things have (or haven't) changed.",[12,13,14,15],"programming","react","svelte","vue","2024-05-15","/phendry/2023-11-06/img/frontend_frameworks_2024.png",{"type":19,"children":20,"toc":754},"root",[21,49,72,95,100,105,110,155,187,192,199,204,209,214,246,251,256,261,291,305,310,319,324,338,357,362,367,372,377,391,399,404,409,414,419,431,462,468,473,496,501,507,512,517,523,528,533,538,543,549,554,744,749],{"type":22,"tag":23,"props":24,"children":25},"element","p",{},[26,29,38,40,47],{"type":27,"value":28},"text","Several years ago, Art+Logic settled on ",{"type":22,"tag":30,"props":31,"children":35},"a",{"href":32,"rel":33},"https://vuejs.org/",[34],"nofollow",[36],{"type":27,"value":37},"Vue.js",{"type":27,"value":39}," as our ",{"type":22,"tag":30,"props":41,"children":44},{"href":42,"rel":43},"https://blog.artandlogic.com/ckeefer/2020-1/why-vue",[34],[45],{"type":27,"value":46},"preferred frontend Web framework",{"type":27,"value":48},". Now, in 2024, we feel it's time to revisit the frontend framework landscape to see how things have (or haven't) changed.",{"type":22,"tag":23,"props":50,"children":51},{},[52,54,61,63,70],{"type":27,"value":53},"Up for consideration, aside from Vue, are two similar frameworks: the dominant ",{"type":22,"tag":30,"props":55,"children":58},{"href":56,"rel":57},"https://react.dev/",[34],[59],{"type":27,"value":60},"React.js",{"type":27,"value":62},", and the minimalist up-and-comer ",{"type":22,"tag":30,"props":64,"children":67},{"href":65,"rel":66},"https://svelte.dev/",[34],[68],{"type":27,"value":69},"Svelte",{"type":27,"value":71},".",{"type":22,"tag":23,"props":73,"children":74},{},[75,77,84,86,93],{"type":27,"value":76},"React, the original declarative component framework, is now over 10 years old, and is the most popular frontend framework by quite a large margin. It describes itself modestly as \"a JavaScript library for building user interfaces\" and, like Vue, it uses a runtime ",{"type":22,"tag":30,"props":78,"children":81},{"href":79,"rel":80},"https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features#rendering_elements",[34],[82],{"type":27,"value":83},"Virtual DOM",{"type":27,"value":85}," system to manage component updates. Components are defined in ",{"type":22,"tag":30,"props":87,"children":90},{"href":88,"rel":89},"https://react.dev/learn/writing-markup-with-jsx",[34],[91],{"type":27,"value":92},"JSX",{"type":27,"value":94},", a syntax which mixes JavaScript and HTML.",{"type":22,"tag":23,"props":96,"children":97},{},[98],{"type":27,"value":99},"Svelte distinguishes itself by providing a similar declarative component development experience to React and Vue, but without a Virtual DOM runtime; it instead outputs code to directly modify the DOM. This is intended to create extremely lean and performant pages while still providing the same capabilities as a Virtual DOM based framework. It describes itself somewhat less modestly as \"cybernetically enhanced web apps\" (formerly \"the magical disappearing UI framework\"), and is the youngest of the three under consideration.",{"type":22,"tag":23,"props":101,"children":102},{},[103],{"type":27,"value":104},"Vue bills itself as \"the progressive JavaScript framework\", and was initially released a year after React. It too is based on a Virtual DOM runtime but, like Svelte (and unlike React), it typically uses a template system for rendering components rather than JSX. It aims to offer the same capabilities as frameworks like React, but in a smaller and incrementally-adoptable package.",{"type":22,"tag":23,"props":106,"children":107},{},[108],{"type":27,"value":109},"We'll be comparing these frameworks in terms of:",{"type":22,"tag":111,"props":112,"children":113},"ul",{},[114,120,125,130,135,140,145,150],{"type":22,"tag":115,"props":116,"children":117},"li",{},[118],{"type":27,"value":119},"Complexity for developers",{"type":22,"tag":115,"props":121,"children":122},{},[123],{"type":27,"value":124},"Performance",{"type":22,"tag":115,"props":126,"children":127},{},[128],{"type":27,"value":129},"Ecosystem",{"type":22,"tag":115,"props":131,"children":132},{},[133],{"type":27,"value":134},"Features",{"type":22,"tag":115,"props":136,"children":137},{},[138],{"type":27,"value":139},"Small-scale viability",{"type":22,"tag":115,"props":141,"children":142},{},[143],{"type":27,"value":144},"Retrofit and migration viability",{"type":22,"tag":115,"props":146,"children":147},{},[148],{"type":27,"value":149},"Large-scale viability",{"type":22,"tag":115,"props":151,"children":152},{},[153],{"type":27,"value":154},"TypeScript Support",{"type":22,"tag":23,"props":156,"children":157},{},[158,160,167,169,176,178,185],{"type":27,"value":159},"Before we start however, a quick nomenclature distinction needs to be made between \"framework\", meaning React/Svelte/Vue, and \"meta-framework\", meaning an expansive collection of tooling built around a given framework. Examples of meta-frameworks would be ",{"type":22,"tag":30,"props":161,"children":164},{"href":162,"rel":163},"https://nextjs.org/",[34],[165],{"type":27,"value":166},"Next.js",{"type":27,"value":168}," for React, ",{"type":22,"tag":30,"props":170,"children":173},{"href":171,"rel":172},"https://nuxt.com/",[34],[174],{"type":27,"value":175},"Nuxt",{"type":27,"value":177}," for Vue, and ",{"type":22,"tag":30,"props":179,"children":182},{"href":180,"rel":181},"https://kit.svelte.dev/",[34],[183],{"type":27,"value":184},"SvelteKit",{"type":27,"value":186}," for Svelte. Each of them provide features like code-splitting, server-side rendering, link pre-fetching, a prescribed project structure, and many other things that are typically needed in larger projects. We won't be digging into these meta-frameworks in detail, mainly because there's a lot of ground to cover there and they offer largely the same types of functionality, but we won't be ignoring them either; that's because these frameworks themselves are quite narrow in scope, so it's common in practice to need to lean on the capabilities of a meta-framework in order to build complex Web applications.",{"type":22,"tag":23,"props":188,"children":189},{},[190],{"type":27,"value":191},"With that out of the way, let's start by looking at ease of use for developers.",{"type":22,"tag":193,"props":194,"children":196},"h2",{"id":195},"complexity-for-developers",[197],{"type":27,"value":198},"Complexity for Developers",{"type":22,"tag":23,"props":200,"children":201},{},[202],{"type":27,"value":203},"Ultimately, frontend frameworks have a pretty narrow scope: they help developers to organize their components, and then put HTML and CSS on the page. Consequently, it's a shame if valuable developer brainpower gets diverted to managing the complexity of a frontend framework, at the expense of delivering more value or of minimizing technical debt.",{"type":22,"tag":23,"props":205,"children":206},{},[207],{"type":27,"value":208},"Svelte, by design, is the standout winner in terms of remaining simple; it provides templated components and basic state management, defers anything fancier than that to a meta-framework like SvelteKit, and otherwise largely stays out of the developer's way. The documentation is concise, focused, and easy for a new developer to take in. That said, I find this simplicity to be a double-edged sword: by having such a narrow focus, it means that for nontrivial projects you'll almost certainly be using it in conjunction with SvelteKit or complementary libraries, at which point it's... not all that much simpler than the competition.",{"type":22,"tag":23,"props":210,"children":211},{},[212],{"type":27,"value":213},"Vue is a step up in complexity compared to Svelte, adding multiple styles for writing components (Options API vs Composition API, templates vs JSX) and a number of bells and whistles that are only possible in a framework that uses a runtime and a Virtual DOM rendering system. The multitude of ways of doing things is arguably more of a historical problem than it is a lasting complexity, since the Composition API is the clear choice going forward. What's nice about Vue is that its features are progressive: you can learn the very basics and be able to write good, idiomatic Vue code, and then introduce larger concepts as you go along.",{"type":22,"tag":23,"props":215,"children":216},{},[217,219,226,228,235,237,244],{"type":27,"value":218},"React suffers, I think, from being the first Virtual DOM component library on the scene. Its original reactivity model, for example, caused components to completely re-render much more often than necessary, and so the framework introduced ",{"type":22,"tag":30,"props":220,"children":223},{"href":221,"rel":222},"https://legacy.reactjs.org/docs/hooks-intro.html",[34],[224],{"type":27,"value":225},"Hooks",{"type":27,"value":227}," in order to offer better control and, consequently, better performance. Hooks are difficult to get right, however (",{"type":22,"tag":30,"props":229,"children":232},{"href":230,"rel":231},"https://adevnadia.medium.com/how-to-usememo-and-usecallback-you-can-remove-most-of-them-b8ef01b2020d",[34],[233],{"type":27,"value":234},"Example 1",{"type":27,"value":236},", ",{"type":22,"tag":30,"props":238,"children":241},{"href":239,"rel":240},"https://adevnadia.medium.com/react-re-renders-guide-preventing-unnecessary-re-renders-8a3d2acbdba3",[34],[242],{"type":27,"value":243},"Example 2",{"type":27,"value":245},"), and Vue and Svelte offer the same or better reactive state management without the same level of manual management. It's a similar story across much of React's APIs: through being by far the most popular framework, it has grown to have a very wide range of capabilities, but this also greatly increases the scope of what developers need to learn in order to be productive React developers.",{"type":22,"tag":193,"props":247,"children":249},{"id":248},"performance",[250],{"type":27,"value":124},{"type":22,"tag":23,"props":252,"children":253},{},[254],{"type":27,"value":255},"Performance is critical for Web apps, and frameworks achieve good performance in a number of ways, but primarily by minimizing the size of bundled scripts and by reducing the overhead of rendering components on the page.",{"type":22,"tag":23,"props":257,"children":258},{},[259],{"type":27,"value":260},"Comparing the performance of these frameworks is nuanced, because of the way Svelte's design differs from React's and Vue's. The other two each include a runtime in the page which, among other things, updates the document using Virtual DOM diffing: the runtime compares the DOM to the Virtual DOM to identify which changes have been made, and then surgically applies those changes to the DOM. Svelte on the other hand, for each place where a component could make changes to the DOM, introduces a snippet of script which applies precisely that one change, much like you might do by hand if you weren't using a component framework. This avoids the potentially-costly step of diffing the DOM in order to identify changes.",{"type":22,"tag":23,"props":262,"children":263},{},[264,266,272,274,281,282,289],{"type":27,"value":265},"As far as bundle size goes, this means that there is a break-even point between Svelte and React/Vue. A Svelte project will initially have a near-zero bundle size because there is no framework runtime to include, but on a per-component basis, it adds ",{"type":22,"tag":267,"props":268,"children":269},"em",{},[270],{"type":27,"value":271},"more",{"type":27,"value":273}," script than a Virtual DOM based framework, so for larger projects, Svelte's bundle size advantages will diminish or even reverse (",{"type":22,"tag":30,"props":275,"children":278},{"href":276,"rel":277},"https://github.com/halfnelson/svelte-it-will-scale",[34],[279],{"type":27,"value":280},"Analysis 1",{"type":27,"value":236},{"type":22,"tag":30,"props":283,"children":286},{"href":284,"rel":285},"https://github.com/yyx990803/vue-svelte-size-analysis",[34],[287],{"type":27,"value":288},"Analysis 2",{"type":27,"value":290},").",{"type":22,"tag":23,"props":292,"children":293},{},[294,296,303],{"type":27,"value":295},"Even if a large Svelte project ships more bytes of script, however, it avoids the overhead of Virtual DOM diffing, which is an advantage, and there are several tricks that can be utilized in order to compensate for having to download more script (like code-splitting or ",{"type":22,"tag":30,"props":297,"children":300},{"href":298,"rel":299},"https://kit.svelte.dev/docs/link-options#data-sveltekit-preload-data",[34],[301],{"type":27,"value":302},"link preloading",{"type":27,"value":304},"). So overall, it's very hard to say with certainty whether a given project would see a meaningful performance difference between these three frameworks.",{"type":22,"tag":23,"props":306,"children":307},{},[308],{"type":27,"value":309},"There are a couple of ways to get a rough idea, however, the first being to look at benchmarks:",{"type":22,"tag":23,"props":311,"children":312},{},[313],{"type":22,"tag":314,"props":315,"children":318},"img",{"alt":316,"src":317},"React, Svelte and Vue benchmarks. Source: https://krausest.github.io/js-framework-benchmark/current.html","/phendry/2023-11-06/img/Benchmarks.png",[],{"type":22,"tag":23,"props":320,"children":321},{},[322],{"type":27,"value":323},"Despite Svelte's claimed performance benefits, it doesn't perform meaningfully better than Vue, whereas React lags a little behind.",{"type":22,"tag":23,"props":325,"children":326},{},[327,329,336],{"type":27,"value":328},"A more realistic comparison is the aptly-named ",{"type":22,"tag":30,"props":330,"children":333},{"href":331,"rel":332},"https://medium.com/dailyjs/a-realworld-comparison-of-front-end-frameworks-2020-4e50655fe4c1",[34],[334],{"type":27,"value":335},"Real World demo application",{"type":27,"value":337},", in which Svelte performs great, Vue performs almost as well, and React again lags a little behind.",{"type":22,"tag":23,"props":339,"children":340},{},[341,343,348,350,355],{"type":27,"value":342},"The big thing to remember with these comparisons however is that these are ",{"type":22,"tag":267,"props":344,"children":345},{},[346],{"type":27,"value":347},"meticulously-crafted",{"type":27,"value":349}," submissions meant to get the best possible score; real-world performance will depend much more on how ",{"type":22,"tag":267,"props":351,"children":352},{},[353],{"type":27,"value":354},"easy",{"type":27,"value":356}," the framework makes it for time-starved developers to write performant code (see the \"Complexity for Developers\" section above). With its manually-managed hooks, React makes this considerably harder than the others, and consequently it has a reputation for making it easy for developers to inadvertently create performance problems.",{"type":22,"tag":23,"props":358,"children":359},{},[360],{"type":27,"value":361},"Overall, for mid- to large-sized Web apps, I don't think there is a strong performance reason to favour one framework over the others. Svelte may be little faster and React a little slower, but all three frameworks can be extremely fast and responsive.",{"type":22,"tag":193,"props":363,"children":365},{"id":364},"ecosystem",[366],{"type":27,"value":129},{"type":22,"tag":23,"props":368,"children":369},{},[370],{"type":27,"value":371},"No nontrivial modern Web application is created without leveraging many additional libraries to manage tasks like client-side routing, state management, authentication, or UI components, and many of these libraries integrate closely with the framework. Consequently it's a liability to use a framework which has a small ecosystem of third-party libraries to lean on, and a boon to use a framework with a rich ecosystem any many well-supported options.",{"type":22,"tag":23,"props":373,"children":374},{},[375],{"type":27,"value":376},"There's also the issue of talent, in that it is much easier to find developers with experience with a popular tool than a less popular one. The importance of this depends on how easy it is for an experienced developer to learn (see \"Complexity for Developers\" above).",{"type":22,"tag":23,"props":378,"children":379},{},[380,382,389],{"type":27,"value":381},"React has by far the biggest ecosystem of any frontend framework. In a recent ",{"type":22,"tag":30,"props":383,"children":386},{"href":384,"rel":385},"https://survey.stackoverflow.co/2023/#most-popular-technologies-webframe-prof",[34],[387],{"type":27,"value":388},"Stack Overflow Developer Survey",{"type":27,"value":390},", it dwarfs Vue and Svelte in usage among professional developers:",{"type":22,"tag":23,"props":392,"children":393},{},[394],{"type":22,"tag":314,"props":395,"children":398},{"alt":396,"src":397},"Reported usage of Web frameworks among professional developers. Source: Stack Overflow","/phendry/2023-11-06/img/Popularity.png",[],{"type":22,"tag":23,"props":400,"children":401},{},[402],{"type":27,"value":403},"For a tech lead looking to make a practical choice of framework, this really makes React the default choice if all else is equal, in my mind. If any of these frameworks could meet your project's needs, then why wouldn't you select the one that makes it easiest to fill out a team of experienced developers, and the easiest to find whatever third-party libraries your team needs in order to be productive? The further you stray from the well-travelled path, the more challenging it becomes, and so the advantages of a less-popular choice need to be quite substantial.",{"type":22,"tag":23,"props":405,"children":406},{},[407],{"type":27,"value":408},"To their credit, though, none of these frameworks have an ecosystem that feels lacking, even Svelte. Each has one or more popular meta-frameworks (Next.js for React, Nuxt for Vue, SvelteKit for Svelte) which provide the advanced and server-integrated features that modern Web apps increasingly demand, and a wide range of well-supported libraries.",{"type":22,"tag":193,"props":410,"children":412},{"id":411},"features",[413],{"type":27,"value":134},{"type":22,"tag":23,"props":415,"children":416},{},[417],{"type":27,"value":418},"While each framework's corresponding meta-frameworks compete on all sorts of advanced features, honestly... for typical projects, there are few meaningful differences between the capabilities of these frameworks. Without going into great detail about every feature they offer and how they compare, the bottom line is that React, Svelte and Vue are all capable of building feature-rich and complex Web applications, and they all offer great TypeScript support, editor plugins, and tooling.",{"type":22,"tag":23,"props":420,"children":421},{},[422,424,429],{"type":27,"value":423},"There are two notable exceptions worth mentioning. The first, and it's a small one, is that Svelte lacks \"render function\" capabilities, i.e. the ability to skip the template system and write a custom function whose output is the HTML content of a component. This limitation is fundamental to Svelte's runtime-less design. Having personally a great deal of experience working within template systems, I would say that in practice this is typically only a limitation for plug-in libraries seeking to be especially generic and flexible; I can't recall ever ",{"type":22,"tag":267,"props":425,"children":426},{},[427],{"type":27,"value":428},"needing",{"type":27,"value":430}," a render function in order to meet a project goal. The limitation is present however, and likely affects the capabilities of third-party packages.",{"type":22,"tag":23,"props":432,"children":433},{},[434,436,443,445,451,453,460],{"type":27,"value":435},"The second and more important difference is that React has native Android/iOS capabilities via ",{"type":22,"tag":30,"props":437,"children":440},{"href":438,"rel":439},"https://reactnative.dev/",[34],[441],{"type":27,"value":442},"React Native",{"type":27,"value":444}," that are unmatched by NativeScript-based tools for ",{"type":22,"tag":30,"props":446,"children":449},{"href":447,"rel":448},"https://svelte-native.technology",[34],[450],{"type":27,"value":69},{"type":27,"value":452}," and for ",{"type":22,"tag":30,"props":454,"children":457},{"href":455,"rel":456},"https://nativescript-vue.org/",[34],[458],{"type":27,"value":459},"Vue",{"type":27,"value":461},", in terms of production-readiness. If it's valuable for your project to be able to build a native mobile app using a Web developer skillset rather than native Android or iOS development skills, then React is the framework which can deliver that.",{"type":22,"tag":193,"props":463,"children":465},{"id":464},"small-scale-viability",[466],{"type":27,"value":467},"Small-scale Viability",{"type":22,"tag":23,"props":469,"children":470},{},[471],{"type":27,"value":472},"At Art+Logic we work on projects ranging from very small to reasonably large, so there is a lot of value in working with a frontend framework that is suitable for the full gamut of project sizes. This should arguably be a consideration for most teams, because most small projects could find themselves needing to scale up, and most large projects end up having small complementary projects like internal tools.",{"type":22,"tag":23,"props":474,"children":475},{},[476,478,494],{"type":27,"value":477},"One of Vue's capabilities that makes it uniquely suited for small projects is that ",{"type":22,"tag":30,"props":479,"children":482},{"href":480,"rel":481},"https://vuejs.org/guide/quick-start.html#using-vue-from-cdn",[34],[483,485,492],{"type":27,"value":484},"it can be used via a CDN ",{"type":22,"tag":486,"props":487,"children":489},"code",{"className":488},[],[490],{"type":27,"value":491},"\u003Cscript>",{"type":27,"value":493}," tag without any build system whatsoever",{"type":27,"value":495},". This means you can inject Vue onto a static page to introduce dynamic functionality to just a portion of it, or easily inject Vue into a project built using a different framework altogether. React and Svelte on the other hand are decidedly geared towards building a project within the bounds of their tooling ecosystems, limiting their small-scale flexibility.",{"type":22,"tag":23,"props":497,"children":498},{},[499],{"type":27,"value":500},"For a green-field project however, Svelte's wafer-thin bundle sizes make it a great choice for small-scale or low-complexity projects. React, having the heftiest runtime of the bunch, is perhaps less advisable for a small project, but still is perfectly serviceable.",{"type":22,"tag":193,"props":502,"children":504},{"id":503},"retrofit-and-migration-viability",[505],{"type":27,"value":506},"Retrofit and Migration Viability",{"type":22,"tag":23,"props":508,"children":509},{},[510],{"type":27,"value":511},"With the frustratingly-fast pace of evolution in frontend Web development tooling over the last couple decades, many teams are left with older projects whose choice of framework has become a hindrance to continued development. A full re-write is a risky proposition, so it's powerful to have the ability to implement a gradual extension or migration in a modern framework.",{"type":22,"tag":23,"props":513,"children":514},{},[515],{"type":27,"value":516},"Vue again is uniquely positioned in this regard: being the self-described \"progressive framework\", it is much more practical than the others to integrate into an existing project built with another framework. In React or Svelte, I would likely not attempt to anything more granular than a page-by-page conversion, but with Vue, it's easy to embed Vue within an existing codebase and build system. I once had occasion to implement a Backbone.js component which renders a Vue component, which in turns renders a Backbone.js component; you'll have to trust me that there was a good reason for doing so!",{"type":22,"tag":193,"props":518,"children":520},{"id":519},"large-scale-viability",[521],{"type":27,"value":522},"Large-scale Viability",{"type":22,"tag":23,"props":524,"children":525},{},[526],{"type":27,"value":527},"All three of these frameworks have powerful build systems and meta-frameworks which enable all sorts of advanced performance-related features (such as prefetching links, server-side rendering, code-splitting, etc). As such, they're all able to scale up and competently handle large, complex projects.",{"type":22,"tag":23,"props":529,"children":530},{},[531],{"type":27,"value":532},"I would personally have some hesitation with using Svelte on a large project however, this being for ecosystem reasons rather than technical ones. A large project is more likely to be difficult to migrate toward alternative technologies in the future, and more likely to require a wide variety of third-party libraries, both of which arguably make conservative technology choices more appropriate. In this case, that would mean choosing a framework with a larger talent pool and third-party ecosystem (i.e. React, or to a lesser extent Vue).",{"type":22,"tag":193,"props":534,"children":536},{"id":535},"typescript-support",[537],{"type":27,"value":154},{"type":22,"tag":23,"props":539,"children":540},{},[541],{"type":27,"value":542},"There's not much to be said here since all three frameworks have great TypeScript support, but it's worth highlighting that fact, since TypeScript is an excellent language choice for developing a robust codebase. It would be difficult to recommend a framework in 2024 which does not provide TypeScript as a first-class option.",{"type":22,"tag":193,"props":544,"children":546},{"id":545},"summary-and-conclusion",[547],{"type":27,"value":548},"Summary and Conclusion",{"type":22,"tag":23,"props":550,"children":551},{},[552],{"type":27,"value":553},"To grossly oversimply the above considerations into a comparison chart:",{"type":22,"tag":555,"props":556,"children":557},"table",{},[558,584],{"type":22,"tag":559,"props":560,"children":561},"thead",{},[562],{"type":22,"tag":563,"props":564,"children":565},"tr",{},[566,570,576,580],{"type":22,"tag":567,"props":568,"children":569},"th",{},[],{"type":22,"tag":567,"props":571,"children":573},{"align":572},"center",[574],{"type":27,"value":575},"React",{"type":22,"tag":567,"props":577,"children":578},{"align":572},[579],{"type":27,"value":69},{"type":22,"tag":567,"props":581,"children":582},{"align":572},[583],{"type":27,"value":459},{"type":22,"tag":585,"props":586,"children":587},"tbody",{},[588,611,630,649,668,687,706,725],{"type":22,"tag":563,"props":589,"children":590},{},[591,596,601,606],{"type":22,"tag":592,"props":593,"children":594},"td",{},[595],{"type":27,"value":119},{"type":22,"tag":592,"props":597,"children":598},{"align":572},[599],{"type":27,"value":600},"🔴",{"type":22,"tag":592,"props":602,"children":603},{"align":572},[604],{"type":27,"value":605},"🟢",{"type":22,"tag":592,"props":607,"children":608},{"align":572},[609],{"type":27,"value":610},"🟡",{"type":22,"tag":563,"props":612,"children":613},{},[614,618,622,626],{"type":22,"tag":592,"props":615,"children":616},{},[617],{"type":27,"value":124},{"type":22,"tag":592,"props":619,"children":620},{"align":572},[621],{"type":27,"value":610},{"type":22,"tag":592,"props":623,"children":624},{"align":572},[625],{"type":27,"value":605},{"type":22,"tag":592,"props":627,"children":628},{"align":572},[629],{"type":27,"value":605},{"type":22,"tag":563,"props":631,"children":632},{},[633,637,641,645],{"type":22,"tag":592,"props":634,"children":635},{},[636],{"type":27,"value":129},{"type":22,"tag":592,"props":638,"children":639},{"align":572},[640],{"type":27,"value":605},{"type":22,"tag":592,"props":642,"children":643},{"align":572},[644],{"type":27,"value":600},{"type":22,"tag":592,"props":646,"children":647},{"align":572},[648],{"type":27,"value":610},{"type":22,"tag":563,"props":650,"children":651},{},[652,656,660,664],{"type":22,"tag":592,"props":653,"children":654},{},[655],{"type":27,"value":134},{"type":22,"tag":592,"props":657,"children":658},{"align":572},[659],{"type":27,"value":605},{"type":22,"tag":592,"props":661,"children":662},{"align":572},[663],{"type":27,"value":605},{"type":22,"tag":592,"props":665,"children":666},{"align":572},[667],{"type":27,"value":605},{"type":22,"tag":563,"props":669,"children":670},{},[671,675,679,683],{"type":22,"tag":592,"props":672,"children":673},{},[674],{"type":27,"value":139},{"type":22,"tag":592,"props":676,"children":677},{"align":572},[678],{"type":27,"value":610},{"type":22,"tag":592,"props":680,"children":681},{"align":572},[682],{"type":27,"value":605},{"type":22,"tag":592,"props":684,"children":685},{"align":572},[686],{"type":27,"value":605},{"type":22,"tag":563,"props":688,"children":689},{},[690,694,698,702],{"type":22,"tag":592,"props":691,"children":692},{},[693],{"type":27,"value":144},{"type":22,"tag":592,"props":695,"children":696},{"align":572},[697],{"type":27,"value":610},{"type":22,"tag":592,"props":699,"children":700},{"align":572},[701],{"type":27,"value":610},{"type":22,"tag":592,"props":703,"children":704},{"align":572},[705],{"type":27,"value":605},{"type":22,"tag":563,"props":707,"children":708},{},[709,713,717,721],{"type":22,"tag":592,"props":710,"children":711},{},[712],{"type":27,"value":149},{"type":22,"tag":592,"props":714,"children":715},{"align":572},[716],{"type":27,"value":605},{"type":22,"tag":592,"props":718,"children":719},{"align":572},[720],{"type":27,"value":610},{"type":22,"tag":592,"props":722,"children":723},{"align":572},[724],{"type":27,"value":605},{"type":22,"tag":563,"props":726,"children":727},{},[728,732,736,740],{"type":22,"tag":592,"props":729,"children":730},{},[731],{"type":27,"value":154},{"type":22,"tag":592,"props":733,"children":734},{"align":572},[735],{"type":27,"value":605},{"type":22,"tag":592,"props":737,"children":738},{"align":572},[739],{"type":27,"value":605},{"type":22,"tag":592,"props":741,"children":742},{"align":572},[743],{"type":27,"value":605},{"type":22,"tag":23,"props":745,"children":746},{},[747],{"type":27,"value":748},"In general, React, Svelte and Vue are all excellent and production-ready frameworks, and a strong case can be made for any one of them. React offers a drastically larger ecosystem and talent pool in addition to native mobile capabilities, though it suffers from a higher complexity for developers and somewhat less reliable performance. Svelte offers cutting-edge performance, but its ecosystem is less mature. Vue sits somewhere in the middle, and offers the greatest versatility for a wide variety of project scenarios.",{"type":22,"tag":23,"props":750,"children":751},{},[752],{"type":27,"value":753},"At Art+Logic, where \"coding the 'impossible'\" can take any number of forms, versatility continues to be one of the most valuable things we look for in a framework, and Vue continues to provide the most versatility while still ticking all the other boxes we expect it to. As such, it continues to be our preferred choice for frontend Web development.",{"title":8,"searchDepth":755,"depth":755,"links":756},3,[757,759,760,761,762,763,764,765,766],{"id":195,"depth":758,"text":198},2,{"id":248,"depth":758,"text":124},{"id":364,"depth":758,"text":129},{"id":411,"depth":758,"text":134},{"id":464,"depth":758,"text":467},{"id":503,"depth":758,"text":506},{"id":519,"depth":758,"text":522},{"id":535,"depth":758,"text":154},{"id":545,"depth":758,"text":548},"markdown","content:phendry:2023-11-06:FrontendFrameworksIn2024.md","content","phendry/2023-11-06/FrontendFrameworksIn2024.md","phendry/2023-11-06/FrontendFrameworksIn2024","md",{"user":774,"name":775},"phendry","Paul Hendry",{"_path":777,"_dir":778,"_draft":7,"_partial":7,"_locale":8,"title":779,"description":780,"image":781,"tags":782,"excerpt":780,"publishDate":783,"body":784,"_type":767,"_id":2474,"_source":769,"_file":2475,"_stem":2476,"_extension":772,"author":2477},"/phendry/2023-04-02/semantichtml","2023-04-02","Don't Give Up on Semantic HTML","Since the early days of the Web, there has been tension between the ideal of \"semantic HTML\" and the practical reality of designing complex page layouts, which often could not be achieved without inserting style concerns into the document. More recently, frameworks like Tailwind CSS have emerged which challenge the very idea that semantic HTML is an ideal to strive for, and which commit to thoroughly embedding style concerns into HTML documents. With modern CSS features however, semantic HTML is more achievable than ever, and I do think it remains a worthy goal.","/phendry/2023-04-02/img/Don't Give Up on Semantic HTML.png",[12],"2024-03-01",{"type":19,"children":785,"toc":2458},[786,800,827,835,841,846,860,872,885,1176,1181,1360,1380,1386,1391,1396,1408,1413,1419,1424,1429,1457,1469,1475,1480,1500,1545,1566,1578,1600,1605,1746,1789,1803,1811,1816,1821,1957,2002,2010,2015,2027,2032,2312,2361,2366,2384,2389,2397,2425,2431,2443,2448,2453],{"type":22,"tag":23,"props":787,"children":788},{},[789,791,798],{"type":27,"value":790},"Since the early days of the Web, there has been tension between the ideal of \"semantic HTML\" and the practical reality of designing complex page layouts, which often could not be achieved without inserting style concerns into the document. More recently, frameworks like ",{"type":22,"tag":30,"props":792,"children":795},{"href":793,"rel":794},"https://tailwindcss.com/",[34],[796],{"type":27,"value":797},"Tailwind CSS",{"type":27,"value":799}," have emerged which challenge the very idea that semantic HTML is an ideal to strive for, and which commit to thoroughly embedding style concerns into HTML documents. With modern CSS features however, semantic HTML is more achievable than ever, and I do think it remains a worthy goal.",{"type":22,"tag":23,"props":801,"children":802},{},[803,805,812,814,825],{"type":27,"value":804},"To demonstrate some modern techniques for writing semantic HTML, I've built a ",{"type":22,"tag":30,"props":806,"children":809},{"href":807,"rel":808},"https://github.com/artandlogic/semantic-html-demo/",[34],[810],{"type":27,"value":811},"demo project",{"type":27,"value":813}," which can be viewed at ",{"type":22,"tag":30,"props":815,"children":818},{"href":816,"rel":817},"https://artandlogic.github.io/semantic-html-demo/",[34],[819],{"type":22,"tag":486,"props":820,"children":822},{"className":821},[],[823],{"type":27,"value":824},"artandlogic.github.io/semantic-html-demo/",{"type":27,"value":826},". It consists of a single, unchanging HTML document, with a dropdown to select between three dramatically-different page styles. I'll be referencing it later on.",{"type":22,"tag":23,"props":828,"children":829},{},[830],{"type":22,"tag":314,"props":831,"children":834},{"alt":832,"src":833},"A preview of the demo page styles","/phendry/2023-04-02/img/demo_preview.png",[],{"type":22,"tag":193,"props":836,"children":838},{"id":837},"what-is-semantic-html",[839],{"type":27,"value":840},"What is Semantic HTML?",{"type":22,"tag":23,"props":842,"children":843},{},[844],{"type":27,"value":845},"Simply put, we'll define semantic HTML as HTML which",{"type":22,"tag":847,"props":848,"children":849},"ol",{},[850,855],{"type":22,"tag":115,"props":851,"children":852},{},[853],{"type":27,"value":854},"uses relevant HTML tags to describe the meaning of page elements, and",{"type":22,"tag":115,"props":856,"children":857},{},[858],{"type":27,"value":859},"represents only the structure and content of the page, separate from any particular presentation of that content.",{"type":22,"tag":23,"props":861,"children":862},{},[863,865,870],{"type":27,"value":864},"I say \"we\" because some might consider that this stretches the definition to include a separate concept, namely the separation of concerns between HTML (for content) and CSS (for presentation). But in my mind, effectively representing the meaning of the content also means ",{"type":22,"tag":267,"props":866,"children":867},{},[868],{"type":27,"value":869},"not",{"type":27,"value":871}," obfuscating it with presentational concerns, so for the purposes of this article we'll be calling it all \"semantic HTML\".",{"type":22,"tag":23,"props":873,"children":874},{},[875,877,883],{"type":27,"value":876},"For example, a page's content could be defined in HTML with ",{"type":22,"tag":486,"props":878,"children":880},{"className":879},[],[881],{"type":27,"value":882},"\u003Cdiv>",{"type":27,"value":884}," elements:",{"type":22,"tag":886,"props":887,"children":891},"pre",{"className":888,"code":889,"language":890,"meta":8,"style":8},"language-html shiki shiki-themes github-light github-dark","\u003Cbody>\n    \u003Cdiv class=\"navbar\">\n        \u003Cdiv class=\"float-to-left\">\n            \u003Cimg class=\"logo\">\n        \u003C/div>\n        \u003Cdiv class=\"nav\">Navigation links\u003C/div>\n    \u003C/div>\n    \u003Cdiv class=\"main\">Main page content\u003C/div>\n    \u003Cdiv class=\"footer\">Page footer\u003C/div>\n\u003C/body>\n","html",[892],{"type":22,"tag":486,"props":893,"children":894},{"__ignoreMap":8},[895,918,952,981,1011,1028,1066,1083,1121,1159],{"type":22,"tag":896,"props":897,"children":900},"span",{"class":898,"line":899},"line",1,[901,907,913],{"type":22,"tag":896,"props":902,"children":904},{"style":903},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[905],{"type":27,"value":906},"\u003C",{"type":22,"tag":896,"props":908,"children":910},{"style":909},"--shiki-default:#22863A;--shiki-dark:#85E89D",[911],{"type":27,"value":912},"body",{"type":22,"tag":896,"props":914,"children":915},{"style":903},[916],{"type":27,"value":917},">\n",{"type":22,"tag":896,"props":919,"children":920},{"class":898,"line":758},[921,926,931,937,942,948],{"type":22,"tag":896,"props":922,"children":923},{"style":903},[924],{"type":27,"value":925},"    \u003C",{"type":22,"tag":896,"props":927,"children":928},{"style":909},[929],{"type":27,"value":930},"div",{"type":22,"tag":896,"props":932,"children":934},{"style":933},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[935],{"type":27,"value":936}," class",{"type":22,"tag":896,"props":938,"children":939},{"style":903},[940],{"type":27,"value":941},"=",{"type":22,"tag":896,"props":943,"children":945},{"style":944},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[946],{"type":27,"value":947},"\"navbar\"",{"type":22,"tag":896,"props":949,"children":950},{"style":903},[951],{"type":27,"value":917},{"type":22,"tag":896,"props":953,"children":954},{"class":898,"line":755},[955,960,964,968,972,977],{"type":22,"tag":896,"props":956,"children":957},{"style":903},[958],{"type":27,"value":959},"        \u003C",{"type":22,"tag":896,"props":961,"children":962},{"style":909},[963],{"type":27,"value":930},{"type":22,"tag":896,"props":965,"children":966},{"style":933},[967],{"type":27,"value":936},{"type":22,"tag":896,"props":969,"children":970},{"style":903},[971],{"type":27,"value":941},{"type":22,"tag":896,"props":973,"children":974},{"style":944},[975],{"type":27,"value":976},"\"float-to-left\"",{"type":22,"tag":896,"props":978,"children":979},{"style":903},[980],{"type":27,"value":917},{"type":22,"tag":896,"props":982,"children":984},{"class":898,"line":983},4,[985,990,994,998,1002,1007],{"type":22,"tag":896,"props":986,"children":987},{"style":903},[988],{"type":27,"value":989},"            \u003C",{"type":22,"tag":896,"props":991,"children":992},{"style":909},[993],{"type":27,"value":314},{"type":22,"tag":896,"props":995,"children":996},{"style":933},[997],{"type":27,"value":936},{"type":22,"tag":896,"props":999,"children":1000},{"style":903},[1001],{"type":27,"value":941},{"type":22,"tag":896,"props":1003,"children":1004},{"style":944},[1005],{"type":27,"value":1006},"\"logo\"",{"type":22,"tag":896,"props":1008,"children":1009},{"style":903},[1010],{"type":27,"value":917},{"type":22,"tag":896,"props":1012,"children":1014},{"class":898,"line":1013},5,[1015,1020,1024],{"type":22,"tag":896,"props":1016,"children":1017},{"style":903},[1018],{"type":27,"value":1019},"        \u003C/",{"type":22,"tag":896,"props":1021,"children":1022},{"style":909},[1023],{"type":27,"value":930},{"type":22,"tag":896,"props":1025,"children":1026},{"style":903},[1027],{"type":27,"value":917},{"type":22,"tag":896,"props":1029,"children":1031},{"class":898,"line":1030},6,[1032,1036,1040,1044,1048,1053,1058,1062],{"type":22,"tag":896,"props":1033,"children":1034},{"style":903},[1035],{"type":27,"value":959},{"type":22,"tag":896,"props":1037,"children":1038},{"style":909},[1039],{"type":27,"value":930},{"type":22,"tag":896,"props":1041,"children":1042},{"style":933},[1043],{"type":27,"value":936},{"type":22,"tag":896,"props":1045,"children":1046},{"style":903},[1047],{"type":27,"value":941},{"type":22,"tag":896,"props":1049,"children":1050},{"style":944},[1051],{"type":27,"value":1052},"\"nav\"",{"type":22,"tag":896,"props":1054,"children":1055},{"style":903},[1056],{"type":27,"value":1057},">Navigation links\u003C/",{"type":22,"tag":896,"props":1059,"children":1060},{"style":909},[1061],{"type":27,"value":930},{"type":22,"tag":896,"props":1063,"children":1064},{"style":903},[1065],{"type":27,"value":917},{"type":22,"tag":896,"props":1067,"children":1069},{"class":898,"line":1068},7,[1070,1075,1079],{"type":22,"tag":896,"props":1071,"children":1072},{"style":903},[1073],{"type":27,"value":1074},"    \u003C/",{"type":22,"tag":896,"props":1076,"children":1077},{"style":909},[1078],{"type":27,"value":930},{"type":22,"tag":896,"props":1080,"children":1081},{"style":903},[1082],{"type":27,"value":917},{"type":22,"tag":896,"props":1084,"children":1086},{"class":898,"line":1085},8,[1087,1091,1095,1099,1103,1108,1113,1117],{"type":22,"tag":896,"props":1088,"children":1089},{"style":903},[1090],{"type":27,"value":925},{"type":22,"tag":896,"props":1092,"children":1093},{"style":909},[1094],{"type":27,"value":930},{"type":22,"tag":896,"props":1096,"children":1097},{"style":933},[1098],{"type":27,"value":936},{"type":22,"tag":896,"props":1100,"children":1101},{"style":903},[1102],{"type":27,"value":941},{"type":22,"tag":896,"props":1104,"children":1105},{"style":944},[1106],{"type":27,"value":1107},"\"main\"",{"type":22,"tag":896,"props":1109,"children":1110},{"style":903},[1111],{"type":27,"value":1112},">Main page content\u003C/",{"type":22,"tag":896,"props":1114,"children":1115},{"style":909},[1116],{"type":27,"value":930},{"type":22,"tag":896,"props":1118,"children":1119},{"style":903},[1120],{"type":27,"value":917},{"type":22,"tag":896,"props":1122,"children":1124},{"class":898,"line":1123},9,[1125,1129,1133,1137,1141,1146,1151,1155],{"type":22,"tag":896,"props":1126,"children":1127},{"style":903},[1128],{"type":27,"value":925},{"type":22,"tag":896,"props":1130,"children":1131},{"style":909},[1132],{"type":27,"value":930},{"type":22,"tag":896,"props":1134,"children":1135},{"style":933},[1136],{"type":27,"value":936},{"type":22,"tag":896,"props":1138,"children":1139},{"style":903},[1140],{"type":27,"value":941},{"type":22,"tag":896,"props":1142,"children":1143},{"style":944},[1144],{"type":27,"value":1145},"\"footer\"",{"type":22,"tag":896,"props":1147,"children":1148},{"style":903},[1149],{"type":27,"value":1150},">Page footer\u003C/",{"type":22,"tag":896,"props":1152,"children":1153},{"style":909},[1154],{"type":27,"value":930},{"type":22,"tag":896,"props":1156,"children":1157},{"style":903},[1158],{"type":27,"value":917},{"type":22,"tag":896,"props":1160,"children":1162},{"class":898,"line":1161},10,[1163,1168,1172],{"type":22,"tag":896,"props":1164,"children":1165},{"style":903},[1166],{"type":27,"value":1167},"\u003C/",{"type":22,"tag":896,"props":1169,"children":1170},{"style":909},[1171],{"type":27,"value":912},{"type":22,"tag":896,"props":1173,"children":1174},{"style":903},[1175],{"type":27,"value":917},{"type":22,"tag":23,"props":1177,"children":1178},{},[1179],{"type":27,"value":1180},"Or it could be defined in terms of \"semantic\" HTML elements:",{"type":22,"tag":886,"props":1182,"children":1184},{"className":888,"code":1183,"language":890,"meta":8,"style":8},"\u003Cbody>\n    \u003Caside class=\"navbar\">\n        \u003Cimg class=\"logo\">\n        \u003Cnav>Navigation links\u003C/nav>\n    \u003C/aside>\n    \u003Cmain>Main page content\u003C/main>\n    \u003Cfooter>Page footer\u003C/footer>\n\u003C/body>\n",[1185],{"type":22,"tag":486,"props":1186,"children":1187},{"__ignoreMap":8},[1188,1203,1231,1258,1282,1297,1321,1345],{"type":22,"tag":896,"props":1189,"children":1190},{"class":898,"line":899},[1191,1195,1199],{"type":22,"tag":896,"props":1192,"children":1193},{"style":903},[1194],{"type":27,"value":906},{"type":22,"tag":896,"props":1196,"children":1197},{"style":909},[1198],{"type":27,"value":912},{"type":22,"tag":896,"props":1200,"children":1201},{"style":903},[1202],{"type":27,"value":917},{"type":22,"tag":896,"props":1204,"children":1205},{"class":898,"line":758},[1206,1210,1215,1219,1223,1227],{"type":22,"tag":896,"props":1207,"children":1208},{"style":903},[1209],{"type":27,"value":925},{"type":22,"tag":896,"props":1211,"children":1212},{"style":909},[1213],{"type":27,"value":1214},"aside",{"type":22,"tag":896,"props":1216,"children":1217},{"style":933},[1218],{"type":27,"value":936},{"type":22,"tag":896,"props":1220,"children":1221},{"style":903},[1222],{"type":27,"value":941},{"type":22,"tag":896,"props":1224,"children":1225},{"style":944},[1226],{"type":27,"value":947},{"type":22,"tag":896,"props":1228,"children":1229},{"style":903},[1230],{"type":27,"value":917},{"type":22,"tag":896,"props":1232,"children":1233},{"class":898,"line":755},[1234,1238,1242,1246,1250,1254],{"type":22,"tag":896,"props":1235,"children":1236},{"style":903},[1237],{"type":27,"value":959},{"type":22,"tag":896,"props":1239,"children":1240},{"style":909},[1241],{"type":27,"value":314},{"type":22,"tag":896,"props":1243,"children":1244},{"style":933},[1245],{"type":27,"value":936},{"type":22,"tag":896,"props":1247,"children":1248},{"style":903},[1249],{"type":27,"value":941},{"type":22,"tag":896,"props":1251,"children":1252},{"style":944},[1253],{"type":27,"value":1006},{"type":22,"tag":896,"props":1255,"children":1256},{"style":903},[1257],{"type":27,"value":917},{"type":22,"tag":896,"props":1259,"children":1260},{"class":898,"line":983},[1261,1265,1270,1274,1278],{"type":22,"tag":896,"props":1262,"children":1263},{"style":903},[1264],{"type":27,"value":959},{"type":22,"tag":896,"props":1266,"children":1267},{"style":909},[1268],{"type":27,"value":1269},"nav",{"type":22,"tag":896,"props":1271,"children":1272},{"style":903},[1273],{"type":27,"value":1057},{"type":22,"tag":896,"props":1275,"children":1276},{"style":909},[1277],{"type":27,"value":1269},{"type":22,"tag":896,"props":1279,"children":1280},{"style":903},[1281],{"type":27,"value":917},{"type":22,"tag":896,"props":1283,"children":1284},{"class":898,"line":1013},[1285,1289,1293],{"type":22,"tag":896,"props":1286,"children":1287},{"style":903},[1288],{"type":27,"value":1074},{"type":22,"tag":896,"props":1290,"children":1291},{"style":909},[1292],{"type":27,"value":1214},{"type":22,"tag":896,"props":1294,"children":1295},{"style":903},[1296],{"type":27,"value":917},{"type":22,"tag":896,"props":1298,"children":1299},{"class":898,"line":1030},[1300,1304,1309,1313,1317],{"type":22,"tag":896,"props":1301,"children":1302},{"style":903},[1303],{"type":27,"value":925},{"type":22,"tag":896,"props":1305,"children":1306},{"style":909},[1307],{"type":27,"value":1308},"main",{"type":22,"tag":896,"props":1310,"children":1311},{"style":903},[1312],{"type":27,"value":1112},{"type":22,"tag":896,"props":1314,"children":1315},{"style":909},[1316],{"type":27,"value":1308},{"type":22,"tag":896,"props":1318,"children":1319},{"style":903},[1320],{"type":27,"value":917},{"type":22,"tag":896,"props":1322,"children":1323},{"class":898,"line":1068},[1324,1328,1333,1337,1341],{"type":22,"tag":896,"props":1325,"children":1326},{"style":903},[1327],{"type":27,"value":925},{"type":22,"tag":896,"props":1329,"children":1330},{"style":909},[1331],{"type":27,"value":1332},"footer",{"type":22,"tag":896,"props":1334,"children":1335},{"style":903},[1336],{"type":27,"value":1150},{"type":22,"tag":896,"props":1338,"children":1339},{"style":909},[1340],{"type":27,"value":1332},{"type":22,"tag":896,"props":1342,"children":1343},{"style":903},[1344],{"type":27,"value":917},{"type":22,"tag":896,"props":1346,"children":1347},{"class":898,"line":1085},[1348,1352,1356],{"type":22,"tag":896,"props":1349,"children":1350},{"style":903},[1351],{"type":27,"value":1167},{"type":22,"tag":896,"props":1353,"children":1354},{"style":909},[1355],{"type":27,"value":912},{"type":22,"tag":896,"props":1357,"children":1358},{"style":903},[1359],{"type":27,"value":917},{"type":22,"tag":23,"props":1361,"children":1362},{},[1363,1365,1370,1372,1378],{"type":27,"value":1364},"Each could be styled identically through CSS, so the rendered end product is the same, but the semantic HTML document encodes more information; a screen reader for example could identify the main page content and the navigation area. It also drops the ",{"type":22,"tag":486,"props":1366,"children":1368},{"className":1367},[],[1369],{"type":27,"value":882},{"type":27,"value":1371}," around the ",{"type":22,"tag":486,"props":1373,"children":1375},{"className":1374},[],[1376],{"type":27,"value":1377},"\u003Cimg>",{"type":27,"value":1379}," tag that was only used for styling, which was cluttering up the document.",{"type":22,"tag":193,"props":1381,"children":1383},{"id":1382},"why-semantic-html",[1384],{"type":27,"value":1385},"Why Semantic HTML?",{"type":22,"tag":23,"props":1387,"children":1388},{},[1389],{"type":27,"value":1390},"First off, a semantic HTML document is one that is inherently more accessible to software, because the meaning of page elements is encoded in the document itself. This means better accessibility to screen readers, and improvements to search engine optimization.",{"type":22,"tag":23,"props":1392,"children":1393},{},[1394],{"type":27,"value":1395},"Second, semantic HTML facilitates code reuse when building layouts that are responsive to different page sizes. Too often, I see pages which conditionally render different HTML content depending on device width; not only does this make for a less performant page, but it also creates a source of bugs, since often this creates a lot of duplication.",{"type":22,"tag":23,"props":1397,"children":1398},{},[1399,1401,1406],{"type":27,"value":1400},"Finally, I believe that writing semantic HTML makes for more robust and maintanable code, since it forces the developer to really think hard about the content that exists on the page and how it all logically relates. When developers feel free to arbitrarily use ",{"type":22,"tag":486,"props":1402,"children":1404},{"className":1403},[],[1405],{"type":27,"value":882},{"type":27,"value":1407}," elements to nest content in a way that's more convenient to style, it creates pages that are brittle in the face of change. They're also harder for developers to maintain, because the HTML is difficult to navigate when developing and debugging.",{"type":22,"tag":23,"props":1409,"children":1410},{},[1411],{"type":27,"value":1412},"If semantic HTML is so great, then I suppose it's fair to ask:",{"type":22,"tag":193,"props":1414,"children":1416},{"id":1415},"why-isnt-semantic-html-everywhere",[1417],{"type":27,"value":1418},"Why Isn't Semantic HTML Everywhere?",{"type":22,"tag":23,"props":1420,"children":1421},{},[1422],{"type":27,"value":1423},"And why do frameworks like Tailwind exist which unabashedly paint it as an unattainable goal?",{"type":22,"tag":23,"props":1425,"children":1426},{},[1427],{"type":27,"value":1428},"I think the answer is that historically, they've been right.",{"type":22,"tag":23,"props":1430,"children":1431},{},[1432,1434,1440,1442,1448,1450,1455],{"type":27,"value":1433},"Up until relatively recently if you wanted to, say, have a single HTML document and style it both with a sidebar on the left for desktop and a navbar on the top for mobile, you would have had a difficult time. There was ",{"type":22,"tag":486,"props":1435,"children":1437},{"className":1436},[],[1438],{"type":27,"value":1439},"float",{"type":27,"value":1441}," and ",{"type":22,"tag":486,"props":1443,"children":1445},{"className":1444},[],[1446],{"type":27,"value":1447},"position: absolute",{"type":27,"value":1449}," for addressing these layout challenges, but the former is limited and the latter typically requires hardcoded dimensions and positions which create maintainability issues. There also haven't always been so many semantic HTML elements, leaving more to be defined with ",{"type":22,"tag":486,"props":1451,"children":1453},{"className":1452},[],[1454],{"type":27,"value":882},{"type":27,"value":1456}," tags.",{"type":22,"tag":23,"props":1458,"children":1459},{},[1460,1462,1467],{"type":27,"value":1461},"Under all those limitations, it ",{"type":22,"tag":267,"props":1463,"children":1464},{},[1465],{"type":27,"value":1466},"was",{"type":27,"value":1468}," hard to write semantic HTML documents for complex webpages that were also functional and maintainable. Since the late 2010's however, a few key CSS features found widespread browser support, which allow for completely re-arranging elements on the page: CSS Flexbox and Grid. Since then, I think it has been perfectly achievable to keep style concerns out of HTML documents and reap the benefits.",{"type":22,"tag":193,"props":1470,"children":1472},{"id":1471},"re-arranging-a-page-with-css-grid-and-flexbox",[1473],{"type":27,"value":1474},"Re-arranging a page with CSS Grid and Flexbox",{"type":22,"tag":23,"props":1476,"children":1477},{},[1478],{"type":27,"value":1479},"With these tools in hand, an HTML document can be written in a sensible structure for sequential reading (e.g. by screen reader or search engine) and then be displayed with a huge amount of flexibility. Here's a quick overview of the techniques I've found the most helpful to that end:",{"type":22,"tag":1481,"props":1482,"children":1484},"h3",{"id":1483},"reordering-elements-with-display-flex-and-order",[1485,1487,1493,1494],{"type":27,"value":1486},"Reordering elements with ",{"type":22,"tag":486,"props":1488,"children":1490},{"className":1489},[],[1491],{"type":27,"value":1492},"display: flex",{"type":27,"value":1441},{"type":22,"tag":486,"props":1495,"children":1497},{"className":1496},[],[1498],{"type":27,"value":1499},"order",{"type":22,"tag":23,"props":1501,"children":1502},{},[1503,1505,1515,1517,1528,1530,1536,1537,1543],{"type":27,"value":1504},"One easy trick, when displaying elements in a flexible row or column Flexbox, is that the child elements can accept an integral ",{"type":22,"tag":30,"props":1506,"children":1509},{"href":1507,"rel":1508},"https://css-tricks.com/almanac/properties/o/order/",[34],[1510],{"type":22,"tag":486,"props":1511,"children":1513},{"className":1512},[],[1514],{"type":27,"value":1499},{"type":27,"value":1516}," property which defines their order in the row/column. And although it's a little more situational, keep in mind that ",{"type":22,"tag":30,"props":1518,"children":1521},{"href":1519,"rel":1520},"https://css-tricks.com/almanac/properties/f/flex-direction/",[34],[1522],{"type":22,"tag":486,"props":1523,"children":1525},{"className":1524},[],[1526],{"type":27,"value":1527},"flex-direction",{"type":27,"value":1529}," also has ",{"type":22,"tag":486,"props":1531,"children":1533},{"className":1532},[],[1534],{"type":27,"value":1535},"row-reverse",{"type":27,"value":1441},{"type":22,"tag":486,"props":1538,"children":1540},{"className":1539},[],[1541],{"type":27,"value":1542},"column-reverse",{"type":27,"value":1544}," values to reverse the order of all elements in the container.",{"type":22,"tag":23,"props":1546,"children":1547},{},[1548,1550,1556,1558,1564],{"type":27,"value":1549},"An example use case might be a navbar which on desktop displays as a sidebar (",{"type":22,"tag":486,"props":1551,"children":1553},{"className":1552},[],[1554],{"type":27,"value":1555},"flex-direction: column",{"type":27,"value":1557},") and orders its elements \"title, logo, navigation\", and on mobile displays as a top bar (",{"type":22,"tag":486,"props":1559,"children":1561},{"className":1560},[],[1562],{"type":27,"value":1563},"flex-direction: row",{"type":27,"value":1565},") and orders its elements \"logo, title, navigation\".",{"type":22,"tag":1481,"props":1567,"children":1569},{"id":1568},"positioning-elements-with-display-grid",[1570,1572],{"type":27,"value":1571},"Positioning elements with ",{"type":22,"tag":486,"props":1573,"children":1575},{"className":1574},[],[1576],{"type":27,"value":1577},"display: grid",{"type":22,"tag":23,"props":1579,"children":1580},{},[1581,1583,1589,1591,1598],{"type":27,"value":1582},"This, in my opinion, is the big game-changer: CSS Grid allows elements to be positioned on a grid in a very flexible way. The ",{"type":22,"tag":486,"props":1584,"children":1586},{"className":1585},[],[1587],{"type":27,"value":1588},"grid",{"type":27,"value":1590}," property, while a little overwhelming when first learning it (see CSS-Tricks's ",{"type":22,"tag":30,"props":1592,"children":1595},{"href":1593,"rel":1594},"https://css-tricks.com/snippets/css/complete-guide-grid/",[34],[1596],{"type":27,"value":1597},"Complete Guide to CSS Grid",{"type":27,"value":1599},"), provides a very visual way to set up the grid and assign elements to it.",{"type":22,"tag":23,"props":1601,"children":1602},{},[1603],{"type":27,"value":1604},"In the demo application for example, the \"Desktop\" layout uses the following grid value:",{"type":22,"tag":886,"props":1606,"children":1610},{"className":1607,"code":1608,"language":1609,"meta":8,"style":8},"language-css shiki shiki-themes github-light github-dark",".app-desktop {\n    display: grid;\n    grid:\n        \"header main\" auto\n        \"navbar main\" 1fr\n        \"footer main\" auto\n        / 288px 1fr;\n}\n","css",[1611],{"type":22,"tag":486,"props":1612,"children":1613},{"__ignoreMap":8},[1614,1627,1650,1663,1676,1695,1707,1738],{"type":22,"tag":896,"props":1615,"children":1616},{"class":898,"line":899},[1617,1622],{"type":22,"tag":896,"props":1618,"children":1619},{"style":933},[1620],{"type":27,"value":1621},".app-desktop",{"type":22,"tag":896,"props":1623,"children":1624},{"style":903},[1625],{"type":27,"value":1626}," {\n",{"type":22,"tag":896,"props":1628,"children":1629},{"class":898,"line":758},[1630,1636,1641,1645],{"type":22,"tag":896,"props":1631,"children":1633},{"style":1632},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[1634],{"type":27,"value":1635},"    display",{"type":22,"tag":896,"props":1637,"children":1638},{"style":903},[1639],{"type":27,"value":1640},": ",{"type":22,"tag":896,"props":1642,"children":1643},{"style":1632},[1644],{"type":27,"value":1588},{"type":22,"tag":896,"props":1646,"children":1647},{"style":903},[1648],{"type":27,"value":1649},";\n",{"type":22,"tag":896,"props":1651,"children":1652},{"class":898,"line":755},[1653,1658],{"type":22,"tag":896,"props":1654,"children":1655},{"style":1632},[1656],{"type":27,"value":1657},"    grid",{"type":22,"tag":896,"props":1659,"children":1660},{"style":903},[1661],{"type":27,"value":1662},":\n",{"type":22,"tag":896,"props":1664,"children":1665},{"class":898,"line":983},[1666,1671],{"type":22,"tag":896,"props":1667,"children":1668},{"style":944},[1669],{"type":27,"value":1670},"        \"header main\"",{"type":22,"tag":896,"props":1672,"children":1673},{"style":1632},[1674],{"type":27,"value":1675}," auto\n",{"type":22,"tag":896,"props":1677,"children":1678},{"class":898,"line":1013},[1679,1684,1689],{"type":22,"tag":896,"props":1680,"children":1681},{"style":944},[1682],{"type":27,"value":1683},"        \"navbar main\"",{"type":22,"tag":896,"props":1685,"children":1686},{"style":1632},[1687],{"type":27,"value":1688}," 1",{"type":22,"tag":896,"props":1690,"children":1692},{"style":1691},"--shiki-default:#D73A49;--shiki-dark:#F97583",[1693],{"type":27,"value":1694},"fr\n",{"type":22,"tag":896,"props":1696,"children":1697},{"class":898,"line":1030},[1698,1703],{"type":22,"tag":896,"props":1699,"children":1700},{"style":944},[1701],{"type":27,"value":1702},"        \"footer main\"",{"type":22,"tag":896,"props":1704,"children":1705},{"style":1632},[1706],{"type":27,"value":1675},{"type":22,"tag":896,"props":1708,"children":1709},{"class":898,"line":1068},[1710,1715,1720,1725,1729,1734],{"type":22,"tag":896,"props":1711,"children":1712},{"style":903},[1713],{"type":27,"value":1714},"        / ",{"type":22,"tag":896,"props":1716,"children":1717},{"style":1632},[1718],{"type":27,"value":1719},"288",{"type":22,"tag":896,"props":1721,"children":1722},{"style":1691},[1723],{"type":27,"value":1724},"px",{"type":22,"tag":896,"props":1726,"children":1727},{"style":1632},[1728],{"type":27,"value":1688},{"type":22,"tag":896,"props":1730,"children":1731},{"style":1691},[1732],{"type":27,"value":1733},"fr",{"type":22,"tag":896,"props":1735,"children":1736},{"style":903},[1737],{"type":27,"value":1649},{"type":22,"tag":896,"props":1739,"children":1740},{"class":898,"line":1085},[1741],{"type":22,"tag":896,"props":1742,"children":1743},{"style":903},[1744],{"type":27,"value":1745},"}\n",{"type":22,"tag":23,"props":1747,"children":1748},{},[1749,1751,1757,1759,1765,1767,1772,1774,1779,1781,1787],{"type":27,"value":1750},"Without going too deep into the details, what this represents is a 2x3 grid with a ",{"type":22,"tag":486,"props":1752,"children":1754},{"className":1753},[],[1755],{"type":27,"value":1756},"header",{"type":27,"value":1758}," area in the top-left, a ",{"type":22,"tag":486,"props":1760,"children":1762},{"className":1761},[],[1763],{"type":27,"value":1764},"navbar",{"type":27,"value":1766}," area in the middle-left, a ",{"type":22,"tag":486,"props":1768,"children":1770},{"className":1769},[],[1771],{"type":27,"value":1332},{"type":27,"value":1773}," area in the bottom-left, and ",{"type":22,"tag":486,"props":1775,"children":1777},{"className":1776},[],[1778],{"type":27,"value":1308},{"type":27,"value":1780}," area stretching down the whole right side. The lengths at the end of each line are the row heights, and the lengths on the last line are the column widths. With each element assigned to a grid area (e.g. with ",{"type":22,"tag":486,"props":1782,"children":1784},{"className":1783},[],[1785],{"type":27,"value":1786},"grid-area: header",{"type":27,"value":1788},"), they'll appear in the appropriate grid area regardless of their order in the document.",{"type":22,"tag":23,"props":1790,"children":1791},{},[1792,1794,1801],{"type":27,"value":1793},"This is how it looks in Chrome with its ",{"type":22,"tag":30,"props":1795,"children":1798},{"href":1796,"rel":1797},"https://developer.chrome.com/docs/devtools/css/grid/",[34],[1799],{"type":27,"value":1800},"CSS Grid debugging tools",{"type":27,"value":1802}," enabled:",{"type":22,"tag":23,"props":1804,"children":1805},{},[1806],{"type":22,"tag":314,"props":1807,"children":1810},{"alt":1808,"src":1809},"The demo \"Desktop\" layout, with Chrome debugging applied to identify the grid areas","/phendry/2023-04-02/img/grid_debug_desktop.png",[],{"type":22,"tag":23,"props":1812,"children":1813},{},[1814],{"type":27,"value":1815},"The orange lines show the grid cells (where solid lines denote the edges of child elements and dashed lines indicate that an element is spanning across multiple cells), and the orange labels show the names of the grid areas. Despite the header, footer and navbar elements all being distinct, we can use a grid to easily style them so that they all look like a unified sidebar.",{"type":22,"tag":23,"props":1817,"children":1818},{},[1819],{"type":27,"value":1820},"The \"Mobile\" layout takes the same document and places it on a totally different grid:",{"type":22,"tag":886,"props":1822,"children":1824},{"className":1607,"code":1823,"language":1609,"meta":8,"style":8},".app-mobile {\n    display: grid;\n    grid:\n        \"logo   header header nav\"   64px\n        \"main   main   main   main\"  1fr\n        \"footer footer style  style\" auto\n        / auto 1fr auto auto;\n}\n",[1825],{"type":22,"tag":486,"props":1826,"children":1827},{"__ignoreMap":8},[1828,1840,1859,1870,1888,1905,1917,1950],{"type":22,"tag":896,"props":1829,"children":1830},{"class":898,"line":899},[1831,1836],{"type":22,"tag":896,"props":1832,"children":1833},{"style":933},[1834],{"type":27,"value":1835},".app-mobile",{"type":22,"tag":896,"props":1837,"children":1838},{"style":903},[1839],{"type":27,"value":1626},{"type":22,"tag":896,"props":1841,"children":1842},{"class":898,"line":758},[1843,1847,1851,1855],{"type":22,"tag":896,"props":1844,"children":1845},{"style":1632},[1846],{"type":27,"value":1635},{"type":22,"tag":896,"props":1848,"children":1849},{"style":903},[1850],{"type":27,"value":1640},{"type":22,"tag":896,"props":1852,"children":1853},{"style":1632},[1854],{"type":27,"value":1588},{"type":22,"tag":896,"props":1856,"children":1857},{"style":903},[1858],{"type":27,"value":1649},{"type":22,"tag":896,"props":1860,"children":1861},{"class":898,"line":755},[1862,1866],{"type":22,"tag":896,"props":1863,"children":1864},{"style":1632},[1865],{"type":27,"value":1657},{"type":22,"tag":896,"props":1867,"children":1868},{"style":903},[1869],{"type":27,"value":1662},{"type":22,"tag":896,"props":1871,"children":1872},{"class":898,"line":983},[1873,1878,1883],{"type":22,"tag":896,"props":1874,"children":1875},{"style":944},[1876],{"type":27,"value":1877},"        \"logo   header header nav\"",{"type":22,"tag":896,"props":1879,"children":1880},{"style":1632},[1881],{"type":27,"value":1882},"   64",{"type":22,"tag":896,"props":1884,"children":1885},{"style":1691},[1886],{"type":27,"value":1887},"px\n",{"type":22,"tag":896,"props":1889,"children":1890},{"class":898,"line":1013},[1891,1896,1901],{"type":22,"tag":896,"props":1892,"children":1893},{"style":944},[1894],{"type":27,"value":1895},"        \"main   main   main   main\"",{"type":22,"tag":896,"props":1897,"children":1898},{"style":1632},[1899],{"type":27,"value":1900},"  1",{"type":22,"tag":896,"props":1902,"children":1903},{"style":1691},[1904],{"type":27,"value":1694},{"type":22,"tag":896,"props":1906,"children":1907},{"class":898,"line":1030},[1908,1913],{"type":22,"tag":896,"props":1909,"children":1910},{"style":944},[1911],{"type":27,"value":1912},"        \"footer footer style  style\"",{"type":22,"tag":896,"props":1914,"children":1915},{"style":1632},[1916],{"type":27,"value":1675},{"type":22,"tag":896,"props":1918,"children":1919},{"class":898,"line":1068},[1920,1924,1929,1933,1937,1942,1946],{"type":22,"tag":896,"props":1921,"children":1922},{"style":903},[1923],{"type":27,"value":1714},{"type":22,"tag":896,"props":1925,"children":1926},{"style":1632},[1927],{"type":27,"value":1928},"auto",{"type":22,"tag":896,"props":1930,"children":1931},{"style":1632},[1932],{"type":27,"value":1688},{"type":22,"tag":896,"props":1934,"children":1935},{"style":1691},[1936],{"type":27,"value":1733},{"type":22,"tag":896,"props":1938,"children":1939},{"style":1632},[1940],{"type":27,"value":1941}," auto",{"type":22,"tag":896,"props":1943,"children":1944},{"style":1632},[1945],{"type":27,"value":1941},{"type":22,"tag":896,"props":1947,"children":1948},{"style":903},[1949],{"type":27,"value":1649},{"type":22,"tag":896,"props":1951,"children":1952},{"class":898,"line":1085},[1953],{"type":22,"tag":896,"props":1954,"children":1955},{"style":903},[1956],{"type":27,"value":1745},{"type":22,"tag":23,"props":1958,"children":1959},{},[1960,1962,1968,1969,1974,1975,1980,1981,1986,1987,1992,1994,2000],{"type":27,"value":1961},"This is a 4x3 grid, with areas for ",{"type":22,"tag":486,"props":1963,"children":1965},{"className":1964},[],[1966],{"type":27,"value":1967},"logo",{"type":27,"value":236},{"type":22,"tag":486,"props":1970,"children":1972},{"className":1971},[],[1973],{"type":27,"value":1756},{"type":27,"value":236},{"type":22,"tag":486,"props":1976,"children":1978},{"className":1977},[],[1979],{"type":27,"value":1269},{"type":27,"value":236},{"type":22,"tag":486,"props":1982,"children":1984},{"className":1983},[],[1985],{"type":27,"value":1308},{"type":27,"value":236},{"type":22,"tag":486,"props":1988,"children":1990},{"className":1989},[],[1991],{"type":27,"value":1332},{"type":27,"value":1993},", and ",{"type":22,"tag":486,"props":1995,"children":1997},{"className":1996},[],[1998],{"type":27,"value":1999},"style",{"type":27,"value":2001}," (the dropdown used to change layouts). By having four columns, the elements of the header and the footer don't need to be in alignment, allowing each to flow as expected, best illustrated in the debugging screenshot below:",{"type":22,"tag":23,"props":2003,"children":2004},{},[2005],{"type":22,"tag":314,"props":2006,"children":2009},{"alt":2007,"src":2008},"The demo \"Mobile\" layout, with Chrome debugging applied to identify the grid areas","/phendry/2023-04-02/img/grid_debug_mobile.png",[],{"type":22,"tag":23,"props":2011,"children":2012},{},[2013],{"type":27,"value":2014},"These two pages have almost nothing in common layout-wise, but they share the same HTML document, and CSS Grid is doing the leg work.",{"type":22,"tag":1481,"props":2016,"children":2018},{"id":2017},"collapsing-elements-with-display-contents",[2019,2021],{"type":27,"value":2020},"Collapsing elements with ",{"type":22,"tag":486,"props":2022,"children":2024},{"className":2023},[],[2025],{"type":27,"value":2026},"display: contents",{"type":22,"tag":23,"props":2028,"children":2029},{},[2030],{"type":27,"value":2031},"The \"Mobile\" layout above has one trick which is normally not allowed in CSS Grids: it puts two elements on the grid that are not direct children of the grid element:",{"type":22,"tag":886,"props":2033,"children":2035},{"className":888,"code":2034,"language":890,"meta":8,"style":8},"\u003Cdiv class=\"app\"> \u003C!-- Grid parent -->\n    \u003Cheader>...\u003C/header> \u003C!-- Child -->\n    \u003Caside class=\"navbar\">\n        ...\n        \u003Cnav class=\"navbar__nav\">...\u003C/nav> \u003C!-- NOT a child -->\n        \u003Cform class=\"navbar__form-options\">...\u003C/form> \u003C!-- NOT a child -->\n        ...\n    \u003C/aside>\n    \u003Cmain>...\u003C/main> \u003C!-- Child -->\n    \u003Cfooter>...\u003C/footer> \u003C!-- Child -->\n\u003C/div>\n",[2036],{"type":22,"tag":486,"props":2037,"children":2038},{"__ignoreMap":8},[2039,2074,2103,2130,2138,2179,2220,2227,2242,2269,2296],{"type":22,"tag":896,"props":2040,"children":2041},{"class":898,"line":899},[2042,2046,2050,2054,2058,2063,2068],{"type":22,"tag":896,"props":2043,"children":2044},{"style":903},[2045],{"type":27,"value":906},{"type":22,"tag":896,"props":2047,"children":2048},{"style":909},[2049],{"type":27,"value":930},{"type":22,"tag":896,"props":2051,"children":2052},{"style":933},[2053],{"type":27,"value":936},{"type":22,"tag":896,"props":2055,"children":2056},{"style":903},[2057],{"type":27,"value":941},{"type":22,"tag":896,"props":2059,"children":2060},{"style":944},[2061],{"type":27,"value":2062},"\"app\"",{"type":22,"tag":896,"props":2064,"children":2065},{"style":903},[2066],{"type":27,"value":2067},"> ",{"type":22,"tag":896,"props":2069,"children":2071},{"style":2070},"--shiki-default:#6A737D;--shiki-dark:#6A737D",[2072],{"type":27,"value":2073},"\u003C!-- Grid parent -->\n",{"type":22,"tag":896,"props":2075,"children":2076},{"class":898,"line":758},[2077,2081,2085,2090,2094,2098],{"type":22,"tag":896,"props":2078,"children":2079},{"style":903},[2080],{"type":27,"value":925},{"type":22,"tag":896,"props":2082,"children":2083},{"style":909},[2084],{"type":27,"value":1756},{"type":22,"tag":896,"props":2086,"children":2087},{"style":903},[2088],{"type":27,"value":2089},">...\u003C/",{"type":22,"tag":896,"props":2091,"children":2092},{"style":909},[2093],{"type":27,"value":1756},{"type":22,"tag":896,"props":2095,"children":2096},{"style":903},[2097],{"type":27,"value":2067},{"type":22,"tag":896,"props":2099,"children":2100},{"style":2070},[2101],{"type":27,"value":2102},"\u003C!-- Child -->\n",{"type":22,"tag":896,"props":2104,"children":2105},{"class":898,"line":755},[2106,2110,2114,2118,2122,2126],{"type":22,"tag":896,"props":2107,"children":2108},{"style":903},[2109],{"type":27,"value":925},{"type":22,"tag":896,"props":2111,"children":2112},{"style":909},[2113],{"type":27,"value":1214},{"type":22,"tag":896,"props":2115,"children":2116},{"style":933},[2117],{"type":27,"value":936},{"type":22,"tag":896,"props":2119,"children":2120},{"style":903},[2121],{"type":27,"value":941},{"type":22,"tag":896,"props":2123,"children":2124},{"style":944},[2125],{"type":27,"value":947},{"type":22,"tag":896,"props":2127,"children":2128},{"style":903},[2129],{"type":27,"value":917},{"type":22,"tag":896,"props":2131,"children":2132},{"class":898,"line":983},[2133],{"type":22,"tag":896,"props":2134,"children":2135},{"style":903},[2136],{"type":27,"value":2137},"        ...\n",{"type":22,"tag":896,"props":2139,"children":2140},{"class":898,"line":1013},[2141,2145,2149,2153,2157,2162,2166,2170,2174],{"type":22,"tag":896,"props":2142,"children":2143},{"style":903},[2144],{"type":27,"value":959},{"type":22,"tag":896,"props":2146,"children":2147},{"style":909},[2148],{"type":27,"value":1269},{"type":22,"tag":896,"props":2150,"children":2151},{"style":933},[2152],{"type":27,"value":936},{"type":22,"tag":896,"props":2154,"children":2155},{"style":903},[2156],{"type":27,"value":941},{"type":22,"tag":896,"props":2158,"children":2159},{"style":944},[2160],{"type":27,"value":2161},"\"navbar__nav\"",{"type":22,"tag":896,"props":2163,"children":2164},{"style":903},[2165],{"type":27,"value":2089},{"type":22,"tag":896,"props":2167,"children":2168},{"style":909},[2169],{"type":27,"value":1269},{"type":22,"tag":896,"props":2171,"children":2172},{"style":903},[2173],{"type":27,"value":2067},{"type":22,"tag":896,"props":2175,"children":2176},{"style":2070},[2177],{"type":27,"value":2178},"\u003C!-- NOT a child -->\n",{"type":22,"tag":896,"props":2180,"children":2181},{"class":898,"line":1030},[2182,2186,2191,2195,2199,2204,2208,2212,2216],{"type":22,"tag":896,"props":2183,"children":2184},{"style":903},[2185],{"type":27,"value":959},{"type":22,"tag":896,"props":2187,"children":2188},{"style":909},[2189],{"type":27,"value":2190},"form",{"type":22,"tag":896,"props":2192,"children":2193},{"style":933},[2194],{"type":27,"value":936},{"type":22,"tag":896,"props":2196,"children":2197},{"style":903},[2198],{"type":27,"value":941},{"type":22,"tag":896,"props":2200,"children":2201},{"style":944},[2202],{"type":27,"value":2203},"\"navbar__form-options\"",{"type":22,"tag":896,"props":2205,"children":2206},{"style":903},[2207],{"type":27,"value":2089},{"type":22,"tag":896,"props":2209,"children":2210},{"style":909},[2211],{"type":27,"value":2190},{"type":22,"tag":896,"props":2213,"children":2214},{"style":903},[2215],{"type":27,"value":2067},{"type":22,"tag":896,"props":2217,"children":2218},{"style":2070},[2219],{"type":27,"value":2178},{"type":22,"tag":896,"props":2221,"children":2222},{"class":898,"line":1068},[2223],{"type":22,"tag":896,"props":2224,"children":2225},{"style":903},[2226],{"type":27,"value":2137},{"type":22,"tag":896,"props":2228,"children":2229},{"class":898,"line":1085},[2230,2234,2238],{"type":22,"tag":896,"props":2231,"children":2232},{"style":903},[2233],{"type":27,"value":1074},{"type":22,"tag":896,"props":2235,"children":2236},{"style":909},[2237],{"type":27,"value":1214},{"type":22,"tag":896,"props":2239,"children":2240},{"style":903},[2241],{"type":27,"value":917},{"type":22,"tag":896,"props":2243,"children":2244},{"class":898,"line":1123},[2245,2249,2253,2257,2261,2265],{"type":22,"tag":896,"props":2246,"children":2247},{"style":903},[2248],{"type":27,"value":925},{"type":22,"tag":896,"props":2250,"children":2251},{"style":909},[2252],{"type":27,"value":1308},{"type":22,"tag":896,"props":2254,"children":2255},{"style":903},[2256],{"type":27,"value":2089},{"type":22,"tag":896,"props":2258,"children":2259},{"style":909},[2260],{"type":27,"value":1308},{"type":22,"tag":896,"props":2262,"children":2263},{"style":903},[2264],{"type":27,"value":2067},{"type":22,"tag":896,"props":2266,"children":2267},{"style":2070},[2268],{"type":27,"value":2102},{"type":22,"tag":896,"props":2270,"children":2271},{"class":898,"line":1161},[2272,2276,2280,2284,2288,2292],{"type":22,"tag":896,"props":2273,"children":2274},{"style":903},[2275],{"type":27,"value":925},{"type":22,"tag":896,"props":2277,"children":2278},{"style":909},[2279],{"type":27,"value":1332},{"type":22,"tag":896,"props":2281,"children":2282},{"style":903},[2283],{"type":27,"value":2089},{"type":22,"tag":896,"props":2285,"children":2286},{"style":909},[2287],{"type":27,"value":1332},{"type":22,"tag":896,"props":2289,"children":2290},{"style":903},[2291],{"type":27,"value":2067},{"type":22,"tag":896,"props":2293,"children":2294},{"style":2070},[2295],{"type":27,"value":2102},{"type":22,"tag":896,"props":2297,"children":2299},{"class":898,"line":2298},11,[2300,2304,2308],{"type":22,"tag":896,"props":2301,"children":2302},{"style":903},[2303],{"type":27,"value":1167},{"type":22,"tag":896,"props":2305,"children":2306},{"style":909},[2307],{"type":27,"value":930},{"type":22,"tag":896,"props":2309,"children":2310},{"style":903},[2311],{"type":27,"value":917},{"type":22,"tag":23,"props":2313,"children":2314},{},[2315,2317,2322,2324,2330,2332,2338,2339,2345,2347,2352,2354,2359],{"type":27,"value":2316},"Thankfully, another CSS property is helpful here: ",{"type":22,"tag":486,"props":2318,"children":2320},{"className":2319},[],[2321],{"type":27,"value":2026},{"type":27,"value":2323},". When applied to an element, it essentially makes it disappear and have its children take its place, as far as rendering is concerned. The \"Mobile\" layout applies this to the ",{"type":22,"tag":486,"props":2325,"children":2327},{"className":2326},[],[2328],{"type":27,"value":2329},"\u003Caside>",{"type":27,"value":2331}," element, causing the ",{"type":22,"tag":486,"props":2333,"children":2335},{"className":2334},[],[2336],{"type":27,"value":2337},"\u003Cnav>",{"type":27,"value":1441},{"type":22,"tag":486,"props":2340,"children":2342},{"className":2341},[],[2343],{"type":27,"value":2344},"\u003Cform>",{"type":27,"value":2346}," elements to behave as if they were children of the ",{"type":22,"tag":486,"props":2348,"children":2350},{"className":2349},[],[2351],{"type":27,"value":882},{"type":27,"value":2353},", and making them valid elements to place on the ",{"type":22,"tag":486,"props":2355,"children":2357},{"className":2356},[],[2358],{"type":27,"value":882},{"type":27,"value":2360},"'s Grid.",{"type":22,"tag":23,"props":2362,"children":2363},{},[2364],{"type":27,"value":2365},"This is a great way to retain the capabilities of CSS Grid when writing semantic HTML, because elements can be defined in a sensible nested structure while still being valid targets for Grid-based styling.",{"type":22,"tag":1481,"props":2367,"children":2369},{"id":2368},"css-based-dropdowns-using-position-absolute-and-hover",[2370,2372,2377,2378],{"type":27,"value":2371},"CSS-based dropdowns using ",{"type":22,"tag":486,"props":2373,"children":2375},{"className":2374},[],[2376],{"type":27,"value":1447},{"type":27,"value":1441},{"type":22,"tag":486,"props":2379,"children":2381},{"className":2380},[],[2382],{"type":27,"value":2383},":hover",{"type":22,"tag":23,"props":2385,"children":2386},{},[2387],{"type":27,"value":2388},"While it has limitations that mean it won't work for all scenarios, a traditional navigation menu can also be converted into a keyboard- and mobile-friendly dropdown menu purely with CSS. The demo project does this in its \"Mobile\" layout:",{"type":22,"tag":23,"props":2390,"children":2391},{},[2392],{"type":22,"tag":314,"props":2393,"children":2396},{"alt":2394,"src":2395},"The dropdown menu in the \"Mobile\" layout of the demo project","/phendry/2023-04-02/img/demo_dropdown.png",[],{"type":22,"tag":23,"props":2398,"children":2399},{},[2400,2402,2409,2411,2416,2417,2423],{"type":27,"value":2401},"The details are a little too detailed to cover here so I'll defer to ",{"type":22,"tag":30,"props":2403,"children":2406},{"href":2404,"rel":2405},"https://css-tricks.com/solved-with-css-dropdown-menus/",[34],[2407],{"type":27,"value":2408},"this CSS-Tricks article",{"type":27,"value":2410}," that covers it, but essentially all it's using are the ",{"type":22,"tag":486,"props":2412,"children":2414},{"className":2413},[],[2415],{"type":27,"value":2383},{"type":27,"value":1441},{"type":22,"tag":486,"props":2418,"children":2420},{"className":2419},[],[2421],{"type":27,"value":2422},":focus-within",{"type":27,"value":2424}," pseudo-classes to control when the dropdown container and mouseover effects are displayed.",{"type":22,"tag":193,"props":2426,"children":2428},{"id":2427},"give-semantic-html-a-chance",[2429],{"type":27,"value":2430},"Give Semantic HTML a Chance!",{"type":22,"tag":23,"props":2432,"children":2433},{},[2434,2436,2441],{"type":27,"value":2435},"Tailwind and other \"atomic CSS\" frameworks are perfectly capable tools and many developers love them, but I can't help but feel that they don't so much solve a problem as much as they ",{"type":22,"tag":267,"props":2437,"children":2438},{},[2439],{"type":27,"value":2440},"shift",{"type":27,"value":2442}," it elsewhere. Defining presentation inline in HTML solves the problem of unmaintainable CSS files, but it also creates a problem of duplication (whenever multiple layouts need to be implemented, at least). Some teams may be better equipped to handle the problems of atomic CSS compared to those of semantic HTML/CSS, but I'm not convinced that they're fundamentally easier to overcome.",{"type":22,"tag":23,"props":2444,"children":2445},{},[2446],{"type":27,"value":2447},"On the contrary, the problems with atomic CSS are arguably inherent to the technique, whereas problems of semantic CSS maintainability can be addressed with largely the same techniques used for code maintainability. As challenging as it can be to maintain CSS, I think most teams would be better off addressing the root causes that are leading to their CSS maintainability issues, rather than reach for a technical solution to what is ultimately (in my view) a knowledge and process problem.",{"type":22,"tag":23,"props":2449,"children":2450},{},[2451],{"type":27,"value":2452},"As we've seen here, styling semantic HTML is easier and more powerful than ever, and semantic HTML has a host of benefits that would be a shame to lose out on. So dig into the CSS Grid and Flexbox documentation and give it a go on your current (or next) project!",{"type":22,"tag":1999,"props":2454,"children":2455},{},[2456],{"type":27,"value":2457},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":755,"depth":755,"links":2459},[2460,2461,2462,2463,2473],{"id":837,"depth":758,"text":840},{"id":1382,"depth":758,"text":1385},{"id":1415,"depth":758,"text":1418},{"id":1471,"depth":758,"text":1474,"children":2464},[2465,2467,2469,2471],{"id":1483,"depth":755,"text":2466},"Reordering elements with display: flex and order",{"id":1568,"depth":755,"text":2468},"Positioning elements with display: grid",{"id":2017,"depth":755,"text":2470},"Collapsing elements with display: contents",{"id":2368,"depth":755,"text":2472},"CSS-based dropdowns using position: absolute and :hover",{"id":2427,"depth":758,"text":2430},"content:phendry:2023-04-02:SemanticHtml.md","phendry/2023-04-02/SemanticHtml.md","phendry/2023-04-02/SemanticHtml",{"user":774,"name":775},{"_path":2479,"_dir":2480,"_draft":7,"_partial":7,"_locale":8,"title":2481,"description":2482,"image":2483,"publishDate":2484,"tags":2485,"excerpt":2482,"body":2486,"_type":767,"_id":2808,"_source":769,"_file":2809,"_stem":2810,"_extension":772,"author":2811},"/phendry/2023-01-31/forgetaboutcodestyle","2023-01-31","Forget About [Code] Style","Good code style, being highly subjective, is something often debated among developers. After all, we spend more time reading code than writing it, so it's worth making sure our code is styled to be as easy as possible to read and to understand. On the other hand, deciding upon and continuously enforcing a style is also time-consuming, and the benefits are near-impossible to quantify. Given that modern code formatting tools can fully automate the process, is it still worth fretting about style?","/phendry/2023-01-31/img/forget_style_header.png","2024-02-01",[12],{"type":19,"children":2487,"toc":2804},[2488,2492,2498,2503,2540,2545,2586,2591,2654,2659,2665,2670,2750,2755,2768,2773,2778,2783,2795,2800],{"type":22,"tag":23,"props":2489,"children":2490},{},[2491],{"type":27,"value":2482},{"type":22,"tag":193,"props":2493,"children":2495},{"id":2494},"the-hidden-costs-of-being-stylish",[2496],{"type":27,"value":2497},"The Hidden Costs of Being Stylish",{"type":22,"tag":23,"props":2499,"children":2500},{},[2501],{"type":27,"value":2502},"Suppose your team or your company decides that it's more readable to place opening curly braces before the newline, i.e.",{"type":22,"tag":886,"props":2504,"children":2508},{"className":2505,"code":2506,"language":2507,"meta":8,"style":8},"language-js shiki shiki-themes github-light github-dark","if (cond) {\n    ...\n}\n","js",[2509],{"type":22,"tag":486,"props":2510,"children":2511},{"__ignoreMap":8},[2512,2525,2533],{"type":22,"tag":896,"props":2513,"children":2514},{"class":898,"line":899},[2515,2520],{"type":22,"tag":896,"props":2516,"children":2517},{"style":1691},[2518],{"type":27,"value":2519},"if",{"type":22,"tag":896,"props":2521,"children":2522},{"style":903},[2523],{"type":27,"value":2524}," (cond) {\n",{"type":22,"tag":896,"props":2526,"children":2527},{"class":898,"line":758},[2528],{"type":22,"tag":896,"props":2529,"children":2530},{"style":1691},[2531],{"type":27,"value":2532},"    ...\n",{"type":22,"tag":896,"props":2534,"children":2535},{"class":898,"line":755},[2536],{"type":22,"tag":896,"props":2537,"children":2538},{"style":903},[2539],{"type":27,"value":1745},{"type":22,"tag":23,"props":2541,"children":2542},{},[2543],{"type":27,"value":2544},"rather than after it, i.e.",{"type":22,"tag":886,"props":2546,"children":2548},{"className":2505,"code":2547,"language":2507,"meta":8,"style":8},"if (cond)\n{\n    ...\n}\n",[2549],{"type":22,"tag":486,"props":2550,"children":2551},{"__ignoreMap":8},[2552,2564,2572,2579],{"type":22,"tag":896,"props":2553,"children":2554},{"class":898,"line":899},[2555,2559],{"type":22,"tag":896,"props":2556,"children":2557},{"style":1691},[2558],{"type":27,"value":2519},{"type":22,"tag":896,"props":2560,"children":2561},{"style":903},[2562],{"type":27,"value":2563}," (cond)\n",{"type":22,"tag":896,"props":2565,"children":2566},{"class":898,"line":758},[2567],{"type":22,"tag":896,"props":2568,"children":2569},{"style":903},[2570],{"type":27,"value":2571},"{\n",{"type":22,"tag":896,"props":2573,"children":2574},{"class":898,"line":755},[2575],{"type":22,"tag":896,"props":2576,"children":2577},{"style":1691},[2578],{"type":27,"value":2532},{"type":22,"tag":896,"props":2580,"children":2581},{"class":898,"line":983},[2582],{"type":22,"tag":896,"props":2583,"children":2584},{"style":903},[2585],{"type":27,"value":1745},{"type":22,"tag":23,"props":2587,"children":2588},{},[2589],{"type":27,"value":2590},"This small rule carries with it a number of hidden costs:",{"type":22,"tag":847,"props":2592,"children":2593},{},[2594,2614,2624,2634,2644],{"type":22,"tag":115,"props":2595,"children":2596},{},[2597,2603,2605,2612],{"type":22,"tag":2598,"props":2599,"children":2600},"strong",{},[2601],{"type":27,"value":2602},"Defining the style:",{"type":27,"value":2604}," It took at least a few minutes to decide on the style, more if ",{"type":22,"tag":30,"props":2606,"children":2609},{"href":2607,"rel":2608},"https://en.wikipedia.org/wiki/Law_of_triviality",[34],[2610],{"type":27,"value":2611},"bikeshedding",{"type":27,"value":2613}," takes place.",{"type":22,"tag":115,"props":2615,"children":2616},{},[2617,2622],{"type":22,"tag":2598,"props":2618,"children":2619},{},[2620],{"type":27,"value":2621},"Configuring tools:",{"type":27,"value":2623}," To save developers time, tooling can be used to catch or even to automatically fix violations of this style, but it takes a little time to configure that tooling.",{"type":22,"tag":115,"props":2625,"children":2626},{},[2627,2632],{"type":22,"tag":2598,"props":2628,"children":2629},{},[2630],{"type":27,"value":2631},"Thinking about style:",{"type":27,"value":2633}," Developers now need to be thinking about this style while they work. That might sound silly, but when a developer has their own differing preferences, or when they have experience using a different style (e.g. on previous teams or outside of work), each rule takes a little bit of brainpower to remember and to follow.",{"type":22,"tag":115,"props":2635,"children":2636},{},[2637,2642],{"type":22,"tag":2598,"props":2638,"children":2639},{},[2640],{"type":27,"value":2641},"Dealing with automated style rejections:",{"type":27,"value":2643}," Every time a tool rejects code due to style violations, a little time is wasted. This can for the most part be avoided using IDE integrations that auto-format code on save, but still affects certain areas (like using online code editing features in source control providers like GitHub or GitLab).",{"type":22,"tag":115,"props":2645,"children":2646},{},[2647,2652],{"type":22,"tag":2598,"props":2648,"children":2649},{},[2650],{"type":27,"value":2651},"Dealing with code review style rejections:",{"type":27,"value":2653}," Even worse than automated style rejections is when there is insufficient automation in place, and style errors are caught manually in code review.",{"type":22,"tag":23,"props":2655,"children":2656},{},[2657],{"type":27,"value":2658},"These costs may seem trivial, but given that most style guides have tens or hundreds of rules, they add up. Is it worth all of that just to have curly braces in the place the developers prefer? Or, to put it another way: does it matter where the curly braces are, if tooling can enforce it one way so seamlessly that developers never need to think about it?",{"type":22,"tag":193,"props":2660,"children":2662},{"id":2661},"the-benefits-of-having-no-style",[2663],{"type":27,"value":2664},"The Benefits of Having No Style",{"type":22,"tag":23,"props":2666,"children":2667},{},[2668],{"type":27,"value":2669},"Many mainstream languages today have one or more so-called \"opinionated code formatters\", for example:",{"type":22,"tag":111,"props":2671,"children":2672},{},[2673,2694,2706,2718,2734],{"type":22,"tag":115,"props":2674,"children":2675},{},[2676,2683,2685,2692],{"type":22,"tag":30,"props":2677,"children":2680},{"href":2678,"rel":2679},"https://prettier.io/",[34],[2681],{"type":27,"value":2682},"Prettier",{"type":27,"value":2684}," or ",{"type":22,"tag":30,"props":2686,"children":2689},{"href":2687,"rel":2688},"https://standardjs.com/",[34],[2690],{"type":27,"value":2691},"Standard JS",{"type":27,"value":2693}," for JavaScript and TypeScript",{"type":22,"tag":115,"props":2695,"children":2696},{},[2697,2704],{"type":22,"tag":30,"props":2698,"children":2701},{"href":2699,"rel":2700},"https://black.readthedocs.io/en/stable/",[34],[2702],{"type":27,"value":2703},"Black",{"type":27,"value":2705}," for Python",{"type":22,"tag":115,"props":2707,"children":2708},{},[2709,2716],{"type":22,"tag":30,"props":2710,"children":2713},{"href":2711,"rel":2712},"https://csharpier.com/",[34],[2714],{"type":27,"value":2715},"CSharpier",{"type":27,"value":2717}," for C#",{"type":22,"tag":115,"props":2719,"children":2720},{},[2721,2732],{"type":22,"tag":30,"props":2722,"children":2725},{"href":2723,"rel":2724},"https://github.com/rust-lang/rustfmt",[34],[2726],{"type":22,"tag":486,"props":2727,"children":2729},{"className":2728},[],[2730],{"type":27,"value":2731},"rustfmt",{"type":27,"value":2733}," for Rust",{"type":22,"tag":115,"props":2735,"children":2736},{},[2737,2748],{"type":22,"tag":30,"props":2738,"children":2741},{"href":2739,"rel":2740},"https://pkg.go.dev/cmd/gofmt",[34],[2742],{"type":22,"tag":486,"props":2743,"children":2745},{"className":2744},[],[2746],{"type":27,"value":2747},"gofmt",{"type":27,"value":2749}," for Go",{"type":22,"tag":23,"props":2751,"children":2752},{},[2753],{"type":27,"value":2754},"They're called \"opinionated\" because they provide little to no configuration capabilities, instead deciding most things for you. If your language of choice is in the above, I encourage you to look at an example of one of those formatters' outputs. What do you think? It likely follows several rules you would choose differently, and you could likely produce nicer code than it can by judiciously breaking the rules in special cases where it's appropriate to do so. But I strongly suspect that overall, you'll find that the output looks... fine. It's readable. It works.",{"type":22,"tag":23,"props":2756,"children":2757},{},[2758,2760,2766],{"type":27,"value":2759},"Now, consider that with a just a few lines of configuration, you could have your editor automatically format your code on save to fully match the style, with additional checks on ",{"type":22,"tag":486,"props":2761,"children":2763},{"className":2762},[],[2764],{"type":27,"value":2765},"git push",{"type":27,"value":2767}," and/or in CI in case anything slipped through.",{"type":22,"tag":23,"props":2769,"children":2770},{},[2771],{"type":27,"value":2772},"No time spent with a committee of developers deciding on the best place for curly braces.",{"type":22,"tag":23,"props":2774,"children":2775},{},[2776],{"type":27,"value":2777},"No time spent mentally context-switching between a work codebase that does things one way and an open-source codebase that does things another way.",{"type":22,"tag":23,"props":2779,"children":2780},{},[2781],{"type":27,"value":2782},"No time spent in code reviews pointing out minor style violations.",{"type":22,"tag":23,"props":2784,"children":2785},{},[2786,2788,2793],{"type":27,"value":2787},"Just type out a hideous-yet-functional stream of code in whatever way suits you, hit \"save\", watch your code instantly get prettified for you, and move on. That doesn't sound like much, but once you've experienced it, it's hard to go back. And once you get used to tolerating some rules you disagree with, you start to realize that what's more important than seeing your preferred style is seeing a ",{"type":22,"tag":267,"props":2789,"children":2790},{},[2791],{"type":27,"value":2792},"consistent",{"type":27,"value":2794}," style, and that's something that can be fully automated.",{"type":22,"tag":23,"props":2796,"children":2797},{},[2798],{"type":27,"value":2799},"The next time you're configuring a project, consider using an opinionated code formatter, and saving your brain power for solving the real problems.",{"type":22,"tag":1999,"props":2801,"children":2802},{},[2803],{"type":27,"value":2457},{"title":8,"searchDepth":755,"depth":755,"links":2805},[2806,2807],{"id":2494,"depth":758,"text":2497},{"id":2661,"depth":758,"text":2664},"content:phendry:2023-01-31:ForgetAboutCodeStyle.md","phendry/2023-01-31/ForgetAboutCodeStyle.md","phendry/2023-01-31/ForgetAboutCodeStyle",{"user":774,"name":775},{"_path":2813,"_dir":2814,"_draft":7,"_partial":7,"_locale":8,"title":2815,"description":2816,"image":2817,"excerpt":2816,"publishDate":2818,"tags":2819,"body":2820,"_type":767,"_id":3492,"_source":769,"_file":3493,"_stem":3494,"_extension":772,"author":3495},"/phendry/2023-07-28/dependencymanagement","2023-07-28","Software Dependency Management: Best Practices","Leveraging third-party libraries and frameworks is essential in most modern software projects, and the projects we build at Art+Logic are no exception. The pressure on developers to rapidly deliver features is high, and there are so many commonalities in the details of each project (particularly in Web development) that a lot of development time can be saved by using well-designed libraries that handle the details.","/phendry/2023-07-28/img/dependency_header.png","2023-01-02",[12],{"type":19,"children":2821,"toc":3483},[2822,2826,2831,2859,2864,2892,2897,2902,2908,2913,3012,3018,3023,3037,3042,3227,3232,3238,3243,3248,3266,3278,3284,3296,3301,3307,3428,3434],{"type":22,"tag":23,"props":2823,"children":2824},{},[2825],{"type":27,"value":2816},{"type":22,"tag":23,"props":2827,"children":2828},{},[2829],{"type":27,"value":2830},"Taking on an external project as a dependency also carries a number of risks however, risks that range from minor to downright catastrophic. Third-party projects can",{"type":22,"tag":111,"props":2832,"children":2833},{},[2834,2839,2844,2849,2854],{"type":22,"tag":115,"props":2835,"children":2836},{},[2837],{"type":27,"value":2838},"lose maintainers without warning, and consequently become insecure or nonfunctional through lack of updates,",{"type":22,"tag":115,"props":2840,"children":2841},{},[2842],{"type":27,"value":2843},"become compromised by malicious code which in turn compromises deployed environments or developer machines,",{"type":22,"tag":115,"props":2845,"children":2846},{},[2847],{"type":27,"value":2848},"lead to software bloat that negatively impacts application performance,",{"type":22,"tag":115,"props":2850,"children":2851},{},[2852],{"type":27,"value":2853},"cause maintenance problems by adding more API surface for developers to learn, and",{"type":22,"tag":115,"props":2855,"children":2856},{},[2857],{"type":27,"value":2858},"create painful upgrade/migration scenarios which consume developer resources.",{"type":22,"tag":23,"props":2860,"children":2861},{},[2862],{"type":27,"value":2863},"In other words, while there's a lot to be gained, there's also a lot that can occasionally go wrong. A fate suffered by many a software project goes something like this:",{"type":22,"tag":847,"props":2865,"children":2866},{},[2867,2872,2877,2882,2887],{"type":22,"tag":115,"props":2868,"children":2869},{},[2870],{"type":27,"value":2871},"At the outset of the project, a few core dependencies are selected. They're vetted to be well-maintained projects that will remain so for a long time.",{"type":22,"tag":115,"props":2873,"children":2874},{},[2875],{"type":27,"value":2876},"As time goes by, further dependencies are added one by one, each solving a particular problem. Whether it's justified to add them or not, each adds a little more to build times, to application size, and sometimes to application latency. The dependencies are rarely updated, because doing so is behind-the-scenes maintenance work whose value is difficult to appreciate; it incurs development and testing costs without improving the end user experience.",{"type":22,"tag":115,"props":2878,"children":2879},{},[2880],{"type":27,"value":2881},"One day, a high-priority dependency-related issue arises. Perhaps the old version of a library stops working and breaks the application until it is updated, or perhaps a critical feature cannot be implemented without the features provided by a newer version of a dependency. The team works to upgrade that dependency.",{"type":22,"tag":115,"props":2883,"children":2884},{},[2885],{"type":27,"value":2886},"In order to upgrade that one dependency, other related dependencies need to be upgraded as well. Soon, many dependencies have been upgraded, and many code changes have been made in order to migrate to their new APIs. The process takes many hours, and introduces many regressions because it was done as one sweeping change rather than as individual upgrades that could have been tested more granularly.",{"type":22,"tag":115,"props":2888,"children":2889},{},[2890],{"type":27,"value":2891},"What looked like a simple bug or feature request turns into tens of hours of maintenance work, and stakeholders are unhappy.",{"type":22,"tag":23,"props":2893,"children":2894},{},[2895],{"type":27,"value":2896},"There is a sweet spot somewhere in between \"reinventing the wheel\" and\n\"dependency hell\", and while it differs from project to project, it sits closer\nto the \"reinventing the wheel\" side than one might intuitively expect. That's\nbecause the positive effects of adding a dependency are usually very immediate\n(\"I had a problem, now this package solves my problem\"), whereas the negative\neffects are delayed (since they mostly relate to long-term maintenance).\nConsequently, over-use of third-party dependencies is a form of tech debt,\nquickly solving problems in the short term but causing larger ones in the long\nterm.",{"type":22,"tag":23,"props":2898,"children":2899},{},[2900],{"type":27,"value":2901},"In this post, we'll outline some rules to follow in order to minimize the risks posed by third-party dependencies, with the aim of ensuring the long-term stability of a project.",{"type":22,"tag":193,"props":2903,"children":2905},{"id":2904},"rule-1-minimize-the-number-of-dependencies",[2906],{"type":27,"value":2907},"Rule #1: Minimize the Number of Dependencies",{"type":22,"tag":23,"props":2909,"children":2910},{},[2911],{"type":27,"value":2912},"It may be obvious, but the fewer dependencies a project has, the lower the risk they pose. Here are some strategies to achieve this:",{"type":22,"tag":111,"props":2914,"children":2915},{},[2916,2934,2967,2985,3002],{"type":22,"tag":115,"props":2917,"children":2918},{},[2919,2924,2926],{"type":22,"tag":2598,"props":2920,"children":2921},{},[2922],{"type":27,"value":2923},"Consolidate libraries:",{"type":27,"value":2925}," If one library can do a reasonable job at filling the role of two or more others, then prefer the single multipurpose library over the multiple specialized ones.\n",{"type":22,"tag":111,"props":2927,"children":2928},{},[2929],{"type":22,"tag":115,"props":2930,"children":2931},{},[2932],{"type":27,"value":2933},"Sometimes the multipurpose option has limitations, or it is not as nice to use. However, it also means fewer library maintainers to rely on, fewer sources of documentation for developers to locate, and fewer package updates to manage.",{"type":22,"tag":115,"props":2935,"children":2936},{},[2937,2942,2944],{"type":22,"tag":2598,"props":2938,"children":2939},{},[2940],{"type":27,"value":2941},"Borrow Code:",{"type":27,"value":2943}," If the code being leveraged is small and self-contained, if library's license allows for copying the code, and if that code has little reason to change in the future, then copy it into the project rather than linking to it as a dependency. We gain the functionality without needing to maintain another dependency.\n",{"type":22,"tag":111,"props":2945,"children":2946},{},[2947,2957,2962],{"type":22,"tag":115,"props":2948,"children":2949},{},[2950,2952],{"type":27,"value":2951},"This is obviously not appropriate when it's illegal. ",{"type":22,"tag":267,"props":2953,"children":2954},{},[2955],{"type":27,"value":2956},"Check the library's license first.",{"type":22,"tag":115,"props":2958,"children":2959},{},[2960],{"type":27,"value":2961},"This not appropriate when the code has reason to change (in particular when its use has security implications). In that case, it's valuable to be able to continue accessing future updates from the upstream maintainer.",{"type":22,"tag":115,"props":2963,"children":2964},{},[2965],{"type":27,"value":2966},"Ensure that any borrowed code is tagged with code comments indicating its origin, in case it needs to be understood in the future.",{"type":22,"tag":115,"props":2968,"children":2969},{},[2970,2975,2977],{"type":22,"tag":2598,"props":2971,"children":2972},{},[2973],{"type":27,"value":2974},"Do it yourself:",{"type":27,"value":2976}," If the functionality is easy to implement yourself, then do it, rather than rely on a dependency.\n",{"type":22,"tag":111,"props":2978,"children":2979},{},[2980],{"type":22,"tag":115,"props":2981,"children":2982},{},[2983],{"type":27,"value":2984},"Look out for icebergs; many things look easy from the surface but are full of complexity underneath and are best left to domain experts.",{"type":22,"tag":115,"props":2986,"children":2987},{},[2988,2993,2995,3000],{"type":22,"tag":2598,"props":2989,"children":2990},{},[2991],{"type":27,"value":2992},"Keep it simple:",{"type":27,"value":2994}," Take on dependencies when you ",{"type":22,"tag":267,"props":2996,"children":2997},{},[2998],{"type":27,"value":2999},"must",{"type":27,"value":3001}," in order to deliver a maintainable product, not when it makes only marginal improvements.",{"type":22,"tag":115,"props":3003,"children":3004},{},[3005,3010],{"type":22,"tag":2598,"props":3006,"children":3007},{},[3008],{"type":27,"value":3009},"Separate your runtime and development dependencies:",{"type":27,"value":3011}," Many package managers have features or workflows for listing runtime dependencies separately from development dependencies. Since development dependencies aren't deployed, they pose fewer risks, so we you be somewhat more liberal with them than with runtime dependencies.",{"type":22,"tag":193,"props":3013,"children":3015},{"id":3014},"rule-2-audit-dependencies",[3016],{"type":27,"value":3017},"Rule #2: Audit Dependencies",{"type":22,"tag":23,"props":3019,"children":3020},{},[3021],{"type":27,"value":3022},"All projects ought to be audited before being added as a dependency: this means reviewing the project to ensure that it is secure, well-implemented, and well-maintained.",{"type":22,"tag":23,"props":3024,"children":3025},{},[3026,3028,3035],{"type":27,"value":3027},"For NPM and PyPi packages, ",{"type":22,"tag":30,"props":3029,"children":3032},{"href":3030,"rel":3031},"https://snyk.io/advisor/",[34],[3033],{"type":27,"value":3034},"Snyk Advisor",{"type":27,"value":3036}," is a useful\nstarting point for assessing a package's health. It algorithmically scores a\npackage based on things like reported security vulnerabilities, frequency of\nmaintenance, number of contributors, and number of downloads.",{"type":22,"tag":23,"props":3038,"children":3039},{},[3040],{"type":27,"value":3041},"Here is a checklist for auditing a candidate (or existing) dependency:",{"type":22,"tag":847,"props":3043,"children":3044},{},[3045,3063,3084,3175],{"type":22,"tag":115,"props":3046,"children":3047},{},[3048,3050],{"type":27,"value":3049},"Security\n",{"type":22,"tag":111,"props":3051,"children":3052},{},[3053,3058],{"type":22,"tag":115,"props":3054,"children":3055},{},[3056],{"type":27,"value":3057},"☐ Has no history of security vulnerabilities, OR past security vulnerabilities have been taken seriously and patched promptly.",{"type":22,"tag":115,"props":3059,"children":3060},{},[3061],{"type":27,"value":3062},"☐ Has a documented policy for reporting and responding to security issues.",{"type":22,"tag":115,"props":3064,"children":3065},{},[3066,3068],{"type":27,"value":3067},"Popularity\n",{"type":22,"tag":111,"props":3069,"children":3070},{},[3071],{"type":22,"tag":115,"props":3072,"children":3073},{},[3074,3076],{"type":27,"value":3075},"☐ Has a healthy popularity (e.g. downloads per month).\n",{"type":22,"tag":111,"props":3077,"children":3078},{},[3079],{"type":22,"tag":115,"props":3080,"children":3081},{},[3082],{"type":27,"value":3083},"This is a proxy for how much demand there is to continue maintaining the project, and how likely it is that it would be forked by the community if the current maintainers were to disappear.",{"type":22,"tag":115,"props":3085,"children":3086},{},[3087,3089],{"type":27,"value":3088},"Maintenance\n",{"type":22,"tag":111,"props":3090,"children":3091},{},[3092,3112,3131,3136,3149,3162],{"type":22,"tag":115,"props":3093,"children":3094},{},[3095,3097],{"type":27,"value":3096},"☐ Has a steady pace of development up to present-day.\n",{"type":22,"tag":111,"props":3098,"children":3099},{},[3100],{"type":22,"tag":115,"props":3101,"children":3102},{},[3103,3105,3110],{"type":27,"value":3104},"Check for projects which may ",{"type":22,"tag":267,"props":3106,"children":3107},{},[3108],{"type":27,"value":3109},"look",{"type":27,"value":3111}," active due to historic popularity, but which are no longer being updated.",{"type":22,"tag":115,"props":3113,"children":3114},{},[3115,3117],{"type":27,"value":3116},"☐ Has a steady pace of releases.\n",{"type":22,"tag":111,"props":3118,"children":3119},{},[3120],{"type":22,"tag":115,"props":3121,"children":3122},{},[3123,3125,3129],{"type":27,"value":3124},"Note that this does ",{"type":22,"tag":267,"props":3126,"children":3127},{},[3128],{"type":27,"value":869},{"type":27,"value":3130}," follow automatically from a steady development pace (e.g. devs plugging away for years at a shiny new rewrite).",{"type":22,"tag":115,"props":3132,"children":3133},{},[3134],{"type":27,"value":3135},"☐ Has a history of prompt handling of bug reports and feature requests.",{"type":22,"tag":115,"props":3137,"children":3138},{},[3139,3141],{"type":27,"value":3140},"☐ Respects semantic versioning policy.\n",{"type":22,"tag":111,"props":3142,"children":3143},{},[3144],{"type":22,"tag":115,"props":3145,"children":3146},{},[3147],{"type":27,"value":3148},"It's a major issue if minor/patch releases can't be trusted not to have breaking changes.",{"type":22,"tag":115,"props":3150,"children":3151},{},[3152,3154],{"type":27,"value":3153},"☐ Provides thorough migration guides for major version upgrades.\n",{"type":22,"tag":111,"props":3155,"children":3156},{},[3157],{"type":22,"tag":115,"props":3158,"children":3159},{},[3160],{"type":27,"value":3161},"It's a major issue if you can't update a major version number with confidence.",{"type":22,"tag":115,"props":3163,"children":3164},{},[3165,3167],{"type":27,"value":3166},"☐ The source code looks like you could maintain or extend it if you needed to.\n",{"type":22,"tag":111,"props":3168,"children":3169},{},[3170],{"type":22,"tag":115,"props":3171,"children":3172},{},[3173],{"type":27,"value":3174},"You might need to, if the library gets abandoned someday.",{"type":22,"tag":115,"props":3176,"children":3177},{},[3178,3180],{"type":27,"value":3179},"Community\n",{"type":22,"tag":111,"props":3181,"children":3182},{},[3183,3201,3214],{"type":22,"tag":115,"props":3184,"children":3185},{},[3186,3188],{"type":27,"value":3187},"☐ Has a healthy number of contributors.\n",{"type":22,"tag":111,"props":3189,"children":3190},{},[3191,3196],{"type":22,"tag":115,"props":3192,"children":3193},{},[3194],{"type":27,"value":3195},"Use a tool like GitHub's \"Insights\" tab to view contributor statistics.",{"type":22,"tag":115,"props":3197,"children":3198},{},[3199],{"type":27,"value":3200},"The definition of \"healthy\" will depend on the project, but generally you want to see that the project owner accepts contributions from others, and that those contributions make up a meaningful portion of the codebase.",{"type":22,"tag":115,"props":3202,"children":3203},{},[3204,3206],{"type":27,"value":3205},"☐ Has good developer documentation to assist new contributors.\n",{"type":22,"tag":111,"props":3207,"children":3208},{},[3209],{"type":22,"tag":115,"props":3210,"children":3211},{},[3212],{"type":27,"value":3213},"This shows interest in having many contributors rather than a one-man show, and also helps you if you ever had to pick up an abandoned project.",{"type":22,"tag":115,"props":3215,"children":3216},{},[3217,3219],{"type":27,"value":3218},"☐ Has funding (e.g. corporate sponsorships or donations).\n",{"type":22,"tag":111,"props":3220,"children":3221},{},[3222],{"type":22,"tag":115,"props":3223,"children":3224},{},[3225],{"type":27,"value":3226},"A project with funding is more likely to stick around than one that's maintained by hobbyists.",{"type":22,"tag":23,"props":3228,"children":3229},{},[3230],{"type":27,"value":3231},"If more than one of those boxes is unchecked, then the package is at higher risk\nof posing future security or maintenance problems, and consequently it should be avoided if a\nbetter alternative exists, or else the risks should be communicated to project management\nand/or stakeholders as appropriate.",{"type":22,"tag":193,"props":3233,"children":3235},{"id":3234},"rule-3-keep-dependencies-up-to-date",[3236],{"type":27,"value":3237},"Rule #3: Keep Dependencies Up-to-date",{"type":22,"tag":23,"props":3239,"children":3240},{},[3241],{"type":27,"value":3242},"Typically, a project's dependencies should be updated on a regular cadence. This is not just to receive security patches and other bugfixes, nor is it so that developers can have the newest and shiniest features; it's to prevent the situation described earlier in this post where out-of-date dependencies need to be suddenly and immediately made current, a long and error-prone process that can grind a project to a halt.",{"type":22,"tag":23,"props":3244,"children":3245},{},[3246],{"type":27,"value":3247},"Like other forms of technical debt reduction however, it can be difficult to convince stakeholders of the importance of budgeting some time away from immediate end product improvements and towards keeping dependencies up-to-date. At Art+Logic, we approach this topic with our clients by:",{"type":22,"tag":111,"props":3249,"children":3250},{},[3251,3256,3261],{"type":22,"tag":115,"props":3252,"children":3253},{},[3254],{"type":27,"value":3255},"Building the client's trust in us, by establishing a track record of rapidly delivering them high-quality results. When a client trusts that we're not out to talk them into things they don't need, they're more likely to be interested in hearing the team's maintenance priorities.",{"type":22,"tag":115,"props":3257,"children":3258},{},[3259],{"type":27,"value":3260},"Communicating the trade-offs of the chosen development pattern. It's not necessarily a bad decision for a project to prioritize feature development over reducing technical debt or risk; what's important however is that the client understands the trade-off they're making.",{"type":22,"tag":115,"props":3262,"children":3263},{},[3264],{"type":27,"value":3265},"When possible, agreeing upon a regular maintenance budget that the team can use for, among other things, keeping dependencies up-to-date. This relieves both the team and the client from having to routinely negotiate maintenance efforts, and it serves as an explicit agreement between the team and the client of what the balance should be between short-term features and long-term stability.",{"type":22,"tag":23,"props":3267,"children":3268},{},[3269,3271,3276],{"type":27,"value":3270},"As far as the procedure goes for applying updates, the focus should be on doing it as atomically as possible; that is, to do it in small steps and return to a functioning application in between each step. If everything is updated all at once, then in the likely scenario that this breaks the application until some fixes are made, it is ",{"type":22,"tag":267,"props":3272,"children":3273},{},[3274],{"type":27,"value":3275},"much",{"type":27,"value":3277}," harder to identify the root causes of each bug. A little time spent recompiling and smoke-testing the application in between each upgrade saves a lot of time spent struggling to get a broken application working again.",{"type":22,"tag":193,"props":3279,"children":3281},{"id":3280},"final-thoughts-and-language-specific-advice",[3282],{"type":27,"value":3283},"Final Thoughts, and Language-specific Advice",{"type":22,"tag":23,"props":3285,"children":3286},{},[3287,3289,3294],{"type":27,"value":3288},"Often, the biggest challenge with managing the dependencies of a software project is just finding the time or the budget to do it. Since the consequences of letting dependencies gather dust for too long can be so severe however, it's worth finding a away. It's also important for teams to always be asking themselves the question: \"do we really ",{"type":22,"tag":267,"props":3290,"children":3291},{},[3292],{"type":27,"value":3293},"need",{"type":27,"value":3295}," this library?\" Keeping the dependency list small is the easiest way to avoid complications further down the road.",{"type":22,"tag":23,"props":3297,"children":3298},{},[3299],{"type":27,"value":3300},"Each language and ecosystem has its own tools which can help with these efforts. We'll close out here with a few recommendations for programming languages we frequently work with:",{"type":22,"tag":1481,"props":3302,"children":3304},{"id":3303},"javascript",[3305],{"type":27,"value":3306},"JavaScript",{"type":22,"tag":111,"props":3308,"children":3309},{},[3310,3344],{"type":22,"tag":115,"props":3311,"children":3312},{},[3313,3315,3326,3328,3334,3336,3342],{"type":27,"value":3314},"Use ",{"type":22,"tag":30,"props":3316,"children":3319},{"href":3317,"rel":3318},"https://www.npmjs.com/package/npm-check-updates",[34],[3320],{"type":22,"tag":486,"props":3321,"children":3323},{"className":3322},[],[3324],{"type":27,"value":3325},"npm-check-updates",{"type":27,"value":3327}," to conveniently check for package updates. It has a ",{"type":22,"tag":486,"props":3329,"children":3331},{"className":3330},[],[3332],{"type":27,"value":3333},"--upgrade",{"type":27,"value":3335}," option which writes all version updates to ",{"type":22,"tag":486,"props":3337,"children":3339},{"className":3338},[],[3340],{"type":27,"value":3341},"package.json",{"type":27,"value":3343}," automatically; it's often worth giving that a shot to see if you're lucky enough to have no issues, and then if there are breakages simply revert it and apply the updates one by one.",{"type":22,"tag":115,"props":3345,"children":3346},{},[3347,3349,3360,3362,3368,3370,3375,3377,3383,3385,3390,3392,3397,3399,3405,3407,3412,3414,3419,3421,3426],{"type":27,"value":3348},"Get in the habit of using ",{"type":22,"tag":30,"props":3350,"children":3353},{"href":3351,"rel":3352},"https://docs.npmjs.com/cli/v9/commands/npm-ci",[34],[3354],{"type":22,"tag":486,"props":3355,"children":3357},{"className":3356},[],[3358],{"type":27,"value":3359},"npm ci",{"type":27,"value":3361}," as your default alternative to ",{"type":22,"tag":486,"props":3363,"children":3365},{"className":3364},[],[3366],{"type":27,"value":3367},"npm install",{"type":27,"value":3369},". The ",{"type":22,"tag":486,"props":3371,"children":3373},{"className":3372},[],[3374],{"type":27,"value":3367},{"type":27,"value":3376}," command is commonly used in order to make locally installed packages match what's defined in the repository's ",{"type":22,"tag":486,"props":3378,"children":3380},{"className":3379},[],[3381],{"type":27,"value":3382},"package*.json",{"type":27,"value":3384}," files, but ",{"type":22,"tag":486,"props":3386,"children":3388},{"className":3387},[],[3389],{"type":27,"value":3367},{"type":27,"value":3391}," ",{"type":22,"tag":267,"props":3393,"children":3394},{},[3395],{"type":27,"value":3396},"updates",{"type":27,"value":3398}," the ",{"type":22,"tag":486,"props":3400,"children":3402},{"className":3401},[],[3403],{"type":27,"value":3404},"package-lock.json",{"type":27,"value":3406}," file if it finds newer versions. This is typically not something you want to happen implicitly! ",{"type":22,"tag":486,"props":3408,"children":3410},{"className":3409},[],[3411],{"type":27,"value":3359},{"type":27,"value":3413}," will only install ",{"type":22,"tag":267,"props":3415,"children":3416},{},[3417],{"type":27,"value":3418},"exactly",{"type":27,"value":3420}," what's in ",{"type":22,"tag":486,"props":3422,"children":3424},{"className":3423},[],[3425],{"type":27,"value":3404},{"type":27,"value":3427},", leaving upgrades to happen as a separate and explicit action.",{"type":22,"tag":1481,"props":3429,"children":3431},{"id":3430},"python",[3432],{"type":27,"value":3433},"Python",{"type":22,"tag":111,"props":3435,"children":3436},{},[3437],{"type":22,"tag":115,"props":3438,"children":3439},{},[3440,3442,3449,3451,3457,3459,3465,3467,3473,3475,3481],{"type":27,"value":3441},"Consider using ",{"type":22,"tag":30,"props":3443,"children":3446},{"href":3444,"rel":3445},"https://python-poetry.org/",[34],[3447],{"type":27,"value":3448},"Poetry",{"type":27,"value":3450}," as a package manager, rather than a ",{"type":22,"tag":486,"props":3452,"children":3454},{"className":3453},[],[3455],{"type":27,"value":3456},"pip freeze > requirements.txt",{"type":27,"value":3458}," approach. ",{"type":22,"tag":486,"props":3460,"children":3462},{"className":3461},[],[3463],{"type":27,"value":3464},"pip freeze",{"type":27,"value":3466}," lumps transitive dependencies in with direct dependencies, making it impractical to go back and determine which packages are still in use. Poetry on the other hand uses a \"lockfile\" approach, where there are two files: the manifest defining the desired packages (",{"type":22,"tag":486,"props":3468,"children":3470},{"className":3469},[],[3471],{"type":27,"value":3472},"pyproject.toml",{"type":27,"value":3474},"), and a separate lockfile that specifies the exact version to install for each package (",{"type":22,"tag":486,"props":3476,"children":3478},{"className":3477},[],[3479],{"type":27,"value":3480},"poetry.lock",{"type":27,"value":3482},"). The lockfile provides the reproducibility needed for deployments, while the manifest file preserves your intentions, making it easy to review dependencies or to automatically upgrade them.",{"title":8,"searchDepth":755,"depth":755,"links":3484},[3485,3486,3487,3488],{"id":2904,"depth":758,"text":2907},{"id":3014,"depth":758,"text":3017},{"id":3234,"depth":758,"text":3237},{"id":3280,"depth":758,"text":3283,"children":3489},[3490,3491],{"id":3303,"depth":755,"text":3306},{"id":3430,"depth":755,"text":3433},"content:phendry:2023-07-28:DependencyManagement.md","phendry/2023-07-28/DependencyManagement.md","phendry/2023-07-28/DependencyManagement",{"user":774,"name":775},{"_path":3497,"_dir":3498,"_draft":7,"_partial":7,"_locale":8,"title":3499,"description":3500,"image":3501,"tags":3502,"excerpt":3500,"publishDate":3503,"body":3504,"_type":767,"_id":3801,"_source":769,"_file":3802,"_stem":3803,"_extension":772,"author":3804},"/phendry/2023-05-16/doyouneedacsspreprocessor","2023-05-16","Do You Need a CSS Preprocessor in 2023?","CSS preprocessors like Less, Sass and Stylus have long provided powerful features that vanilla CSS lacked: variables, nesting of rulesets, mixins, control flow constructs, etc. These days however, the feature gap is considerably narrower, and it's not so clear that the benefits of a preprocessor outweight the burdens of setting it up.","/phendry/2023-05-16/img/css_preprocessor_header.png",[12],"2023-01-01",{"type":19,"children":3505,"toc":3799},[3506,3536,3554,3577,3605,3610,3784,3789,3794],{"type":22,"tag":23,"props":3507,"children":3508},{},[3509,3511,3518,3519,3526,3527,3534],{"type":27,"value":3510},"CSS preprocessors like ",{"type":22,"tag":30,"props":3512,"children":3515},{"href":3513,"rel":3514},"https://lesscss.org/",[34],[3516],{"type":27,"value":3517},"Less",{"type":27,"value":236},{"type":22,"tag":30,"props":3520,"children":3523},{"href":3521,"rel":3522},"https://sass-lang.com/",[34],[3524],{"type":27,"value":3525},"Sass",{"type":27,"value":1441},{"type":22,"tag":30,"props":3528,"children":3531},{"href":3529,"rel":3530},"https://stylus-lang.com/",[34],[3532],{"type":27,"value":3533},"Stylus",{"type":27,"value":3535}," have long provided powerful features that vanilla CSS lacked: variables, nesting of rulesets, mixins, control flow constructs, etc. These days however, the feature gap is considerably narrower, and it's not so clear that the benefits of a preprocessor outweight the burdens of setting it up.",{"type":22,"tag":23,"props":3537,"children":3538},{},[3539,3541,3552],{"type":27,"value":3540},"It's not terribly hard to set up a CSS preprocessor, but still, it adds complexity to a frontend build system that oftentimes is frightfully complicated to begin with. The project's build system needs to transform files to CSS, which typically involves adding another project dependency (e.g. ",{"type":22,"tag":30,"props":3542,"children":3545},{"href":3543,"rel":3544},"https://webpack.js.org/loaders/sass-loader/",[34],[3546],{"type":22,"tag":486,"props":3547,"children":3549},{"className":3548},[],[3550],{"type":27,"value":3551},"sass-loader",{"type":27,"value":3553}," for Sass in Webpack). Quirks with the ways the preprocessor's import system differs from the project's JavaScript module system can cause issues. Getting sourcemaps to work so that styles can be viewed/debugged in the browser can similarly require fiddling. There's also the added cognitive load it places on developers, who will need to learn the preprocessor's ins and outs in addition to those of CSS itself. Projects should be kept as lean as possible, so it's unwise to add additional languages and build tooling unless the benefit they provide is substantial.",{"type":22,"tag":23,"props":3555,"children":3556},{},[3557,3559,3566,3568,3575],{"type":27,"value":3558},"As for the benefits, well, it's true that you don't have to look for long before you find one that vanilla CSS still lacks; ",{"type":22,"tag":30,"props":3560,"children":3563},{"href":3561,"rel":3562},"https://caniuse.com/css-nesting",[34],[3564],{"type":27,"value":3565},"nesting",{"type":27,"value":3567}," for example only just recently (as of May 2023) saw support land in Chrome and Safari, with no support yet in Firefox. But there's one extra tool which makes the difference here, and it's ",{"type":22,"tag":30,"props":3569,"children":3572},{"href":3570,"rel":3571},"https://postcss.org/",[34],[3573],{"type":27,"value":3574},"PostCSS",{"type":27,"value":3576},": with PostCSS, not-yet-supported CSS proposals can be transpiled into widely-supported CSS, which enables enough nice CSS features that you arguably don't need a preprocessor like Sass or Less.",{"type":22,"tag":23,"props":3578,"children":3579},{},[3580,3582,3587,3589,3594,3596,3603],{"type":27,"value":3581},"That may seem like a disingenuous take, given that PostCSS ",{"type":22,"tag":267,"props":3583,"children":3584},{},[3585],{"type":27,"value":3586},"itself",{"type":27,"value":3588}," is a plugin-based CSS preprocessor. But the difference is, ",{"type":22,"tag":267,"props":3590,"children":3591},{},[3592],{"type":27,"value":3593},"you're already using PostCSS anyway",{"type":27,"value":3595},". Or at least, you should be, because of plugins like ",{"type":22,"tag":30,"props":3597,"children":3600},{"href":3598,"rel":3599},"https://github.com/postcss/autoprefixer",[34],[3601],{"type":27,"value":3602},"autoprefixer",{"type":27,"value":3604}," which automatically improve the browser compatibility of your styles. If you've already integrated PostCSS into your project, then it costs nothing (in terms of time and project complexity) to leverage it further.",{"type":22,"tag":23,"props":3606,"children":3607},{},[3608],{"type":27,"value":3609},"Here's a quick overview of how vanilla CSS and PostCSS plugins compare to the major selling points of other CSS preprocessors:",{"type":22,"tag":847,"props":3611,"children":3612},{},[3613,3631,3650,3660,3670,3734],{"type":22,"tag":115,"props":3614,"children":3615},{},[3616,3621,3623,3630],{"type":22,"tag":2598,"props":3617,"children":3618},{},[3619],{"type":27,"value":3620},"Variables:",{"type":27,"value":3622}," CSS Variables have ",{"type":22,"tag":30,"props":3624,"children":3627},{"href":3625,"rel":3626},"https://caniuse.com/css-variables",[34],[3628],{"type":27,"value":3629},"wide browser support",{"type":27,"value":71},{"type":22,"tag":115,"props":3632,"children":3633},{},[3634,3639,3641,3648],{"type":22,"tag":2598,"props":3635,"children":3636},{},[3637],{"type":27,"value":3638},"Nesting:",{"type":27,"value":3640}," Nesting is a real win for code readability when it's not overused. This is a ",{"type":22,"tag":30,"props":3642,"children":3645},{"href":3643,"rel":3644},"https://cssdb.org/#nesting-rules",[34],[3646],{"type":27,"value":3647},"Stage 2 CSS proposal",{"type":27,"value":3649},", meaning it requires a PostCSS plugin and is subject to change, but it's comparable in terms of capabilities to nesting in other preprocessors.",{"type":22,"tag":115,"props":3651,"children":3652},{},[3653,3658],{"type":22,"tag":2598,"props":3654,"children":3655},{},[3656],{"type":27,"value":3657},"Mixins and partials:",{"type":27,"value":3659}," CSS does not provide these concepts, however with variables and classes providing a lot of overlap capability-wise in terms of factoring out common rules, it's debatable whether the added capabilities of mixins/partials are a benefit or an overcomplication.",{"type":22,"tag":115,"props":3661,"children":3662},{},[3663,3668],{"type":22,"tag":2598,"props":3664,"children":3665},{},[3666],{"type":27,"value":3667},"Functions and Control Flow:",{"type":27,"value":3669}," Some preprocessors like Sass provide conditional and looping constructs, or even full-on scripting capabilities. CSS doesn't provide this, but it's another case where arguably this is achieved more cleanly in JavaScript rather than introducing scripting into stylesheets.",{"type":22,"tag":115,"props":3671,"children":3672},{},[3673,3678,3680],{"type":22,"tag":2598,"props":3674,"children":3675},{},[3676],{"type":27,"value":3677},"Color Functions:",{"type":27,"value":3679}," Most fancy ways to manipulate colors are not yet standardized in CSS, but existing proposals cover a lot of what preprocessors offer:\n",{"type":22,"tag":111,"props":3681,"children":3682},{},[3683,3703,3716],{"type":22,"tag":115,"props":3684,"children":3685},{},[3686,3692,3694,3701],{"type":22,"tag":486,"props":3687,"children":3689},{"className":3688},[],[3690],{"type":27,"value":3691},"color-mix()",{"type":27,"value":3693}," is a ",{"type":22,"tag":30,"props":3695,"children":3698},{"href":3696,"rel":3697},"https://cssdb.org/#color-contrast",[34],[3699],{"type":27,"value":3700},"Stage 2",{"type":27,"value":3702}," proposal that enables dynamically mixing colors together.",{"type":22,"tag":115,"props":3704,"children":3705},{},[3706,3708,3714],{"type":27,"value":3707},"Relative color syntax is a ",{"type":22,"tag":30,"props":3709,"children":3712},{"href":3710,"rel":3711},"https://cssdb.org/#relative-color-syntax",[34],[3713],{"type":27,"value":3700},{"type":27,"value":3715}," proposal which allows defining one color relative to another one.",{"type":22,"tag":115,"props":3717,"children":3718},{},[3719,3725,3727,3733],{"type":22,"tag":486,"props":3720,"children":3722},{"className":3721},[],[3723],{"type":27,"value":3724},"color-contrast()",{"type":27,"value":3726}," helps choose the color that contrasts the most, though it is an experimental ",{"type":22,"tag":30,"props":3728,"children":3730},{"href":3696,"rel":3729},[34],[3731],{"type":27,"value":3732},"Stage 1 proposal",{"type":27,"value":71},{"type":22,"tag":115,"props":3735,"children":3736},{},[3737,3742,3744,3750,3752,3758,3760,3766,3768,3775,3777,3782],{"type":22,"tag":2598,"props":3738,"children":3739},{},[3740],{"type":27,"value":3741},"Concatenating class names:",{"type":27,"value":3743}," Some preprocessors have a syntax where a nested block's selector can be concatenated with the parent's, e.g. to combine ",{"type":22,"tag":486,"props":3745,"children":3747},{"className":3746},[],[3748],{"type":27,"value":3749},".parent",{"type":27,"value":3751}," with ",{"type":22,"tag":486,"props":3753,"children":3755},{"className":3754},[],[3756],{"type":27,"value":3757},"__child",{"type":27,"value":3759}," to form a ",{"type":22,"tag":486,"props":3761,"children":3763},{"className":3762},[],[3764],{"type":27,"value":3765},".parent__child",{"type":27,"value":3767}," class in ",{"type":22,"tag":30,"props":3769,"children":3772},{"href":3770,"rel":3771},"https://getbem.com/introduction/",[34],[3773],{"type":27,"value":3774},"BEM",{"type":27,"value":3776}," convention rather than writing an un-nested ",{"type":22,"tag":486,"props":3778,"children":3780},{"className":3779},[],[3781],{"type":27,"value":3765},{"type":27,"value":3783}," selector. CSS doesn't provide this either, but it's a mild convenience that comes at the expense of some clarity.",{"type":22,"tag":23,"props":3785,"children":3786},{},[3787],{"type":27,"value":3788},"Overall, variables seem like the key feature that was missing from CSS when preprocessors first gained popularity, but they're widely-adopted now and enable a lot of code reuse possibilities. Nesting is also considered essential by many, which currently requires enabling one Stage 2 proposal via a PostCSS plugin. It may feel scary to enable a proposal whose stage implies \"relatively unstable and subject to change\", but nesting support has recently landed in Chrome and Safari, so it's looking more standardized than its stage would suggest. And after all, Sass, Less and Stylus all have a history of breaking changes, so a certain level of risk of future breaking changes is inherent to using any of these tools.",{"type":22,"tag":23,"props":3790,"children":3791},{},[3792],{"type":27,"value":3793},"In other words, with one PostCSS plugin, you can write plain CSS in 2023 whose clean maintainability rivals what's possible in Sass, Less or Stylus. And there are dozens of other plugins to reach for if your use case ends up calling for something specific (such as fancy color functions).",{"type":22,"tag":23,"props":3795,"children":3796},{},[3797],{"type":27,"value":3798},"With this being the case, there's no reason to stop using a preprocessor that's already integrated into your codebase; that would be a time-consuming and regression-prone migration, for little gain. But for a greenfield project in 2023, I really don't think it's worthwhile any longer to complicate a project with yet another language and build step. Why not keep things simple and stick to plain CSS?",{"title":8,"searchDepth":755,"depth":755,"links":3800},[],"content:phendry:2023-05-16:DoYouNeedACSSPreprocessor.md","phendry/2023-05-16/DoYouNeedACSSPreprocessor.md","phendry/2023-05-16/DoYouNeedACSSPreprocessor",{"user":774,"name":775},{"_path":3806,"_dir":3807,"_draft":7,"_partial":7,"_locale":8,"title":3808,"description":3809,"tags":3810,"image":3811,"publishDate":3807,"excerpt":3809,"body":3812,"_type":767,"_id":5890,"_source":769,"_file":5891,"_stem":5892,"_extension":772,"author":5893},"/phendry/2021-08-15/exploringdependenttypesinidris","2021-08-15","Exploring Dependent Types in Idris","When I'm not coding the \"impossible\" at Art+Logic, I take a lot of interest in new programming technologies and paradigms; even if they're not yet viable for use in production, there can often be takeaways for improving your everyday code.",[12],"/phendry/2021-08-15/img/dependent-types.jpg",{"type":19,"children":3813,"toc":5880},[3814,3818,3832,3838,3857,3862,3899,3936,4009,4014,4076,4081,4093,4180,4215,4241,4247,4260,4392,4397,4509,4560,4586,4793,4806,4925,4930,5092,5111,5130,5248,5253,5263,5290,5295,5436,5442,5454,5476,5481,5487,5492,5498,5503,5509,5520,5541,5606,5635,5810,5844,5849,5855,5876],{"type":22,"tag":23,"props":3815,"children":3816},{},[3817],{"type":27,"value":3809},{"type":22,"tag":23,"props":3819,"children":3820},{},[3821,3823,3830],{"type":27,"value":3822},"My current fascination is the ",{"type":22,"tag":30,"props":3824,"children":3827},{"href":3825,"rel":3826},"https://www.idris-lang.org/",[34],[3828],{"type":27,"value":3829},"Idris",{"type":27,"value":3831}," programming language, a research language built around making dependent types practical. This is a quick primer on what dependent types are, how they work in Idris, and how they can change the way you think about types in other languages; we'll assume no prior knowledge of Idris or of purely functional languages in general, but a basic familiarity with functional programming will make things easier to follow.",{"type":22,"tag":193,"props":3833,"children":3835},{"id":3834},"what-is-a-dependent-type",[3836],{"type":27,"value":3837},"What is a Dependent Type?",{"type":22,"tag":23,"props":3839,"children":3840},{},[3841,3843,3848,3850,3855],{"type":27,"value":3842},"A ",{"type":22,"tag":267,"props":3844,"children":3845},{},[3846],{"type":27,"value":3847},"dependent type",{"type":27,"value":3849}," is a type whose definition depends on a value. More generally, Idris has ",{"type":22,"tag":267,"props":3851,"children":3852},{},[3853],{"type":27,"value":3854},"first-class types",{"type":27,"value":3856},", meaning that types can be computed and manipulated like any other language construct (e.g. functions or values).",{"type":22,"tag":23,"props":3858,"children":3859},{},[3860],{"type":27,"value":3861},"In Idris for example, it's no problem to define a function with a type like this:",{"type":22,"tag":886,"props":3863,"children":3867},{"className":3864,"code":3865,"language":3866,"meta":8,"style":8},"language-haskell shiki shiki-themes github-light github-dark","stringOrInt : Bool -> Type\n","haskell",[3868],{"type":22,"tag":486,"props":3869,"children":3870},{"__ignoreMap":8},[3871],{"type":22,"tag":896,"props":3872,"children":3873},{"class":898,"line":899},[3874,3879,3884,3889,3894],{"type":22,"tag":896,"props":3875,"children":3876},{"style":903},[3877],{"type":27,"value":3878},"stringOrInt ",{"type":22,"tag":896,"props":3880,"children":3881},{"style":1691},[3882],{"type":27,"value":3883},":",{"type":22,"tag":896,"props":3885,"children":3886},{"style":1632},[3887],{"type":27,"value":3888}," Bool",{"type":22,"tag":896,"props":3890,"children":3891},{"style":1691},[3892],{"type":27,"value":3893}," ->",{"type":22,"tag":896,"props":3895,"children":3896},{"style":1632},[3897],{"type":27,"value":3898}," Type\n",{"type":22,"tag":23,"props":3900,"children":3901},{},[3902,3904,3910,3912,3918,3920,3926,3928,3934],{"type":27,"value":3903},"The ",{"type":22,"tag":486,"props":3905,"children":3907},{"className":3906},[],[3908],{"type":27,"value":3909},"stringOrInt :",{"type":27,"value":3911}," notation begins a type definition, and ",{"type":22,"tag":486,"props":3913,"children":3915},{"className":3914},[],[3916],{"type":27,"value":3917},"Bool -> Type",{"type":27,"value":3919}," defines a function of one parameter (",{"type":22,"tag":486,"props":3921,"children":3923},{"className":3922},[],[3924],{"type":27,"value":3925},"Bool",{"type":27,"value":3927},") with a return type of ",{"type":22,"tag":486,"props":3929,"children":3931},{"className":3930},[],[3932],{"type":27,"value":3933},"Type",{"type":27,"value":3935},". It could be implemented like so:",{"type":22,"tag":886,"props":3937,"children":3939},{"className":3864,"code":3938,"language":3866,"meta":8,"style":8},"stringOrInt : Bool -> Type\nstringOrInt False = String\nstringOrInt True = Int\n",[3940],{"type":22,"tag":486,"props":3941,"children":3942},{"__ignoreMap":8},[3943,3966,3988],{"type":22,"tag":896,"props":3944,"children":3945},{"class":898,"line":899},[3946,3950,3954,3958,3962],{"type":22,"tag":896,"props":3947,"children":3948},{"style":903},[3949],{"type":27,"value":3878},{"type":22,"tag":896,"props":3951,"children":3952},{"style":1691},[3953],{"type":27,"value":3883},{"type":22,"tag":896,"props":3955,"children":3956},{"style":1632},[3957],{"type":27,"value":3888},{"type":22,"tag":896,"props":3959,"children":3960},{"style":1691},[3961],{"type":27,"value":3893},{"type":22,"tag":896,"props":3963,"children":3964},{"style":1632},[3965],{"type":27,"value":3898},{"type":22,"tag":896,"props":3967,"children":3968},{"class":898,"line":758},[3969,3973,3978,3983],{"type":22,"tag":896,"props":3970,"children":3971},{"style":903},[3972],{"type":27,"value":3878},{"type":22,"tag":896,"props":3974,"children":3975},{"style":1632},[3976],{"type":27,"value":3977},"False",{"type":22,"tag":896,"props":3979,"children":3980},{"style":1691},[3981],{"type":27,"value":3982}," =",{"type":22,"tag":896,"props":3984,"children":3985},{"style":1632},[3986],{"type":27,"value":3987}," String\n",{"type":22,"tag":896,"props":3989,"children":3990},{"class":898,"line":755},[3991,3995,4000,4004],{"type":22,"tag":896,"props":3992,"children":3993},{"style":903},[3994],{"type":27,"value":3878},{"type":22,"tag":896,"props":3996,"children":3997},{"style":1632},[3998],{"type":27,"value":3999},"True",{"type":22,"tag":896,"props":4001,"children":4002},{"style":1691},[4003],{"type":27,"value":3982},{"type":22,"tag":896,"props":4005,"children":4006},{"style":1632},[4007],{"type":27,"value":4008}," Int\n",{"type":22,"tag":23,"props":4010,"children":4011},{},[4012],{"type":27,"value":4013},"Idris (and Haskell, the language its syntax is based on) lets you implement a function by pattern-matching on its arguments. So the full definition above can be understood as",{"type":22,"tag":847,"props":4015,"children":4016},{},[4017,4042,4060],{"type":22,"tag":115,"props":4018,"children":4019},{},[4020,4022,4028,4030,4035,4037],{"type":27,"value":4021},"Define a function ",{"type":22,"tag":486,"props":4023,"children":4025},{"className":4024},[],[4026],{"type":27,"value":4027},"stringOrInt",{"type":27,"value":4029}," which takes a ",{"type":22,"tag":486,"props":4031,"children":4033},{"className":4032},[],[4034],{"type":27,"value":3925},{"type":27,"value":4036}," and returns a ",{"type":22,"tag":486,"props":4038,"children":4040},{"className":4039},[],[4041],{"type":27,"value":3933},{"type":22,"tag":115,"props":4043,"children":4044},{},[4045,4047,4052,4054],{"type":27,"value":4046},"If the argument matches ",{"type":22,"tag":486,"props":4048,"children":4050},{"className":4049},[],[4051],{"type":27,"value":3977},{"type":27,"value":4053},", then return ",{"type":22,"tag":486,"props":4055,"children":4057},{"className":4056},[],[4058],{"type":27,"value":4059},"String",{"type":22,"tag":115,"props":4061,"children":4062},{},[4063,4064,4069,4070],{"type":27,"value":4046},{"type":22,"tag":486,"props":4065,"children":4067},{"className":4066},[],[4068],{"type":27,"value":3999},{"type":27,"value":4053},{"type":22,"tag":486,"props":4071,"children":4073},{"className":4072},[],[4074],{"type":27,"value":4075},"Int",{"type":22,"tag":23,"props":4077,"children":4078},{},[4079],{"type":27,"value":4080},"The compiler is smart enough to know that you've covered all possible cases in the pattern match. This is how functions are defined in a purely functional language: declaratively, by describing the expression that calculates the result (rather than by describing a sequence of imperative steps directly).",{"type":22,"tag":23,"props":4082,"children":4083},{},[4084,4086,4091],{"type":27,"value":4085},"So what we've got is a function which returns not a value but a ",{"type":22,"tag":267,"props":4087,"children":4088},{},[4089],{"type":27,"value":4090},"type",{"type":27,"value":4092},", something which isn't supposed to even exist at runtime. What good is that? Well, we can use it in the type of another definition to do something that would be totally crazy in almost any other language:",{"type":22,"tag":886,"props":4094,"children":4096},{"className":3864,"code":4095,"language":3866,"meta":8,"style":8},"dependent : (x : Bool) -> stringOrInt x\ndependent False = \"a string\"\ndependent True = 123\n",[4097],{"type":22,"tag":486,"props":4098,"children":4099},{"__ignoreMap":8},[4100,4140,4160],{"type":22,"tag":896,"props":4101,"children":4102},{"class":898,"line":899},[4103,4108,4112,4117,4121,4125,4130,4135],{"type":22,"tag":896,"props":4104,"children":4105},{"style":903},[4106],{"type":27,"value":4107},"dependent ",{"type":22,"tag":896,"props":4109,"children":4110},{"style":1691},[4111],{"type":27,"value":3883},{"type":22,"tag":896,"props":4113,"children":4114},{"style":903},[4115],{"type":27,"value":4116}," (x ",{"type":22,"tag":896,"props":4118,"children":4119},{"style":1691},[4120],{"type":27,"value":3883},{"type":22,"tag":896,"props":4122,"children":4123},{"style":1632},[4124],{"type":27,"value":3888},{"type":22,"tag":896,"props":4126,"children":4127},{"style":903},[4128],{"type":27,"value":4129},") ",{"type":22,"tag":896,"props":4131,"children":4132},{"style":1691},[4133],{"type":27,"value":4134},"->",{"type":22,"tag":896,"props":4136,"children":4137},{"style":903},[4138],{"type":27,"value":4139}," stringOrInt x\n",{"type":22,"tag":896,"props":4141,"children":4142},{"class":898,"line":758},[4143,4147,4151,4155],{"type":22,"tag":896,"props":4144,"children":4145},{"style":903},[4146],{"type":27,"value":4107},{"type":22,"tag":896,"props":4148,"children":4149},{"style":1632},[4150],{"type":27,"value":3977},{"type":22,"tag":896,"props":4152,"children":4153},{"style":1691},[4154],{"type":27,"value":3982},{"type":22,"tag":896,"props":4156,"children":4157},{"style":944},[4158],{"type":27,"value":4159}," \"a string\"\n",{"type":22,"tag":896,"props":4161,"children":4162},{"class":898,"line":755},[4163,4167,4171,4175],{"type":22,"tag":896,"props":4164,"children":4165},{"style":903},[4166],{"type":27,"value":4107},{"type":22,"tag":896,"props":4168,"children":4169},{"style":1632},[4170],{"type":27,"value":3999},{"type":22,"tag":896,"props":4172,"children":4173},{"style":1691},[4174],{"type":27,"value":3982},{"type":22,"tag":896,"props":4176,"children":4177},{"style":1632},[4178],{"type":27,"value":4179}," 123\n",{"type":22,"tag":23,"props":4181,"children":4182},{},[4183,4184,4190,4192,4198,4200,4206,4208,4213],{"type":27,"value":3903},{"type":22,"tag":486,"props":4185,"children":4187},{"className":4186},[],[4188],{"type":27,"value":4189},"(x : Bool)",{"type":27,"value":4191}," syntax here gives the name ",{"type":22,"tag":486,"props":4193,"children":4195},{"className":4194},[],[4196],{"type":27,"value":4197},"x",{"type":27,"value":4199}," to the parameter, so that it can be referred to in the return type. This ",{"type":22,"tag":486,"props":4201,"children":4203},{"className":4202},[],[4204],{"type":27,"value":4205},"dependent",{"type":27,"value":4207}," function returns ",{"type":22,"tag":267,"props":4209,"children":4210},{},[4211],{"type":27,"value":4212},"either",{"type":27,"value":4214}," a string or a number, depending on the input.",{"type":22,"tag":23,"props":4216,"children":4217},{},[4218,4220,4225,4227,4232,4234,4239],{"type":27,"value":4219},"In this example, ",{"type":22,"tag":486,"props":4221,"children":4223},{"className":4222},[],[4224],{"type":27,"value":4027},{"type":27,"value":4226}," is a dependent type: what it ends up being depends on the ",{"type":22,"tag":486,"props":4228,"children":4230},{"className":4229},[],[4231],{"type":27,"value":3925},{"type":27,"value":4233}," you provide to it. While ",{"type":22,"tag":486,"props":4235,"children":4237},{"className":4236},[],[4238],{"type":27,"value":4027},{"type":27,"value":4240}," might not be very useful, this concept enables some powerful capabilities.",{"type":22,"tag":193,"props":4242,"children":4244},{"id":4243},"why-dependent-types",[4245],{"type":27,"value":4246},"Why Dependent Types?",{"type":22,"tag":23,"props":4248,"children":4249},{},[4250,4252,4258],{"type":27,"value":4251},"A practical example of the power of dependent types is for defining a data type for lists of a specific length. In Idris, a ",{"type":22,"tag":486,"props":4253,"children":4255},{"className":4254},[],[4256],{"type":27,"value":4257},"Vect",{"type":27,"value":4259}," type can be defined like so:",{"type":22,"tag":886,"props":4261,"children":4263},{"className":3864,"code":4262,"language":3866,"meta":8,"style":8},"data Vect : Nat -> Type -> Type where\n   Nil  : Vect 0 t\n   Cons : t -> Vect k t -> Vect (S k) t\n",[4264],{"type":22,"tag":486,"props":4265,"children":4266},{"__ignoreMap":8},[4267,4312,4339],{"type":22,"tag":896,"props":4268,"children":4269},{"class":898,"line":899},[4270,4275,4280,4285,4290,4294,4299,4303,4307],{"type":22,"tag":896,"props":4271,"children":4272},{"style":1691},[4273],{"type":27,"value":4274},"data",{"type":22,"tag":896,"props":4276,"children":4277},{"style":1691},[4278],{"type":27,"value":4279}," Vect",{"type":22,"tag":896,"props":4281,"children":4282},{"style":1691},[4283],{"type":27,"value":4284}," :",{"type":22,"tag":896,"props":4286,"children":4287},{"style":1691},[4288],{"type":27,"value":4289}," Nat",{"type":22,"tag":896,"props":4291,"children":4292},{"style":1691},[4293],{"type":27,"value":3893},{"type":22,"tag":896,"props":4295,"children":4296},{"style":1691},[4297],{"type":27,"value":4298}," Type",{"type":22,"tag":896,"props":4300,"children":4301},{"style":1691},[4302],{"type":27,"value":3893},{"type":22,"tag":896,"props":4304,"children":4305},{"style":1691},[4306],{"type":27,"value":4298},{"type":22,"tag":896,"props":4308,"children":4309},{"style":1691},[4310],{"type":27,"value":4311}," where\n",{"type":22,"tag":896,"props":4313,"children":4314},{"class":898,"line":758},[4315,4320,4325,4329,4334],{"type":22,"tag":896,"props":4316,"children":4317},{"style":1632},[4318],{"type":27,"value":4319},"   Nil",{"type":22,"tag":896,"props":4321,"children":4322},{"style":1691},[4323],{"type":27,"value":4324},"  :",{"type":22,"tag":896,"props":4326,"children":4327},{"style":1691},[4328],{"type":27,"value":4279},{"type":22,"tag":896,"props":4330,"children":4331},{"style":1632},[4332],{"type":27,"value":4333}," 0",{"type":22,"tag":896,"props":4335,"children":4336},{"style":903},[4337],{"type":27,"value":4338}," t\n",{"type":22,"tag":896,"props":4340,"children":4341},{"class":898,"line":755},[4342,4347,4351,4356,4360,4364,4369,4373,4377,4382,4387],{"type":22,"tag":896,"props":4343,"children":4344},{"style":1632},[4345],{"type":27,"value":4346},"   Cons",{"type":22,"tag":896,"props":4348,"children":4349},{"style":1691},[4350],{"type":27,"value":4284},{"type":22,"tag":896,"props":4352,"children":4353},{"style":903},[4354],{"type":27,"value":4355}," t ",{"type":22,"tag":896,"props":4357,"children":4358},{"style":1691},[4359],{"type":27,"value":4134},{"type":22,"tag":896,"props":4361,"children":4362},{"style":1691},[4363],{"type":27,"value":4279},{"type":22,"tag":896,"props":4365,"children":4366},{"style":903},[4367],{"type":27,"value":4368}," k t ",{"type":22,"tag":896,"props":4370,"children":4371},{"style":1691},[4372],{"type":27,"value":4134},{"type":22,"tag":896,"props":4374,"children":4375},{"style":1691},[4376],{"type":27,"value":4279},{"type":22,"tag":896,"props":4378,"children":4379},{"style":903},[4380],{"type":27,"value":4381}," (",{"type":22,"tag":896,"props":4383,"children":4384},{"style":1691},[4385],{"type":27,"value":4386},"S",{"type":22,"tag":896,"props":4388,"children":4389},{"style":903},[4390],{"type":27,"value":4391}," k) t\n",{"type":22,"tag":23,"props":4393,"children":4394},{},[4395],{"type":27,"value":4396},"We'll gloss over the syntax here, so this can be understood as",{"type":22,"tag":847,"props":4398,"children":4399},{},[4400,4435,4455],{"type":22,"tag":115,"props":4401,"children":4402},{},[4403,4405,4411,4413,4419,4420,4426,4428,4433],{"type":27,"value":4404},"Define a new data type ",{"type":22,"tag":486,"props":4406,"children":4408},{"className":4407},[],[4409],{"type":27,"value":4410},"Vect k a",{"type":27,"value":4412},", where ",{"type":22,"tag":486,"props":4414,"children":4416},{"className":4415},[],[4417],{"type":27,"value":4418},"k",{"type":27,"value":3693},{"type":22,"tag":486,"props":4421,"children":4423},{"className":4422},[],[4424],{"type":27,"value":4425},"Nat",{"type":27,"value":4427}," (a natural number) and ",{"type":22,"tag":486,"props":4429,"children":4431},{"className":4430},[],[4432],{"type":27,"value":30},{"type":27,"value":4434}," is the type of the elements",{"type":22,"tag":115,"props":4436,"children":4437},{},[4438,4440,4446,4448,4453],{"type":27,"value":4439},"Define a constructor ",{"type":22,"tag":486,"props":4441,"children":4443},{"className":4442},[],[4444],{"type":27,"value":4445},"Nil",{"type":27,"value":4447}," to create an empty ",{"type":22,"tag":486,"props":4449,"children":4451},{"className":4450},[],[4452],{"type":27,"value":4257},{"type":27,"value":4454}," for any element type, of length 0",{"type":22,"tag":115,"props":4456,"children":4457},{},[4458,4459,4465,4467,4472,4474,4479,4480,4486,4488,4493,4494,4500,4502,4507],{"type":27,"value":4439},{"type":22,"tag":486,"props":4460,"children":4462},{"className":4461},[],[4463],{"type":27,"value":4464},"Cons x xs",{"type":27,"value":4466}," to prepend the element ",{"type":22,"tag":486,"props":4468,"children":4470},{"className":4469},[],[4471],{"type":27,"value":4197},{"type":27,"value":4473}," to another ",{"type":22,"tag":486,"props":4475,"children":4477},{"className":4476},[],[4478],{"type":27,"value":4257},{"type":27,"value":3391},{"type":22,"tag":486,"props":4481,"children":4483},{"className":4482},[],[4484],{"type":27,"value":4485},"xs",{"type":27,"value":4487},", adding 1 to the length of the ",{"type":22,"tag":486,"props":4489,"children":4491},{"className":4490},[],[4492],{"type":27,"value":4257},{"type":27,"value":4381},{"type":22,"tag":486,"props":4495,"children":4497},{"className":4496},[],[4498],{"type":27,"value":4499},"(S k)",{"type":27,"value":4501}," here meaning \"add 1 to ",{"type":22,"tag":486,"props":4503,"children":4505},{"className":4504},[],[4506],{"type":27,"value":4418},{"type":27,"value":4508},"\")",{"type":22,"tag":23,"props":4510,"children":4511},{},[4512,4514,4520,4522,4528,4530,4536,4537,4543,4545,4551,4553,4558],{"type":27,"value":4513},"This ",{"type":22,"tag":486,"props":4515,"children":4517},{"className":4516},[],[4518],{"type":27,"value":4519},"Vect n t",{"type":27,"value":4521}," type has something in common with generic array types like TypeScript's ",{"type":22,"tag":486,"props":4523,"children":4525},{"className":4524},[],[4526],{"type":27,"value":4527},"Array\u003CT>",{"type":27,"value":4529},": they each have a ",{"type":22,"tag":486,"props":4531,"children":4533},{"className":4532},[],[4534],{"type":27,"value":4535},"t",{"type":27,"value":2684},{"type":22,"tag":486,"props":4538,"children":4540},{"className":4539},[],[4541],{"type":27,"value":4542},"T",{"type":27,"value":4544}," parameter to define the type of the array elements (although since TypeScript's types aren't first-class, it appears in the special ",{"type":22,"tag":486,"props":4546,"children":4548},{"className":4547},[],[4549],{"type":27,"value":4550},"\u003C>",{"type":27,"value":4552}," syntax rather than being a regular old function parameter). What ",{"type":22,"tag":486,"props":4554,"children":4556},{"className":4555},[],[4557],{"type":27,"value":4257},{"type":27,"value":4559}," adds is a value which encodes the length of the list right in the type itself.",{"type":22,"tag":23,"props":4561,"children":4562},{},[4563,4565,4570,4572,4577,4578,4584],{"type":27,"value":4564},"With this definition in hand, we can define ",{"type":22,"tag":486,"props":4566,"children":4568},{"className":4567},[],[4569],{"type":27,"value":4257},{"type":27,"value":4571}," values using the ",{"type":22,"tag":486,"props":4573,"children":4575},{"className":4574},[],[4576],{"type":27,"value":4445},{"type":27,"value":1441},{"type":22,"tag":486,"props":4579,"children":4581},{"className":4580},[],[4582],{"type":27,"value":4583},"Cons",{"type":27,"value":4585}," constructors:",{"type":22,"tag":886,"props":4587,"children":4589},{"className":3864,"code":4588,"language":3866,"meta":8,"style":8},"-- An Int Vect of length 0\nnoInts : Vect Z Int\nnoInts = Nil\n\n-- A Bool Vect of length 2\ntwoBools : Vect 2 Bool\ntwoBools = Cons True (Cons False Nil)\n\n-- A Bool Vect of length 3, built by adding a third element to twoBools\nthreeBools : Vect 3 Bool\nthreeBools = Cons True twoBools\n",[4590],{"type":22,"tag":486,"props":4591,"children":4592},{"__ignoreMap":8},[4593,4601,4626,4642,4651,4659,4685,4729,4736,4744,4769],{"type":22,"tag":896,"props":4594,"children":4595},{"class":898,"line":899},[4596],{"type":22,"tag":896,"props":4597,"children":4598},{"style":2070},[4599],{"type":27,"value":4600},"-- An Int Vect of length 0\n",{"type":22,"tag":896,"props":4602,"children":4603},{"class":898,"line":758},[4604,4609,4613,4617,4622],{"type":22,"tag":896,"props":4605,"children":4606},{"style":903},[4607],{"type":27,"value":4608},"noInts ",{"type":22,"tag":896,"props":4610,"children":4611},{"style":1691},[4612],{"type":27,"value":3883},{"type":22,"tag":896,"props":4614,"children":4615},{"style":1632},[4616],{"type":27,"value":4279},{"type":22,"tag":896,"props":4618,"children":4619},{"style":1632},[4620],{"type":27,"value":4621}," Z",{"type":22,"tag":896,"props":4623,"children":4624},{"style":1632},[4625],{"type":27,"value":4008},{"type":22,"tag":896,"props":4627,"children":4628},{"class":898,"line":755},[4629,4633,4637],{"type":22,"tag":896,"props":4630,"children":4631},{"style":903},[4632],{"type":27,"value":4608},{"type":22,"tag":896,"props":4634,"children":4635},{"style":1691},[4636],{"type":27,"value":941},{"type":22,"tag":896,"props":4638,"children":4639},{"style":1632},[4640],{"type":27,"value":4641}," Nil\n",{"type":22,"tag":896,"props":4643,"children":4644},{"class":898,"line":983},[4645],{"type":22,"tag":896,"props":4646,"children":4648},{"emptyLinePlaceholder":4647},true,[4649],{"type":27,"value":4650},"\n",{"type":22,"tag":896,"props":4652,"children":4653},{"class":898,"line":1013},[4654],{"type":22,"tag":896,"props":4655,"children":4656},{"style":2070},[4657],{"type":27,"value":4658},"-- A Bool Vect of length 2\n",{"type":22,"tag":896,"props":4660,"children":4661},{"class":898,"line":1030},[4662,4667,4671,4675,4680],{"type":22,"tag":896,"props":4663,"children":4664},{"style":903},[4665],{"type":27,"value":4666},"twoBools ",{"type":22,"tag":896,"props":4668,"children":4669},{"style":1691},[4670],{"type":27,"value":3883},{"type":22,"tag":896,"props":4672,"children":4673},{"style":1632},[4674],{"type":27,"value":4279},{"type":22,"tag":896,"props":4676,"children":4677},{"style":1632},[4678],{"type":27,"value":4679}," 2",{"type":22,"tag":896,"props":4681,"children":4682},{"style":1632},[4683],{"type":27,"value":4684}," Bool\n",{"type":22,"tag":896,"props":4686,"children":4687},{"class":898,"line":1068},[4688,4692,4696,4701,4706,4710,4714,4719,4724],{"type":22,"tag":896,"props":4689,"children":4690},{"style":903},[4691],{"type":27,"value":4666},{"type":22,"tag":896,"props":4693,"children":4694},{"style":1691},[4695],{"type":27,"value":941},{"type":22,"tag":896,"props":4697,"children":4698},{"style":1632},[4699],{"type":27,"value":4700}," Cons",{"type":22,"tag":896,"props":4702,"children":4703},{"style":1632},[4704],{"type":27,"value":4705}," True",{"type":22,"tag":896,"props":4707,"children":4708},{"style":903},[4709],{"type":27,"value":4381},{"type":22,"tag":896,"props":4711,"children":4712},{"style":1632},[4713],{"type":27,"value":4583},{"type":22,"tag":896,"props":4715,"children":4716},{"style":1632},[4717],{"type":27,"value":4718}," False",{"type":22,"tag":896,"props":4720,"children":4721},{"style":1632},[4722],{"type":27,"value":4723}," Nil",{"type":22,"tag":896,"props":4725,"children":4726},{"style":903},[4727],{"type":27,"value":4728},")\n",{"type":22,"tag":896,"props":4730,"children":4731},{"class":898,"line":1085},[4732],{"type":22,"tag":896,"props":4733,"children":4734},{"emptyLinePlaceholder":4647},[4735],{"type":27,"value":4650},{"type":22,"tag":896,"props":4737,"children":4738},{"class":898,"line":1123},[4739],{"type":22,"tag":896,"props":4740,"children":4741},{"style":2070},[4742],{"type":27,"value":4743},"-- A Bool Vect of length 3, built by adding a third element to twoBools\n",{"type":22,"tag":896,"props":4745,"children":4746},{"class":898,"line":1161},[4747,4752,4756,4760,4765],{"type":22,"tag":896,"props":4748,"children":4749},{"style":903},[4750],{"type":27,"value":4751},"threeBools ",{"type":22,"tag":896,"props":4753,"children":4754},{"style":1691},[4755],{"type":27,"value":3883},{"type":22,"tag":896,"props":4757,"children":4758},{"style":1632},[4759],{"type":27,"value":4279},{"type":22,"tag":896,"props":4761,"children":4762},{"style":1632},[4763],{"type":27,"value":4764}," 3",{"type":22,"tag":896,"props":4766,"children":4767},{"style":1632},[4768],{"type":27,"value":4684},{"type":22,"tag":896,"props":4770,"children":4771},{"class":898,"line":2298},[4772,4776,4780,4784,4788],{"type":22,"tag":896,"props":4773,"children":4774},{"style":903},[4775],{"type":27,"value":4751},{"type":22,"tag":896,"props":4777,"children":4778},{"style":1691},[4779],{"type":27,"value":941},{"type":22,"tag":896,"props":4781,"children":4782},{"style":1632},[4783],{"type":27,"value":4700},{"type":22,"tag":896,"props":4785,"children":4786},{"style":1632},[4787],{"type":27,"value":4705},{"type":22,"tag":896,"props":4789,"children":4790},{"style":903},[4791],{"type":27,"value":4792}," twoBools\n",{"type":22,"tag":23,"props":4794,"children":4795},{},[4796,4798,4804],{"type":27,"value":4797},"This length allows you to be much more specific in the type of, for instance, an ",{"type":22,"tag":486,"props":4799,"children":4801},{"className":4800},[],[4802],{"type":27,"value":4803},"append",{"type":27,"value":4805}," function:",{"type":22,"tag":886,"props":4807,"children":4809},{"className":3864,"code":4808,"language":3866,"meta":8,"style":8},"append : Vect n t -> Vect m t -> Vect (n + m) t\nappend Nil ys = ys\nappend (Cons x xs) ys = Cons x (append xs ys)\n",[4810],{"type":22,"tag":486,"props":4811,"children":4812},{"__ignoreMap":8},[4813,4870,4895],{"type":22,"tag":896,"props":4814,"children":4815},{"class":898,"line":899},[4816,4821,4825,4829,4834,4838,4842,4847,4851,4855,4860,4865],{"type":22,"tag":896,"props":4817,"children":4818},{"style":903},[4819],{"type":27,"value":4820},"append ",{"type":22,"tag":896,"props":4822,"children":4823},{"style":1691},[4824],{"type":27,"value":3883},{"type":22,"tag":896,"props":4826,"children":4827},{"style":1632},[4828],{"type":27,"value":4279},{"type":22,"tag":896,"props":4830,"children":4831},{"style":903},[4832],{"type":27,"value":4833}," n t ",{"type":22,"tag":896,"props":4835,"children":4836},{"style":1691},[4837],{"type":27,"value":4134},{"type":22,"tag":896,"props":4839,"children":4840},{"style":1632},[4841],{"type":27,"value":4279},{"type":22,"tag":896,"props":4843,"children":4844},{"style":903},[4845],{"type":27,"value":4846}," m t ",{"type":22,"tag":896,"props":4848,"children":4849},{"style":1691},[4850],{"type":27,"value":4134},{"type":22,"tag":896,"props":4852,"children":4853},{"style":1632},[4854],{"type":27,"value":4279},{"type":22,"tag":896,"props":4856,"children":4857},{"style":903},[4858],{"type":27,"value":4859}," (n ",{"type":22,"tag":896,"props":4861,"children":4862},{"style":1691},[4863],{"type":27,"value":4864},"+",{"type":22,"tag":896,"props":4866,"children":4867},{"style":903},[4868],{"type":27,"value":4869}," m) t\n",{"type":22,"tag":896,"props":4871,"children":4872},{"class":898,"line":758},[4873,4877,4881,4886,4890],{"type":22,"tag":896,"props":4874,"children":4875},{"style":903},[4876],{"type":27,"value":4820},{"type":22,"tag":896,"props":4878,"children":4879},{"style":1632},[4880],{"type":27,"value":4445},{"type":22,"tag":896,"props":4882,"children":4883},{"style":903},[4884],{"type":27,"value":4885}," ys ",{"type":22,"tag":896,"props":4887,"children":4888},{"style":1691},[4889],{"type":27,"value":941},{"type":22,"tag":896,"props":4891,"children":4892},{"style":903},[4893],{"type":27,"value":4894}," ys\n",{"type":22,"tag":896,"props":4896,"children":4897},{"class":898,"line":755},[4898,4903,4907,4912,4916,4920],{"type":22,"tag":896,"props":4899,"children":4900},{"style":903},[4901],{"type":27,"value":4902},"append (",{"type":22,"tag":896,"props":4904,"children":4905},{"style":1632},[4906],{"type":27,"value":4583},{"type":22,"tag":896,"props":4908,"children":4909},{"style":903},[4910],{"type":27,"value":4911}," x xs) ys ",{"type":22,"tag":896,"props":4913,"children":4914},{"style":1691},[4915],{"type":27,"value":941},{"type":22,"tag":896,"props":4917,"children":4918},{"style":1632},[4919],{"type":27,"value":4700},{"type":22,"tag":896,"props":4921,"children":4922},{"style":903},[4923],{"type":27,"value":4924}," x (append xs ys)\n",{"type":22,"tag":23,"props":4926,"children":4927},{},[4928],{"type":27,"value":4929},"Let's break down what's going on here:",{"type":22,"tag":847,"props":4931,"children":4932},{},[4933,4980,5014,5039],{"type":22,"tag":115,"props":4934,"children":4935},{},[4936,4938,4943,4945,4950,4952,4958,4960,4965,4966,4972,4974,4979],{"type":27,"value":4937},"The type of ",{"type":22,"tag":486,"props":4939,"children":4941},{"className":4940},[],[4942],{"type":27,"value":4803},{"type":27,"value":4944}," says it takes two parameters, a ",{"type":22,"tag":486,"props":4946,"children":4948},{"className":4947},[],[4949],{"type":27,"value":4257},{"type":27,"value":4951}," of length ",{"type":22,"tag":486,"props":4953,"children":4955},{"className":4954},[],[4956],{"type":27,"value":4957},"n",{"type":27,"value":4959}," and a ",{"type":22,"tag":486,"props":4961,"children":4963},{"className":4962},[],[4964],{"type":27,"value":4257},{"type":27,"value":4951},{"type":22,"tag":486,"props":4967,"children":4969},{"className":4968},[],[4970],{"type":27,"value":4971},"m",{"type":27,"value":4973},", each of which have elements of the same type ",{"type":22,"tag":486,"props":4975,"children":4977},{"className":4976},[],[4978],{"type":27,"value":4535},{"type":27,"value":71},{"type":22,"tag":115,"props":4981,"children":4982},{},[4983,4985,4991,4993,4998,5000,5005,5007,5012],{"type":27,"value":4984},"The type says that the return type is ",{"type":22,"tag":486,"props":4986,"children":4988},{"className":4987},[],[4989],{"type":27,"value":4990},"Vect (n + m) t",{"type":27,"value":4992},", meaning it's a ",{"type":22,"tag":486,"props":4994,"children":4996},{"className":4995},[],[4997],{"type":27,"value":4257},{"type":27,"value":4999}," with the combined lengths of both inputs. We've got math going on ",{"type":22,"tag":267,"props":5001,"children":5002},{},[5003],{"type":27,"value":5004},"at the type level",{"type":27,"value":5006},", and Idris is OK with this: when we implement or use ",{"type":22,"tag":486,"props":5008,"children":5010},{"className":5009},[],[5011],{"type":27,"value":4803},{"type":27,"value":5013},", the compiler will evaluate this to make sure the code conforms. Crazy!",{"type":22,"tag":115,"props":5015,"children":5016},{},[5017,5019,5024,5026,5031,5033,5038],{"type":27,"value":5018},"If the first ",{"type":22,"tag":486,"props":5020,"children":5022},{"className":5021},[],[5023],{"type":27,"value":4257},{"type":27,"value":5025}," is ",{"type":22,"tag":486,"props":5027,"children":5029},{"className":5028},[],[5030],{"type":27,"value":4445},{"type":27,"value":5032}," (i.e. empty), then just return the second ",{"type":22,"tag":486,"props":5034,"children":5036},{"className":5035},[],[5037],{"type":27,"value":4257},{"type":27,"value":71},{"type":22,"tag":115,"props":5040,"children":5041},{},[5042,5044,5049,5051,5056,5058,5063,5064,5069,5071,5076,5078,5083,5085,5090],{"type":27,"value":5043},"Otherwise, the first ",{"type":22,"tag":486,"props":5045,"children":5047},{"className":5046},[],[5048],{"type":27,"value":4257},{"type":27,"value":5050}," must be an element ",{"type":22,"tag":486,"props":5052,"children":5054},{"className":5053},[],[5055],{"type":27,"value":4197},{"type":27,"value":5057}," prepended onto some other ",{"type":22,"tag":486,"props":5059,"children":5061},{"className":5060},[],[5062],{"type":27,"value":4257},{"type":27,"value":236},{"type":22,"tag":486,"props":5065,"children":5067},{"className":5066},[],[5068],{"type":27,"value":4485},{"type":27,"value":5070},". We can recursively ",{"type":22,"tag":486,"props":5072,"children":5074},{"className":5073},[],[5075],{"type":27,"value":4803},{"type":27,"value":5077}," the remaining ",{"type":22,"tag":486,"props":5079,"children":5081},{"className":5080},[],[5082],{"type":27,"value":4257},{"type":27,"value":5084},"s together and prepend ",{"type":22,"tag":486,"props":5086,"children":5088},{"className":5087},[],[5089],{"type":27,"value":4197},{"type":27,"value":5091}," onto the result.",{"type":22,"tag":23,"props":5093,"children":5094},{},[5095,5097,5102,5104,5109],{"type":27,"value":5096},"Using dependent types, we're able to have ",{"type":22,"tag":486,"props":5098,"children":5100},{"className":5099},[],[5101],{"type":27,"value":4803},{"type":27,"value":5103}," make a much stronger guarantee than it otherwise could: not only will it return a ",{"type":22,"tag":486,"props":5105,"children":5107},{"className":5106},[],[5108],{"type":27,"value":4257},{"type":27,"value":5110}," with the same type of elements as the inputs, but it will guarantee that the result has the correct length, or else the code won't even compile.",{"type":22,"tag":23,"props":5112,"children":5113},{},[5114,5116,5121,5123,5129],{"type":27,"value":5115},"Consider what would have happened if we had made a simple mistake and accidentally wrote a second ",{"type":22,"tag":486,"props":5117,"children":5119},{"className":5118},[],[5120],{"type":27,"value":4485},{"type":27,"value":5122}," instead of a ",{"type":22,"tag":486,"props":5124,"children":5126},{"className":5125},[],[5127],{"type":27,"value":5128},"ys",{"type":27,"value":3883},{"type":22,"tag":886,"props":5131,"children":5133},{"className":3864,"code":5132,"language":3866,"meta":8,"style":8},"append : Vect n t -> Vect m t -> Vect (n + m) t\nappend Nil ys = ys\nappend (Cons x xs) = Cons x (append xs xs)\n                                    -- ^ Oops, this should be ys\n",[5134],{"type":22,"tag":486,"props":5135,"children":5136},{"__ignoreMap":8},[5137,5188,5211,5240],{"type":22,"tag":896,"props":5138,"children":5139},{"class":898,"line":899},[5140,5144,5148,5152,5156,5160,5164,5168,5172,5176,5180,5184],{"type":22,"tag":896,"props":5141,"children":5142},{"style":903},[5143],{"type":27,"value":4820},{"type":22,"tag":896,"props":5145,"children":5146},{"style":1691},[5147],{"type":27,"value":3883},{"type":22,"tag":896,"props":5149,"children":5150},{"style":1632},[5151],{"type":27,"value":4279},{"type":22,"tag":896,"props":5153,"children":5154},{"style":903},[5155],{"type":27,"value":4833},{"type":22,"tag":896,"props":5157,"children":5158},{"style":1691},[5159],{"type":27,"value":4134},{"type":22,"tag":896,"props":5161,"children":5162},{"style":1632},[5163],{"type":27,"value":4279},{"type":22,"tag":896,"props":5165,"children":5166},{"style":903},[5167],{"type":27,"value":4846},{"type":22,"tag":896,"props":5169,"children":5170},{"style":1691},[5171],{"type":27,"value":4134},{"type":22,"tag":896,"props":5173,"children":5174},{"style":1632},[5175],{"type":27,"value":4279},{"type":22,"tag":896,"props":5177,"children":5178},{"style":903},[5179],{"type":27,"value":4859},{"type":22,"tag":896,"props":5181,"children":5182},{"style":1691},[5183],{"type":27,"value":4864},{"type":22,"tag":896,"props":5185,"children":5186},{"style":903},[5187],{"type":27,"value":4869},{"type":22,"tag":896,"props":5189,"children":5190},{"class":898,"line":758},[5191,5195,5199,5203,5207],{"type":22,"tag":896,"props":5192,"children":5193},{"style":903},[5194],{"type":27,"value":4820},{"type":22,"tag":896,"props":5196,"children":5197},{"style":1632},[5198],{"type":27,"value":4445},{"type":22,"tag":896,"props":5200,"children":5201},{"style":903},[5202],{"type":27,"value":4885},{"type":22,"tag":896,"props":5204,"children":5205},{"style":1691},[5206],{"type":27,"value":941},{"type":22,"tag":896,"props":5208,"children":5209},{"style":903},[5210],{"type":27,"value":4894},{"type":22,"tag":896,"props":5212,"children":5213},{"class":898,"line":755},[5214,5218,5222,5227,5231,5235],{"type":22,"tag":896,"props":5215,"children":5216},{"style":903},[5217],{"type":27,"value":4902},{"type":22,"tag":896,"props":5219,"children":5220},{"style":1632},[5221],{"type":27,"value":4583},{"type":22,"tag":896,"props":5223,"children":5224},{"style":903},[5225],{"type":27,"value":5226}," x xs) ",{"type":22,"tag":896,"props":5228,"children":5229},{"style":1691},[5230],{"type":27,"value":941},{"type":22,"tag":896,"props":5232,"children":5233},{"style":1632},[5234],{"type":27,"value":4700},{"type":22,"tag":896,"props":5236,"children":5237},{"style":903},[5238],{"type":27,"value":5239}," x (append xs xs)\n",{"type":22,"tag":896,"props":5241,"children":5242},{"class":898,"line":983},[5243],{"type":22,"tag":896,"props":5244,"children":5245},{"style":2070},[5246],{"type":27,"value":5247},"                                    -- ^ Oops, this should be ys\n",{"type":22,"tag":23,"props":5249,"children":5250},{},[5251],{"type":27,"value":5252},"This code will not compile, producing an error something like this:",{"type":22,"tag":886,"props":5254,"children":5258},{"className":5255,"code":5257,"language":27},[5256],"language-text","Error: While processing right hand side of append. When unifying `n + n` and `n + m`.\nMismatch between: `n` and `m`.\n\nExample:28:25--28:46\n 26 | append : Vect n t -> Vect m t -> Vect (n + m) t\n 27 | append Nil ys = ys\n 28 | append (Cons x xs) ys = Cons x (append xs xs)\n                              ^^^^^^^^^^^^^^^^^^^^^\n",[5259],{"type":22,"tag":486,"props":5260,"children":5261},{"__ignoreMap":8},[5262],{"type":27,"value":5257},{"type":22,"tag":23,"props":5264,"children":5265},{},[5266,5268,5274,5276,5281,5283,5288],{"type":27,"value":5267},"The compiler has identified that the type of the code we wrote is ",{"type":22,"tag":486,"props":5269,"children":5271},{"className":5270},[],[5272],{"type":27,"value":5273},"Vect (n + n) t",{"type":27,"value":5275},", but the definition of ",{"type":22,"tag":486,"props":5277,"children":5279},{"className":5278},[],[5280],{"type":27,"value":4803},{"type":27,"value":5282}," expects ",{"type":22,"tag":486,"props":5284,"children":5286},{"className":5285},[],[5287],{"type":27,"value":4990},{"type":27,"value":5289},", which doesn't match.",{"type":22,"tag":23,"props":5291,"children":5292},{},[5293],{"type":27,"value":5294},"This is a sample of the power of dependent types: making stronger guarantees at the type level, so that the compiler can catch and prevent many more errors and bugs. For the purposes of this overview we'll stop here, but there are a ton of other interesting applications, such as:",{"type":22,"tag":111,"props":5296,"children":5297},{},[5298,5316,5321,5326,5331],{"type":22,"tag":115,"props":5299,"children":5300},{},[5301],{"type":22,"tag":30,"props":5302,"children":5305},{"href":5303,"rel":5304},"https://www.youtube.com/watch?v=fVBck2Zngjo",[34],[5306,5308,5314],{"type":27,"value":5307},"A type-safe ",{"type":22,"tag":486,"props":5309,"children":5311},{"className":5310},[],[5312],{"type":27,"value":5313},"printf",{"type":27,"value":5315}," function",{"type":22,"tag":115,"props":5317,"children":5318},{},[5319],{"type":27,"value":5320},"Defining state machines as data types, so that only valid state transitions are possible",{"type":22,"tag":115,"props":5322,"children":5323},{},[5324],{"type":27,"value":5325},"Allowing functions to make formal assertions about their inputs or outputs, such as \"the input array is sorted\"",{"type":22,"tag":115,"props":5327,"children":5328},{},[5329],{"type":27,"value":5330},"Using interactive editing to get the compiler to automatically fill in parts of your solution",{"type":22,"tag":115,"props":5332,"children":5333},{},[5334,5336],{"type":27,"value":5335},"Implementing your unit tests as proofs, e.g.:",{"type":22,"tag":886,"props":5337,"children":5339},{"className":3864,"code":5338,"language":3866,"meta":8,"style":8},"-- The type is an assertion that `length ['a', 'b']` evaluates to `2`.\n-- `Refl` asks the compiler to check that this is trivially true, by\n-- evaluating it down to `2 = 2`. The code gets executed as part of the\n-- compilation process, so if it compiles, then this \"unit test\" passes!\ntestLength2 : length ['a', 'b'] = 2\ntestLength2 = Refl\n",[5340],{"type":22,"tag":486,"props":5341,"children":5342},{"__ignoreMap":8},[5343,5351,5359,5367,5375,5420],{"type":22,"tag":896,"props":5344,"children":5345},{"class":898,"line":899},[5346],{"type":22,"tag":896,"props":5347,"children":5348},{"style":2070},[5349],{"type":27,"value":5350},"-- The type is an assertion that `length ['a', 'b']` evaluates to `2`.\n",{"type":22,"tag":896,"props":5352,"children":5353},{"class":898,"line":758},[5354],{"type":22,"tag":896,"props":5355,"children":5356},{"style":2070},[5357],{"type":27,"value":5358},"-- `Refl` asks the compiler to check that this is trivially true, by\n",{"type":22,"tag":896,"props":5360,"children":5361},{"class":898,"line":755},[5362],{"type":22,"tag":896,"props":5363,"children":5364},{"style":2070},[5365],{"type":27,"value":5366},"-- evaluating it down to `2 = 2`. The code gets executed as part of the\n",{"type":22,"tag":896,"props":5368,"children":5369},{"class":898,"line":983},[5370],{"type":22,"tag":896,"props":5371,"children":5372},{"style":2070},[5373],{"type":27,"value":5374},"-- compilation process, so if it compiles, then this \"unit test\" passes!\n",{"type":22,"tag":896,"props":5376,"children":5377},{"class":898,"line":1013},[5378,5383,5387,5392,5397,5401,5406,5411,5415],{"type":22,"tag":896,"props":5379,"children":5380},{"style":903},[5381],{"type":27,"value":5382},"testLength2 ",{"type":22,"tag":896,"props":5384,"children":5385},{"style":1691},[5386],{"type":27,"value":3883},{"type":22,"tag":896,"props":5388,"children":5389},{"style":903},[5390],{"type":27,"value":5391}," length [",{"type":22,"tag":896,"props":5393,"children":5394},{"style":944},[5395],{"type":27,"value":5396},"'a'",{"type":22,"tag":896,"props":5398,"children":5399},{"style":903},[5400],{"type":27,"value":236},{"type":22,"tag":896,"props":5402,"children":5403},{"style":944},[5404],{"type":27,"value":5405},"'b'",{"type":22,"tag":896,"props":5407,"children":5408},{"style":903},[5409],{"type":27,"value":5410},"] ",{"type":22,"tag":896,"props":5412,"children":5413},{"style":1691},[5414],{"type":27,"value":941},{"type":22,"tag":896,"props":5416,"children":5417},{"style":1632},[5418],{"type":27,"value":5419}," 2\n",{"type":22,"tag":896,"props":5421,"children":5422},{"class":898,"line":1030},[5423,5427,5431],{"type":22,"tag":896,"props":5424,"children":5425},{"style":903},[5426],{"type":27,"value":5382},{"type":22,"tag":896,"props":5428,"children":5429},{"style":1691},[5430],{"type":27,"value":941},{"type":22,"tag":896,"props":5432,"children":5433},{"style":1632},[5434],{"type":27,"value":5435}," Refl\n",{"type":22,"tag":193,"props":5437,"children":5439},{"id":5438},"why-not-dependent-types",[5440],{"type":27,"value":5441},"Why Not Dependent Types?",{"type":22,"tag":23,"props":5443,"children":5444},{},[5445,5447,5452],{"type":27,"value":5446},"With great power comes great responsibility, and first-class/dependent types introduce a ",{"type":22,"tag":267,"props":5448,"children":5449},{},[5450],{"type":27,"value":5451},"lot",{"type":27,"value":5453}," of power to a language. The catch with leaning on the compiler to make much stronger guarantees about your programs is that you need to convince the compiler that your code meets these guarantees, in rigorous, mathematical detail.",{"type":22,"tag":23,"props":5455,"children":5456},{},[5457,5459,5466,5468,5474],{"type":27,"value":5458},"For example, I've implemented a Binary Search Tree in Idris (see ",{"type":22,"tag":30,"props":5460,"children":5463},{"href":5461,"rel":5462},"https://github.com/polendri/idris-collections/blob/master/src/Collections/BSTree/Map/Core.idr",[34],[5464],{"type":27,"value":5465},"here",{"type":27,"value":5467}," for the main data types, and ",{"type":22,"tag":30,"props":5469,"children":5472},{"href":5470,"rel":5471},"https://github.com/polendri/idris-collections/blob/master/src/Collections/BSTree/Map.idr",[34],[5473],{"type":27,"value":5465},{"type":27,"value":5475}," for the insertion/querying implementations), one which guarantees that any instance of a tree satisfies the ordering constraints. It works, but the implementation is definitely noisier and it took much, much longer to implement than it would have had I not given it a type with so many guarantees. As the code that you're formally verifying becomes more complex, implementation quickly goes from easy, to time-consuming, to an active research area.",{"type":22,"tag":23,"props":5477,"children":5478},{},[5479],{"type":27,"value":5480},"Finding language features and development practices which make dependent types easier to use is an exciting research area; you won't use Idris in production tomorrow, but... maybe someday.",{"type":22,"tag":193,"props":5482,"children":5484},{"id":5483},"practical-applications",[5485],{"type":27,"value":5486},"Practical Applications",{"type":22,"tag":23,"props":5488,"children":5489},{},[5490],{"type":27,"value":5491},"You may not be using Idris in production today, but taking some time to learn it might make you see your current code in a new light:",{"type":22,"tag":1481,"props":5493,"children":5495},{"id":5494},"the-power-of-types",[5496],{"type":27,"value":5497},"The Power of Types",{"type":22,"tag":23,"props":5499,"children":5500},{},[5501],{"type":27,"value":5502},"Early on in a new JavaScript, Python or Ruby project, the sheer speed of development might make the language feel far and away superior to a strictly typed language like C++, Java, or Rust. As the months and years go by however, and runtime errors keep cropping up, you may be longing for the guard rails that those types provide. Depending on the project's priorities, a strictly typed language may be a better investment in the long term. And keep in mind that with the backwards-compatible TypeScript and with Python's type annotations, sometimes a gradual transition is possible.",{"type":22,"tag":1481,"props":5504,"children":5506},{"id":5505},"the-value-of-descriptive-data-types",[5507],{"type":27,"value":5508},"The Value of Descriptive Data Types",{"type":22,"tag":23,"props":5510,"children":5511},{},[5512,5514,5518],{"type":27,"value":5513},"We might not be able to encode \"an array of length ",{"type":22,"tag":267,"props":5515,"children":5516},{},[5517],{"type":27,"value":4957},{"type":27,"value":5519},"\" in any popular programming language, or to get our compiler to formally guarantee that an array is properly sorted, but there can still be a lot of value in defining new types to encode these properties anyway.",{"type":22,"tag":23,"props":5521,"children":5522},{},[5523,5525,5531,5533,5539],{"type":27,"value":5524},"Take email addresses for example, which typically would be stored as a ",{"type":22,"tag":486,"props":5526,"children":5528},{"className":5527},[],[5529],{"type":27,"value":5530},"string",{"type":27,"value":5532},". If your ",{"type":22,"tag":486,"props":5534,"children":5536},{"className":5535},[],[5537],{"type":27,"value":5538},"sendReminder",{"type":27,"value":5540}," function is implemented like this,",{"type":22,"tag":886,"props":5542,"children":5546},{"className":5543,"code":5544,"language":5545,"meta":8,"style":8},"language-csharp shiki shiki-themes github-light github-dark","void sendReminder(Appointment appointment, string email) { ... }\n","csharp",[5547],{"type":22,"tag":486,"props":5548,"children":5549},{"__ignoreMap":8},[5550],{"type":22,"tag":896,"props":5551,"children":5552},{"class":898,"line":899},[5553,5558,5563,5568,5573,5578,5582,5586,5591,5596,5601],{"type":22,"tag":896,"props":5554,"children":5555},{"style":1691},[5556],{"type":27,"value":5557},"void",{"type":22,"tag":896,"props":5559,"children":5560},{"style":933},[5561],{"type":27,"value":5562}," sendReminder",{"type":22,"tag":896,"props":5564,"children":5565},{"style":903},[5566],{"type":27,"value":5567},"(",{"type":22,"tag":896,"props":5569,"children":5570},{"style":933},[5571],{"type":27,"value":5572},"Appointment",{"type":22,"tag":896,"props":5574,"children":5575},{"style":933},[5576],{"type":27,"value":5577}," appointment",{"type":22,"tag":896,"props":5579,"children":5580},{"style":903},[5581],{"type":27,"value":236},{"type":22,"tag":896,"props":5583,"children":5584},{"style":1691},[5585],{"type":27,"value":5530},{"type":22,"tag":896,"props":5587,"children":5588},{"style":933},[5589],{"type":27,"value":5590}," email",{"type":22,"tag":896,"props":5592,"children":5593},{"style":903},[5594],{"type":27,"value":5595},") { ",{"type":22,"tag":896,"props":5597,"children":5598},{"style":1691},[5599],{"type":27,"value":5600},"..",{"type":22,"tag":896,"props":5602,"children":5603},{"style":903},[5604],{"type":27,"value":5605},". }\n",{"type":22,"tag":23,"props":5607,"children":5608},{},[5609,5611,5617,5619,5625,5627,5633],{"type":27,"value":5610},"then any number of bugs could cause the value of ",{"type":22,"tag":486,"props":5612,"children":5614},{"className":5613},[],[5615],{"type":27,"value":5616},"email",{"type":27,"value":5618}," to be something which is not an email address. This is unfortunate, because the error is going to occur somewhere inside ",{"type":22,"tag":486,"props":5620,"children":5622},{"className":5621},[],[5623],{"type":27,"value":5624},"sendReminder()",{"type":27,"value":5626},", leaving you to go on a hunt for the true source of the bug. Consider however if you were to define an ",{"type":22,"tag":486,"props":5628,"children":5630},{"className":5629},[],[5631],{"type":27,"value":5632},"EmailAddress",{"type":27,"value":5634}," data type:",{"type":22,"tag":886,"props":5636,"children":5638},{"className":5543,"code":5637,"language":5545,"meta":8,"style":8},"class EmailAddress {\n    public readonly string value; // Exposes the validated email as a property\n    public Email(string value) { /* validates `value` via regex */ }\n}\n\n...\n\nvoid sendReminder(Appointment appointment, EmailAddress email) { ... }\n",[5639],{"type":22,"tag":486,"props":5640,"children":5641},{"__ignoreMap":8},[5642,5659,5692,5730,5737,5744,5756,5763],{"type":22,"tag":896,"props":5643,"children":5644},{"class":898,"line":899},[5645,5650,5655],{"type":22,"tag":896,"props":5646,"children":5647},{"style":1691},[5648],{"type":27,"value":5649},"class",{"type":22,"tag":896,"props":5651,"children":5652},{"style":933},[5653],{"type":27,"value":5654}," EmailAddress",{"type":22,"tag":896,"props":5656,"children":5657},{"style":903},[5658],{"type":27,"value":1626},{"type":22,"tag":896,"props":5660,"children":5661},{"class":898,"line":758},[5662,5667,5672,5677,5682,5687],{"type":22,"tag":896,"props":5663,"children":5664},{"style":1691},[5665],{"type":27,"value":5666},"    public",{"type":22,"tag":896,"props":5668,"children":5669},{"style":1691},[5670],{"type":27,"value":5671}," readonly",{"type":22,"tag":896,"props":5673,"children":5674},{"style":1691},[5675],{"type":27,"value":5676}," string",{"type":22,"tag":896,"props":5678,"children":5679},{"style":933},[5680],{"type":27,"value":5681}," value",{"type":22,"tag":896,"props":5683,"children":5684},{"style":903},[5685],{"type":27,"value":5686},"; ",{"type":22,"tag":896,"props":5688,"children":5689},{"style":2070},[5690],{"type":27,"value":5691},"// Exposes the validated email as a property\n",{"type":22,"tag":896,"props":5693,"children":5694},{"class":898,"line":755},[5695,5699,5704,5708,5712,5716,5720,5725],{"type":22,"tag":896,"props":5696,"children":5697},{"style":1691},[5698],{"type":27,"value":5666},{"type":22,"tag":896,"props":5700,"children":5701},{"style":933},[5702],{"type":27,"value":5703}," Email",{"type":22,"tag":896,"props":5705,"children":5706},{"style":903},[5707],{"type":27,"value":5567},{"type":22,"tag":896,"props":5709,"children":5710},{"style":1691},[5711],{"type":27,"value":5530},{"type":22,"tag":896,"props":5713,"children":5714},{"style":933},[5715],{"type":27,"value":5681},{"type":22,"tag":896,"props":5717,"children":5718},{"style":903},[5719],{"type":27,"value":5595},{"type":22,"tag":896,"props":5721,"children":5722},{"style":2070},[5723],{"type":27,"value":5724},"/* validates `value` via regex */",{"type":22,"tag":896,"props":5726,"children":5727},{"style":903},[5728],{"type":27,"value":5729}," }\n",{"type":22,"tag":896,"props":5731,"children":5732},{"class":898,"line":983},[5733],{"type":22,"tag":896,"props":5734,"children":5735},{"style":903},[5736],{"type":27,"value":1745},{"type":22,"tag":896,"props":5738,"children":5739},{"class":898,"line":1013},[5740],{"type":22,"tag":896,"props":5741,"children":5742},{"emptyLinePlaceholder":4647},[5743],{"type":27,"value":4650},{"type":22,"tag":896,"props":5745,"children":5746},{"class":898,"line":1030},[5747,5751],{"type":22,"tag":896,"props":5748,"children":5749},{"style":1691},[5750],{"type":27,"value":5600},{"type":22,"tag":896,"props":5752,"children":5753},{"style":903},[5754],{"type":27,"value":5755},".\n",{"type":22,"tag":896,"props":5757,"children":5758},{"class":898,"line":1068},[5759],{"type":22,"tag":896,"props":5760,"children":5761},{"emptyLinePlaceholder":4647},[5762],{"type":27,"value":4650},{"type":22,"tag":896,"props":5764,"children":5765},{"class":898,"line":1085},[5766,5770,5774,5778,5782,5786,5790,5794,5798,5802,5806],{"type":22,"tag":896,"props":5767,"children":5768},{"style":1691},[5769],{"type":27,"value":5557},{"type":22,"tag":896,"props":5771,"children":5772},{"style":933},[5773],{"type":27,"value":5562},{"type":22,"tag":896,"props":5775,"children":5776},{"style":903},[5777],{"type":27,"value":5567},{"type":22,"tag":896,"props":5779,"children":5780},{"style":933},[5781],{"type":27,"value":5572},{"type":22,"tag":896,"props":5783,"children":5784},{"style":933},[5785],{"type":27,"value":5577},{"type":22,"tag":896,"props":5787,"children":5788},{"style":903},[5789],{"type":27,"value":236},{"type":22,"tag":896,"props":5791,"children":5792},{"style":933},[5793],{"type":27,"value":5632},{"type":22,"tag":896,"props":5795,"children":5796},{"style":933},[5797],{"type":27,"value":5590},{"type":22,"tag":896,"props":5799,"children":5800},{"style":903},[5801],{"type":27,"value":5595},{"type":22,"tag":896,"props":5803,"children":5804},{"style":1691},[5805],{"type":27,"value":5600},{"type":22,"tag":896,"props":5807,"children":5808},{"style":903},[5809],{"type":27,"value":5605},{"type":22,"tag":23,"props":5811,"children":5812},{},[5813,5815,5820,5822,5827,5829,5834,5836,5842],{"type":27,"value":5814},"Now the precondition \"",{"type":22,"tag":486,"props":5816,"children":5818},{"className":5817},[],[5819],{"type":27,"value":5616},{"type":27,"value":5821}," must be a valid email address\" is encoded in the type of ",{"type":22,"tag":486,"props":5823,"children":5825},{"className":5824},[],[5826],{"type":27,"value":5538},{"type":27,"value":5828}," itself. This precondition is not formally verified, so it depends on the implementation of ",{"type":22,"tag":486,"props":5830,"children":5832},{"className":5831},[],[5833],{"type":27,"value":5632},{"type":27,"value":5835}," being bug-free, but that's an easy one to cover with a couple unit tests. And now, if there's a bug somewhere, the stack trace will be right at the source, i.e. where someone tried to call ",{"type":22,"tag":486,"props":5837,"children":5839},{"className":5838},[],[5840],{"type":27,"value":5841},"new Email()",{"type":27,"value":5843}," with something that isn't an email.",{"type":22,"tag":23,"props":5845,"children":5846},{},[5847],{"type":27,"value":5848},"Object-oriented languages unfortunately tend to require a lot of boilerplate to define data types like these, but it's still an easy way to trade a little bit more verboseness in exchange for a lot of robustness.",{"type":22,"tag":193,"props":5850,"children":5852},{"id":5851},"conclusion",[5853],{"type":27,"value":5854},"Conclusion",{"type":22,"tag":23,"props":5856,"children":5857},{},[5858,5860,5867,5868,5875],{"type":27,"value":5859},"There are many more interesting aspects of Idris to explore (like its Quantitative Type Theory which lets you express whether a variable can be used zero, one, or many times), but this has already been a lot! If you're interested in giving Idris a go, I would recommend ",{"type":22,"tag":30,"props":5861,"children":5864},{"href":5862,"rel":5863},"https://www.youtube.com/watch?v=X36ye-1x_HQ",[34],[5865],{"type":27,"value":5866},"this introductory video",{"type":27,"value":1993},{"type":22,"tag":30,"props":5869,"children":5872},{"href":5870,"rel":5871},"https://idris2.readthedocs.io/en/latest/tutorial/index.html",[34],[5873],{"type":27,"value":5874},"the official \"Crash Course\" documentation",{"type":27,"value":71},{"type":22,"tag":1999,"props":5877,"children":5878},{},[5879],{"type":27,"value":2457},{"title":8,"searchDepth":755,"depth":755,"links":5881},[5882,5883,5884,5885,5889],{"id":3834,"depth":758,"text":3837},{"id":4243,"depth":758,"text":4246},{"id":5438,"depth":758,"text":5441},{"id":5483,"depth":758,"text":5486,"children":5886},[5887,5888],{"id":5494,"depth":755,"text":5497},{"id":5505,"depth":755,"text":5508},{"id":5851,"depth":758,"text":5854},"content:phendry:2021-08-15:ExploringDependentTypesInIdris.md","phendry/2021-08-15/ExploringDependentTypesInIdris.md","phendry/2021-08-15/ExploringDependentTypesInIdris",{"user":774,"name":775},{"_path":5895,"_dir":5896,"_draft":7,"_partial":7,"_locale":8,"title":5897,"description":5898,"publishDate":5899,"tags":5900,"excerpt":5898,"body":5901,"_type":767,"_id":6194,"_source":769,"_file":6195,"_stem":6196,"_extension":772,"author":6197},"/ckeefer/2013-4/teaching-programming","2013-4","Can (and Should) Everyone Learn to Program?","Fair warning: The following article is long, rambly, and contains no code. It does, however, contain some rumination on the idea that everyone can and should learn to program.","2013-12-03",[12],{"type":19,"children":5902,"toc":6192},[5903,5907,5916,5921,5926,5934,5939,5983,5996,6001,6006,6014,6033,6038,6043,6060,6065,6070,6075,6080,6091,6096,6104,6109,6114,6122,6127,6132,6140,6154,6159,6167,6172,6177,6182,6187],{"type":22,"tag":23,"props":5904,"children":5905},{},[5906],{"type":27,"value":5898},{"type":22,"tag":5908,"props":5909,"children":5910},"blockquote",{},[5911],{"type":22,"tag":23,"props":5912,"children":5913},{},[5914],{"type":27,"value":5915},"So, I was just thinking, it would be really great if you could come present to my CS class - talk about programming in general, as a job, that kind of thing.",{"type":22,"tag":23,"props":5917,"children":5918},{},[5919],{"type":27,"value":5920},"Recently an old university friend of mine, now a Math and Physics teacher, was tasked by his administration with teaching computer science to his highschool students.",{"type":22,"tag":23,"props":5922,"children":5923},{},[5924],{"type":27,"value":5925},"His experience with CS being limited to the half a dozen or so courses he took years ago to fill out the minor requirements of his degree, and the prescribed curriculum being heavy on the history and ethics of computing and light on actual programming content, he's been struggling with how best to present the essential concepts in a way that would appeal to typically lethargic teenagers.",{"type":22,"tag":5908,"props":5927,"children":5928},{},[5929],{"type":22,"tag":23,"props":5930,"children":5931},{},[5932],{"type":27,"value":5933},"They really need to be taking the work home, exploring the concepts on their own - there's only so much we can do in an hour a day. There's all sorts of resources out there - I just need a way to spark some excitement about programming in them. Maybe having a 'real' programmer come in would be helpful?",{"type":22,"tag":23,"props":5935,"children":5936},{},[5937],{"type":27,"value":5938},"I agreed, and began to prepare my presentation. While the Way of the Programmer had been known to me since the childhood discovery of the QBasic interpreter in DOS, it wasn't until highschool that I truly took the first steps on this Path to Enlightenment - thanks in large part to a wise teacher/mentor, as per the trope. Doing my part to introduce some young minds to this sometimes frustrating, but often rewarding discipline would be a Good Deed®.",{"type":22,"tag":23,"props":5940,"children":5941},{},[5942,5944,5951,5952,5959,5960,5967,5969,5974,5976,5981],{"type":27,"value":5943},"I had an additional ulterior motive, however. I had often seen espoused - mostly in various communities populated primarily by programmers (such as Hacker News) - the opinion that programming was/is the new literacy; that is, a basic element of education that everyone can and should learn. Both they and my teacher friend had noted the ever-expanding collection of sites dedicated to teaching programming: ",{"type":22,"tag":30,"props":5945,"children":5948},{"href":5946,"rel":5947},"http://codeacademy.com",[34],[5949],{"type":27,"value":5950},"Code Academy",{"type":27,"value":236},{"type":22,"tag":30,"props":5953,"children":5956},{"href":5954,"rel":5955},"http://starterleague.com",[34],[5957],{"type":27,"value":5958},"Starter League",{"type":27,"value":236},{"type":22,"tag":30,"props":5961,"children":5964},{"href":5962,"rel":5963},"http://code.org",[34],[5965],{"type":27,"value":5966},"Code.org",{"type":27,"value":5968},", amongst many others. They seemed certain that not only ",{"type":22,"tag":267,"props":5970,"children":5971},{},[5972],{"type":27,"value":5973},"could",{"type":27,"value":5975}," anyone learn programming, but everyone ",{"type":22,"tag":267,"props":5977,"children":5978},{},[5979],{"type":27,"value":5980},"should",{"type":27,"value":5982}," learn programming.",{"type":22,"tag":23,"props":5984,"children":5985},{},[5986,5988,5994],{"type":27,"value":5987},"I wondered if anyone not already committed to our discipline felt the same way - particularly the students who should apparently be learning their ",{"type":22,"tag":486,"props":5989,"children":5991},{"className":5990},[],[5992],{"type":27,"value":5993},"if, while, foreach",{"type":27,"value":5995}," alongside their ABCs.",{"type":22,"tag":23,"props":5997,"children":5998},{},[5999],{"type":27,"value":6000},"Here was a chance to find out.",{"type":22,"tag":23,"props":6002,"children":6003},{},[6004],{"type":27,"value":6005},"While I mostly wanted to present on programming as a career, my friend had asked that I cover some programming topics and be receptive to any questions the students might have. Also, I wanted to present them with a little programming challenge to cap off the presentation - something simple they could achieve that might spark interest in some of the more complex challenges or learning resources on the web. So, before preparing my presentation, my friend and I had a chat about where his students currently were, programming-wise, three months into the course.",{"type":22,"tag":5908,"props":6007,"children":6008},{},[6009],{"type":22,"tag":23,"props":6010,"children":6011},{},[6012],{"type":27,"value":6013},"Well, I started off trying to take them through some basic control structures - if/else, loops, etc. - but they just couldn't follow the logic. They had a tough time grasping why functions or classes were better than just writing everything in main(), so I had to back off there too. I'm hoping to work them back up to it, but we've really only gotten as far as some basic string manipulation. They... really like video games, though.",{"type":22,"tag":23,"props":6015,"children":6016},{},[6017,6019,6024,6026,6031],{"type":27,"value":6018},"I thanked my friend for his time, feeling reflective. These were students who had ",{"type":22,"tag":267,"props":6020,"children":6021},{},[6022],{"type":27,"value":6023},"chosen",{"type":27,"value":6025}," to take a programming course, in place of another science-related elective - assumably amongst their student population, they would be the ones most interested in (and, by extension, most capable of) learning to program. The unworthy thought that perhaps my teacher friend was just ",{"type":22,"tag":267,"props":6027,"children":6028},{},[6029],{"type":27,"value":6030},"teaching it wrong",{"type":27,"value":6032}," flitted across my mind - while he is a far better mathematician than I will ever be, his CS experience is, as previously mentioned, fairly limited.",{"type":22,"tag":23,"props":6034,"children":6035},{},[6036],{"type":27,"value":6037},"But even if this were true (and I'm quite certain it is not), by the arguments of many of the programming literacy advocates, that shouldn't really matter - these students could always supplement a lackluster in-school education with any of the numerous self-teaching resources.",{"type":22,"tag":23,"props":6039,"children":6040},{},[6041],{"type":27,"value":6042},"The presentation itself went well, I feel - the class was attentive and responsive (as much so as any group of teenagers early in the morning ever are). It was, however, easy to pick out those in the class with minds inclined towards programming. There were moments of dawning comprehension that could be seen in them - not just a matter of possessing more knowledge than their fellows, but the ability to grasp abstractions and make logical connections that the others in the class could not.",{"type":22,"tag":23,"props":6044,"children":6045},{},[6046,6048,6053,6055,6059],{"type":27,"value":6047},"Before expanding upon the previous comment, let's note again the assertions we are considering: that everyone ",{"type":22,"tag":267,"props":6049,"children":6050},{},[6051],{"type":27,"value":6052},"can",{"type":27,"value":6054}," learn programming, and that everyone ",{"type":22,"tag":267,"props":6056,"children":6057},{},[6058],{"type":27,"value":5980},{"type":27,"value":5982},{"type":22,"tag":23,"props":6061,"children":6062},{},[6063],{"type":27,"value":6064},"I've been an athlete for much of my life, and in that arena of life, it's no secret that some people have significant advantages over others. Training is required to reach the pinnacle of personal ability - but it is one's personal ability that is in question. Some people are, and with all else equal will continue to be, taller, stronger, or faster than others; genetics does not adhere to egalitarian principles.",{"type":22,"tag":23,"props":6066,"children":6067},{},[6068],{"type":27,"value":6069},"I strongly suspect that this is true of the mind as well. That's not to say that some people have strong minds, and others weak, anymore than one would categorize all bodies as either strong or weak. Rather, in much the way that an individual might be predisposed towards excellent performance in basketball by being exceptionally tall and possessing good hand-eye coordination, or succeed as a contest-winning bodybuilder by possessing the genetic predisposition towards lean muscle gain; in this way, so too are some minds better equipped 'out of the box' for various types of mental tasks and, all else being equal, will always excel at them relative to their peers. Training can benefit any body, and so too any mind - but there are natural limits imposed by the individual's physical 'hardware' (or wetware, in this instance).",{"type":22,"tag":23,"props":6071,"children":6072},{},[6073],{"type":27,"value":6074},"So, can anyone learn to program? Probably - but maybe not to the same degree.",{"type":22,"tag":23,"props":6076,"children":6077},{},[6078],{"type":27,"value":6079},"Aside: I know this is a major claim to make on the basis of a single presentation to a single class and some years worth of anecdotal evidence. Luckily, scientific rigour is not a requirement for blog posts. :) I look forward to seeing some real studies done on this topic, someday... perhaps if/when programming truly becomes considered as essential as primary human language literacy.",{"type":22,"tag":23,"props":6081,"children":6082},{},[6083,6085,6089],{"type":27,"value":6084},"Let's turn our attention on the second part of the premise, now: that everyone ",{"type":22,"tag":267,"props":6086,"children":6087},{},[6088],{"type":27,"value":5980},{"type":27,"value":6090}," learn to program. It seems obvious to a programmer, of course - computers are everywhere, and programming is how one interacts most directly and powerfully with these devices. The more computer-centric our society becomes, the more imperative it is that everyone know how to program.",{"type":22,"tag":23,"props":6092,"children":6093},{},[6094],{"type":27,"value":6095},"In the classroom, the scenario plays out differently. Near the middle of the presentation, I broke out the whiteboard marker and began an exploration of binary and hexadecimal with them. After breaking down the place value of each bit, we talked about the total value of a single byte - 128+64+32+16+8+4+2+1 = decimal 255. Then, we broke apart a two-digit hexadecimal number.",{"type":22,"tag":5908,"props":6097,"children":6098},{},[6099],{"type":22,"tag":23,"props":6100,"children":6101},{},[6102],{"type":27,"value":6103},"So, if the ones column can contain values from 0-15, represented by 0-9, A-F, and what would be the 'tens' column for decimal is here the 'sixteens' column, and we have the hexadecimal number FF, what does that represent?",{"type":22,"tag":23,"props":6105,"children":6106},{},[6107],{"type":27,"value":6108},"The light came on almost immediately for one student - I ignored his hand, though, waving at me from the back row, as he'd had been the one who always 'got it' first. After a few more moments, another student tentatively offered: 255? Yes, I said, which is the same as... and after a few more moments, his face brightened as he made the connection between hexadecimal as a sort of shorthand representation for binary.",{"type":22,"tag":23,"props":6110,"children":6111},{},[6112],{"type":27,"value":6113},"The other students, however, complained that I had told them math wasn't important for programming (which was true - I'd only just finished explaining how programming was really an extension of language skills, while specific algorithms were where math found its expression in programming).",{"type":22,"tag":5908,"props":6115,"children":6116},{},[6117],{"type":22,"tag":23,"props":6118,"children":6119},{},[6120],{"type":27,"value":6121},"That's true, I did say math wasn't important. But we aren't talking math - this is still language. Can anyone tell me why?",{"type":22,"tag":23,"props":6123,"children":6124},{},[6125],{"type":27,"value":6126},"Only the one eager grade 12 in the back row was able to make the connection that binary was the 'language' of the computer (which I'd already mentioned earlier), and everything else - hexadecimal, assembly, C and the other programming languages we'd spoken about - were 'translation layers' between the computer and the programmer.",{"type":22,"tag":23,"props":6128,"children":6129},{},[6130],{"type":27,"value":6131},"Continuing, I told them that, although they should never have to program in binary, or even hexadecimal, a computer programmer should have some idea of how a computer actually works, and the language it understands. One of the students who hadn't got it sort of shrugged and volunteered on the class' behalf that:",{"type":22,"tag":5908,"props":6133,"children":6134},{},[6135],{"type":22,"tag":23,"props":6136,"children":6137},{},[6138],{"type":27,"value":6139},"I don't think any of us actually wants to be a programmer.",{"type":22,"tag":23,"props":6141,"children":6142},{},[6143,6145,6152],{"type":27,"value":6144},"This didn't strike me as too surprising - not too many teenagers have a strong sense of what they want their eventual career to be. The more telling point was that, on further conversation, most of the students admitted that while they appreciated the end results of programming (such as video games), and enjoyed a superficial understanding of how they worked, the actual details of classes and objects and models and servers and so on, left them feeling much the way many do ",{"type":22,"tag":30,"props":6146,"children":6149},{"href":6147,"rel":6148},"http://www.plosone.org/article/info:doi/10.1371/journal.pone.0048076",[34],[6150],{"type":27,"value":6151},"when faced with the prospect of math",{"type":27,"value":6153}," - uncomfortable, and decidedly disinterested in pursuing it.",{"type":22,"tag":23,"props":6155,"children":6156},{},[6157],{"type":27,"value":6158},"After the class, my teacher friend and I were able to head for coffee over his lunch break, and we talked about his efforts in teaching computer science, alongside his math and science classes.",{"type":22,"tag":5908,"props":6160,"children":6161},{},[6162],{"type":22,"tag":23,"props":6163,"children":6164},{},[6165],{"type":27,"value":6166},"It's pretty much the same for all of them,\" he admitted. \"A few students are really enthusiastic and pick up concepts quickly. Others - they may never get it, and many don't even really want to. They're not necessarily stupid or inattentive - their marks in other courses can be anywhere from terrible to great - they just don't have any interest in the sciences.",{"type":22,"tag":23,"props":6168,"children":6169},{},[6170],{"type":27,"value":6171},"A key part of higher education is picking a specialization - even the most capable human needs to shy away from attempting be a polymath given the sheer breadth of human knowledge in the modern era. During our conversation, we made the comparison to understanding an internal combustion engine (I know, car metaphors - bear with me). Most people have an extremely superficial understanding of how an internal combustion engine (and their car in general) works - and as long as that doesn't impede them in the tasks they want to pursue with that device, they have no desire to learn more. Indeed, learning more would be an undesirable burden that would get in the way of performing other tasks, including those involving the vehicle in question.",{"type":22,"tag":23,"props":6173,"children":6174},{},[6175],{"type":27,"value":6176},"Someone with in-depth knowledge of how a car works might argue that they could get so much more out of the vehicle if they understood how it actually worked, and could tweak it as they desired - or at least maintain it properly. They'd be right, of course - but the time cost involved in becoming intimately acquainted with the operation of the vehicle is prohibitive to anyone who isn't making their living as a mechanic.",{"type":22,"tag":23,"props":6178,"children":6179},{},[6180],{"type":27,"value":6181},"It took us two coffees and a bagel each to reach the conclusion that probably, most people weren't going to want to be programmers if they weren't going to make money at it. In the foreseeable future, most people will continue to have a superficial understanding of how their various computing devices work 'under the hood', and will be entirely content with that. Even if programming did become a new 'literacy' requirement in the future, it would likely be at the same level that writing is for most of the populace - that is, while any literate individual can write, only a small percentage of the population write well enough to make a living at it.",{"type":22,"tag":23,"props":6183,"children":6184},{},[6185],{"type":27,"value":6186},"And one other nightmare scenario that occurs to me just now... who will be tasked with repairing/maintaining the code generated by these citizen programmers in the future, I wonder? I hope I'm retired by then...",{"type":22,"tag":23,"props":6188,"children":6189},{},[6190],{"type":27,"value":6191},"What do you think? Is programming going to become a primary concern of education in the coming years? If it does, will people really embrace it, or remember it about as well as they do their highschool trig lessons? And what happens to programming as a discipline if 'anyone' could write a program, the same way that 'anyone' could write a story?",{"title":8,"searchDepth":755,"depth":755,"links":6193},[],"content:ckeefer:2013-4:teaching-programming.md","ckeefer/2013-4/teaching-programming.md","ckeefer/2013-4/teaching-programming",{"user":6198,"name":6199},"ckeefer","Christopher Keefer",1780330265434]