<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Robert Johnson</title>
    <description>The latest articles on DEV Community by Robert Johnson (@robertmjohnson).</description>
    <link>https://dev.to/robertmjohnson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F16794%2F97809272-9871-4c62-997f-672dd3385309.jpg</url>
      <title>DEV Community: Robert Johnson</title>
      <link>https://dev.to/robertmjohnson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robertmjohnson"/>
    <language>en</language>
    <item>
      <title>A Developer's Guide to Blogging</title>
      <dc:creator>Robert Johnson</dc:creator>
      <pubDate>Sat, 26 Aug 2023 14:58:52 +0000</pubDate>
      <link>https://dev.to/robertmjohnson/a-developers-guide-to-blogging-n01</link>
      <guid>https://dev.to/robertmjohnson/a-developers-guide-to-blogging-n01</guid>
      <description>&lt;p&gt;So you're a developer looking to start a blog? In this guide I'm going to take you through running your own blog site, from domain names &amp;amp; site creation to SEO &amp;amp; syndication.&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://hackernoon.com/get-started-blogging-an-essential-guide-for-developers"&gt;Originally featured at HackerNoon&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Section 0 (optional) - Start writing on blog platforms

&lt;ul&gt;
&lt;li&gt;Dev.to&lt;/li&gt;
&lt;li&gt;Hashnode&lt;/li&gt;
&lt;li&gt;HackerNoon&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Section 1 - Buy a domain

&lt;ul&gt;
&lt;li&gt;What domain name to choose?&lt;/li&gt;
&lt;li&gt;Where to buy a domain name?&lt;/li&gt;
&lt;li&gt;Domain costs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Section 2 - Create a simple site using a Static Site Generator

&lt;ul&gt;
&lt;li&gt;What is a Static Site Generator?&lt;/li&gt;
&lt;li&gt;Why use an SSG?&lt;/li&gt;
&lt;li&gt;Creating a basic site with Hugo&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Section 3 - Get your blog hosted&lt;/li&gt;
&lt;li&gt;Section 4 - Analytics&lt;/li&gt;
&lt;li&gt;
Section 5 - Basic SEO

&lt;ul&gt;
&lt;li&gt;Letting Google know you exist&lt;/li&gt;
&lt;li&gt;Checking for issues&lt;/li&gt;
&lt;li&gt;Canonicalization &amp;amp; Canonical URLs&lt;/li&gt;
&lt;li&gt;Private GitHub repo&lt;/li&gt;
&lt;li&gt;Backlinks &amp;amp; domain authority&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Section 6 - Sharing your work

&lt;ul&gt;
&lt;li&gt;Use Reddit&lt;/li&gt;
&lt;li&gt;Post on specific subreddit(s)&lt;/li&gt;
&lt;li&gt;Check the rules for each subreddit&lt;/li&gt;
&lt;li&gt;Be part of the community&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Section 7 - Republishing on blog platforms

&lt;ul&gt;
&lt;li&gt;
Canonicalization for republished articles

&lt;ul&gt;
&lt;li&gt;Canonicals for dev.to&lt;/li&gt;
&lt;li&gt;Canonicals on Hashnode&lt;/li&gt;
&lt;li&gt;Canonicalization on HackerNoon&lt;/li&gt;
&lt;li&gt;You can't canonicalize on FreeCodeCamp!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Publicity done for you&lt;/li&gt;
&lt;li&gt;Republishing drawbacks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Appendix

&lt;ul&gt;
&lt;li&gt;
Why blog?

&lt;ul&gt;
&lt;li&gt;Improves your writing &amp;amp; communication skills&lt;/li&gt;
&lt;li&gt;Gives you a way to prove you know what you're talking about&lt;/li&gt;
&lt;li&gt;Helps you market yourself &amp;amp; creates opportunities&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
A quick comparison of SSGs

&lt;ul&gt;
&lt;li&gt;Jekyll&lt;/li&gt;
&lt;li&gt;Eleventy&lt;/li&gt;
&lt;li&gt;Gatsby&lt;/li&gt;
&lt;li&gt;Hugo&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Section 0 (optional) - Start writing on blog platforms
&lt;/h2&gt;

&lt;p&gt;If you want to warm up a bit before going through the effort of setting up your own site, the easiest &amp;amp; simplest thing to do is to just get writing and contribute your work to existing blogging platforms.&lt;/p&gt;

&lt;p&gt;Ultimately the hardest thing about blogging is the actual writing - at least, that's usually the case for us developers! So it doesn't hurt to get a feel for it first to see if you enjoy it. Also, as we'll see later, these are still useful to make use of even if/when you set up your own separate blog site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dev.to
&lt;/h3&gt;

&lt;p&gt;You've probably heard of &lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt; already - it's still the biggest developer blog platform and it's where I recommend you start. You can easily start writing using their simple markdown editor, and it has a large readership so you'll get a good number of eyes on your work straight away.&lt;/p&gt;

&lt;p&gt;It has great built-in analytics, which can help you see how many people are reading your posts, and even how they found them - e.g. Reddit or Twitter. This helps you see what's working best when you share your work.&lt;/p&gt;

&lt;p&gt;Its design contains a lot of social media style elements, with likes &amp;amp; reactions to posts, as well as message-board style discussion threads. (This could be a pro or a con, depending on your tastes.)&lt;/p&gt;

&lt;p&gt;If in any doubt, start here!&lt;/p&gt;

&lt;h3&gt;
  
  
  Hashnode
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hashnode.com/"&gt;Hashnode&lt;/a&gt; is a newer dev blog site. In my opinion it has a much more professional feel than dev.to. It feels much more blog-oriented than dev.to, and less like a social media site; it gives you a separate blog subdomain, giving your blog a bit of its own separate identity within the site. If you want to you can even hook it up to your own private domain name if you wanted.&lt;/p&gt;

&lt;p&gt;Sadly though it's a lot less popular than dev.to, and in my experience I hardly get any traffic through it. If you like its cleaner style it could be worth a look, but expect to do more legwork to get your posts noticed.&lt;/p&gt;

&lt;h3&gt;
  
  
  HackerNoon
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hackernoon.com/"&gt;HackerNoon&lt;/a&gt; is very different to dev.to &amp;amp; Hashnode in that any article you submit there has to go through a human editor who works with you to ensure your article is at its best before it is published. However, they may choose not to publish your article at all.&lt;/p&gt;

&lt;p&gt;This has its tradeoffs; on the one hand this is a great learning experience, but on the other it limits your freedom to simply post when and what you want. Therefore I would recommend submitting work to HackerNoon for the learning experience it will give you, but consider keeping the primary home for your blog elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 1 - Buy a domain
&lt;/h2&gt;

&lt;p&gt;Sadly, creating your own blog involves one of the most dreaded developer tasks - naming things! Though not very difficult from a purely technical point of view, it's worth looking into and thinking about up-front; think it over while you're getting your site set up.&lt;/p&gt;

&lt;h3&gt;
  
  
  What domain name to choose?
&lt;/h3&gt;

&lt;p&gt;There are two main choices here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Name your blog after yourself&lt;/li&gt;
&lt;li&gt;Give your blog its own identity with a separate name&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've come across varying opinions on each; for my own blog I've simply named it after myself. My thinking is that if you're using a blog to help get your name out there, why not use your name in your blog? (If &lt;a href="https://youtu.be/hFjwbKMlmF4?t=24"&gt;this logic is good enough for Will Smith&lt;/a&gt;, it's good enough for me! 😅)&lt;/p&gt;

&lt;p&gt;Whatever you choose remember that your domain is a bit of a pain to change, so it's worth taking the time to find one you're confident with. However you may have to rethink or adapt your domain if the one you'd like is already taken, so check if it's available before you get your heart too set on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to buy a domain name?
&lt;/h3&gt;

&lt;p&gt;You'll probably have heard of many domain registrars already, such as &lt;a href="https://www.bluehost.com/"&gt;BlueHost&lt;/a&gt;, &lt;a href="https://www.hostinger.co.uk/"&gt;Hostinger&lt;/a&gt;, &lt;a href="https://www.godaddy.com"&gt;GoDaddy&lt;/a&gt;, and &lt;a href="https://www.namecheap.com/"&gt;Namecheap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, a key one to check out is &lt;a href="https://domains.google/"&gt;Google Domains&lt;/a&gt;, because &lt;strong&gt;this is the only registrar which sells &lt;code&gt;.dev&lt;/code&gt; domains&lt;/strong&gt;. My advice is that &lt;code&gt;.com&lt;/code&gt; is still the best if you can get one, so if there's one available that you like then go for it, but if not then &lt;code&gt;.dev&lt;/code&gt; could be a great alternative.&lt;/p&gt;

&lt;h3&gt;
  
  
  Domain costs
&lt;/h3&gt;

&lt;p&gt;Note that domains cost varying amounts, and moreover &lt;strong&gt;you have to pay up every year&lt;/strong&gt; to keep them. Domains can be as cheap as $10 per year, so unless you're buying a domain for business purposes, think hard before shelling out for an expensive one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 2 - Create a simple site using a Static Site Generator
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a Static Site Generator?
&lt;/h3&gt;

&lt;p&gt;A static site generator - or SSG - will template &amp;amp; generate the content for a website such that it can be served up as static files held on a web server. After setting up the main structure of the site - such as your home page, posts index, 'about' page, etc. - you can generate complete pages simply by adding markdown files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use an SSG?
&lt;/h3&gt;

&lt;p&gt;In short, SSGs&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;give you fast sites&lt;/li&gt;
&lt;li&gt;let you publish using a &lt;a href="https://about.gitlab.com/topics/gitops/"&gt;GitOps&lt;/a&gt; workflow&lt;/li&gt;
&lt;li&gt;are secure (no database to hack into!)&lt;/li&gt;
&lt;li&gt;let you take advantage of your tech skills, rather than being limited to what
platforms can provide&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As developers we use something pretty much every day which does something very similar: GitHub. In our code repos we'll write readmes &amp;amp; other documentation in markdown, and GitHub nicely formats it for viewing on a webpage.&lt;/p&gt;

&lt;p&gt;An SSG can let you follow much the same GitOps workflow for your own blog. When I got started writing on dev.to I managed my drafts in a Git repo. This was fine up until wanted to publish, at which point I needed to transfer the contents into their web editor. This was fine, but when I found out that SSGs can let you use the same workflow as for updating a readme in GitHub, that was enough for me to want to find out more.&lt;/p&gt;

&lt;p&gt;A common way to set up a blog is to use something like WordPress; this has plenty of advantages for sure, but it stores all your content in a database. A statically-generated site doesn't need one, which for one thing means it will be much faster. Speed isn't everything of course, but &lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals#signals"&gt;Google cares about how fast your site loads&lt;/a&gt;, so why not take advantage of the speed that an SSG-generated site can give you?&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a basic site with Hugo
&lt;/h3&gt;

&lt;p&gt;For creating a static site I recommend &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;. In short this is because it is popular, well-supported, fast, and allows you to get up and running quickly with premade templates.&lt;/p&gt;

&lt;p&gt;(For details on alternative SSGs, see the Comparing SSGs section in the appendix.)&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://gohugo.io/getting-started/quick-start/"&gt;quick start page&lt;/a&gt; of Hugo's official docs gives a great description of getting a basic Hugo site set up. Follow the steps it provides, but when you reach its &lt;a href="https://gohugo.io/getting-started/quick-start/#commands"&gt;'create a site' commands&lt;/a&gt;, I recommend changing them instead to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo new site quickstart
&lt;span class="nb"&gt;cd &lt;/span&gt;quickstart
git init
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"/public/"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"/resources/_gen/"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".hugo_build.lock"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
git clone https://github.com/leafee98/hugo-theme-flat themes/flat
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; themes/flat/.git/ themes/flat/.github/
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"theme = flat"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; hugo.toml
hugo server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has the following differences to the official guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets up a &lt;code&gt;.gitignore&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Uses the excellent &lt;a href="https://github.com/leafee98/hugo-theme-flat"&gt;'Flat'
theme&lt;/a&gt; instead of the Ananke
theme&lt;/li&gt;
&lt;li&gt;Sets up the theme as code within your own repo rather than as a git submodule;
this makes it a lot easier to tweak the theme to your own taste later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When creating your GitHub repo, make it private&lt;/strong&gt; - this is for SEO reasons, which we'll explore later.&lt;/p&gt;

&lt;p&gt;Once you've followed the guide then you'll have a site ready to host! At this point there will most likely be aspects to the site that you'll want to change. However, you don't need to let that stop you from getting hosted. &lt;strong&gt;The main thing that you want to avoid changing later is your URLs&lt;/strong&gt;; everything else can be changed later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 3 - Get your blog hosted
&lt;/h2&gt;

&lt;p&gt;As the &lt;a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-hugo-site/#create-a-github-repository"&gt;Hosting and Deployment&lt;/a&gt; section of the Hugo docs describes, since you've got a static site it can be hosted virtually anywhere, and almost certainly for free too. Of course there are plenty of free/cheap hosts for WordPress-based sites as well, but any particular host will only give you so much bandwidth; in general, a static site gives your more bandwidth for your buck, even if you haven't actually handed over any money.&lt;/p&gt;

&lt;p&gt;Hugo's hosting guide lists plenty of possibilities, but personally I can agree with &lt;a href="https://www.brycewray.com/posts/2023/03/publish-or-perish-2023/"&gt;Bryce Wray's recommendation&lt;/a&gt; to go with &lt;a href="https://pages.cloudflare.com/"&gt;CloudFlare Pages&lt;/a&gt;; its free tier is possibly the fastest out there, and it's easy to use. Simply follow &lt;a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-hugo-site/#create-a-github-repository"&gt;their guide from 'setting up a GitHub repository'&lt;/a&gt; onwards. At this point your site will be online! But you'll have an ugly domain like &lt;code&gt;my-blog-xyz.pages.dev&lt;/code&gt;. Simply follow &lt;a href="https://developers.cloudflare.com/pages/platform/custom-domains/#add-a-custom-domain"&gt;CloudFlare's guide on setting up a custom&lt;/a&gt; domain to get your site live on the domain which you bought earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 4 - Analytics
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Vanity of vanities, all is vanity!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— Ecclesiastes 1:2&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now strictly speaking this is an optional step, but at this point it's worth setting up analytics for your site. Perhaps I'm just vain, but for me a lot of the fun of blogging is being able to see that people actually see and care about what you've written.&lt;/p&gt;

&lt;p&gt;One limitation of setting up a static site is that you can't host analytics yourself since that would require some sort of database. But that's not too much of a problem anyway since there are plenty of good third-party analytics providers available. The biggest one which you'll likely have heard of is Google Analytics.&lt;/p&gt;

&lt;p&gt;Setting up analytics with a third-party provider generally involves&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting up an account&lt;/li&gt;
&lt;li&gt;Adding a javascript snippet or link to your pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the Hugo template I recommended, you would simply add something like the following to your &lt;code&gt;head.html&lt;/code&gt; template file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{/* Include analytics, but only in production */}}
{{- if hugo.IsProduction | or (eq site.Params.env "production") }}
&amp;lt;script defer data-domain="yourdomain.com" src="/link/to/script.js"&amp;gt;&amp;lt;/script&amp;gt;
{{- end }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to set up GA for your site, follow &lt;a href="https://support.google.com/analytics/answer/9304153?sjid=31720388772907155-EU"&gt;Google's official docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The analytics provider I've gone with is &lt;a href="https://plausible.io/"&gt;Plausible&lt;/a&gt;. Sadly it's not free - about $9 a month - but it's easy to use, lightweight (the script is less than 1kb), and respects privacy, so it's worth a look IMO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 5 - Basic SEO
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you build it, they will come&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— Field of Dreams&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is &lt;em&gt;some&lt;/em&gt; truth to the above quote; there's not too much you need to do to get your blog on Google, and thankfully &lt;a href="https://daedtech.com/seo-for-non-scumbags-how-to-earn-site-visitors-without-selling-your-soul/"&gt;the days of keyword-stuffing shenanigans are over&lt;/a&gt;; what ultimately matters is &lt;a href="https://developers.google.com/search/updates/helpful-content-update"&gt;writing good content&lt;/a&gt;, which is good news for independent bloggers such as us.&lt;/p&gt;

&lt;p&gt;That said, there are a few things you can do to make sure that your site is up to scratch for search engines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Letting Google know you exist
&lt;/h3&gt;

&lt;p&gt;Google's web crawlers will find your site eventually, but it certainly helps to give Google a head start and get your site set up on &lt;a href="https://search.google.com/search-console/about"&gt;Google search console&lt;/a&gt;. You'll want to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign up&lt;/li&gt;
&lt;li&gt;Register your domain address (this will be greatly simplified if your bought
your domain from Google Domains)&lt;/li&gt;
&lt;li&gt;Submit the URL for your site's &lt;code&gt;sitemap.xml&lt;/code&gt; on the sitemap tab. Thankfully,
Hugo will have generated one for you at &lt;code&gt;https://yourdomain.com/sitemap.xml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this is done then Google will at some point over the next few days start a crawl of your website. Note that there's a bit of a lag of a few days to the search console though. The most reliable way of seeing what's indexed on your site is to google using a &lt;code&gt;site:&lt;/code&gt; query, e.g. &lt;code&gt;site:yourdomain.dom&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another useful benefit to getting set up on the search console is that under the 'Pages' tab you can see any reported issues about why Google can't or won't index your pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking for issues
&lt;/h3&gt;

&lt;p&gt;Hugo and the template we're using should cover most of the bases for good SEO practices. But still, it doesn't hurt to check your site on &lt;a href="https://pagespeed.web.dev/"&gt;Google's PageSpeed Insights tool&lt;/a&gt;. (This will become more important if/when you start making your site more to your own liking by tweaking the template files.) This will give you a good overview of the performance, accessibility and any SEO issues (such as missing meta tags) for the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Canonicalization &amp;amp; Canonical URLs
&lt;/h3&gt;

&lt;p&gt;One SEO concept that is key to understanding as a blogger is &lt;a href="https://moz.com/learn/seo/canonicalization"&gt;canonicalization &amp;amp; canonical URLs&lt;/a&gt;. Basically the idea is that the same content can be accessed via different URLs, but you don't want Google to split the page ranking for a single page across multiple URLs. Therefore you can have a page declare what URL search engines should consider as being &lt;em&gt;the&lt;/em&gt; URL for the page.&lt;/p&gt;

&lt;p&gt;Remember that CloudFlare also generates an "ugly" domain like &lt;code&gt;my-blog-xyz.pages.dev&lt;/code&gt; alongside your custom domain. Most (if not all) hosting providers don't allow you to turn off this basic domain, but so long as you have canonical URLs set up on your pages this won't be a problem - Google will only list under your custom domain, not the "ugly" one.&lt;/p&gt;

&lt;p&gt;One of the reasons that I recommend the Flat theme for Hugo is that (unlike the recommended default of Ananke) it already includes a canonical link. However if you want to use a different theme you can add this simply to your header template like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="canonical" href="{{ .Permalink }}" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check that this is set up correctly for any given page by checking it in the PageSpeed Insights tool mentioned above; you'll see whether a page has a canonical URL as a checklist item under the "SEO" section.&lt;/p&gt;

&lt;p&gt;We'll explore canonicalization further in the section on republishing, further below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private GitHub repo
&lt;/h3&gt;

&lt;p&gt;You'll recall earlier that you made your GitHub repo private. This is because (at time of writing) &lt;strong&gt;you can't add canonical URLs&lt;/strong&gt; or &lt;a href="https://developers.google.com/search/docs/crawling-indexing/block-indexing"&gt;noindex tags&lt;/a&gt; to the markdown documents, and so this serves as another subtle code duplication issue. A private repo works around this issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backlinks &amp;amp; domain authority
&lt;/h3&gt;

&lt;p&gt;You've likely heard that one of the factors in Google ranking a site is how many other sites link to it; this is known as &lt;em&gt;domain authority&lt;/em&gt;, and such links from one site to another in this context are known as &lt;em&gt;backlinks&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I'm not going to talk about how to "farm" backlinks to make your blog rank higher on Google; for one thing Google has got wise to such schemes, and besides one of the benefits of creating your own site is that you can help make the web a better place rather than further filling it with such douchebaggery.&lt;/p&gt;

&lt;p&gt;The main takeaway instead is simply that &lt;strong&gt;it'll take time before you can rank well on Google&lt;/strong&gt;; you won't have much domain authority to start with, but that will grow over time. As ever, your focus should simply be on writing the best posts you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 6 - Sharing your work
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Advertising is only evil when it advertises evil things.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— David Ogilvy&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As much as I would love to say that Google being able to find your blog is enough, realistically it really helps to do a bit of self-publicity and share your posts online.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Reddit
&lt;/h3&gt;

&lt;p&gt;The best way to do this is by post links to your articles on Reddit. Posting on social media doesn't hurt, but the key advantage with Reddit is that its voting system means that good posts (as I'm sure yours will be) can stay on a subreddit's front pages for a while, rather than immediately getting swept along down the timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post on specific subreddit(s)
&lt;/h3&gt;

&lt;p&gt;Post to a specific subreddit such as the programming language or technology which you're writing about. The bigger the subreddit the more people will potentially see your post, but the catch is that big subreddits like &lt;code&gt;/r/programming&lt;/code&gt; can often have a lot of people tactically downvoting good posts simply to bury them, in order to help keep their own posts on top.&lt;/p&gt;

&lt;p&gt;Instead, you'll have more luck by sticking to the most specific Reddits that apply to your post; such communities are more likely to care and read your post, and they're more receptive to self-publicity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the rules for each subreddit
&lt;/h3&gt;

&lt;p&gt;If you're unfamiliar with a subreddit &lt;strong&gt;always check its community rules before posting&lt;/strong&gt;. Each subreddit will have different rules, in particular different tolerances to linking to your own articles. This can vary between total bans on self-publicity, an expectation that you don't self-post too much, or no restrictions at all.&lt;/p&gt;

&lt;p&gt;Another key rule to &lt;strong&gt;watch out for is undesired post topics&lt;/strong&gt; with possibly a redirection towards a more appropriate subreddit instead. For example &lt;code&gt;/r/programming&lt;/code&gt;'s rules ask you to redirect technical questions towards &lt;code&gt;r/learnprogramming&lt;/code&gt; instead, and similarly job listings towards &lt;code&gt;/r/forhire&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Be part of the community
&lt;/h3&gt;

&lt;p&gt;Regardless of the rules for the subreddit you're submitting to, it's always a good idea to be a part of the community rather than only using Reddit as a tool for your own ends. Post links from other blogs you enjoy, engage in discussions and just have fun.&lt;/p&gt;

&lt;p&gt;Read through the &lt;a href="https://support.reddithelp.com/hc/en-us/articles/205926439"&gt;"Reddiquette"&lt;/a&gt; once in a while to check that you're taking part in a responsible way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 7 - Republishing on blog platforms
&lt;/h2&gt;

&lt;p&gt;As rewarding as it is to have your own separate blog it's still useful to republish posts on dev blogging platforms such as dev.to. You might wonder why we'd come back around full circle after taking the time to set up your own blog.&lt;/p&gt;

&lt;p&gt;Ultimately all that matters is getting your thoughts read and appreciated by other developers, and republishing helps you do that simply by adding another way for people to come across your work. Another key benefit though is that it helps you build up domain authority for your own blog through backlinks - but in order for your site to be credited with those backlinks, you need to republish in the right way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Canonicalization for republished articles
&lt;/h3&gt;

&lt;p&gt;Remember the concept of canonical URLs that we discussed earlier? When reposting your articles elsewhere, ideally you only want to post on sites that let you indicate that the canonical version of your article is the one on your blog. That way you get the exposure benefits of republishing but any resulting backlinks build up the domain authority of your own blog.&lt;/p&gt;

&lt;h4&gt;
  
  
  Canonicals for dev.to
&lt;/h4&gt;

&lt;p&gt;As detailed in &lt;a href="https://dev.to/p/editor_guide#front-matter"&gt;dev.to's editor guide&lt;/a&gt;, you can add a canonical URL simply by adding a &lt;code&gt;canonical_url&lt;/code&gt; property to a post's "front matter" properties.&lt;/p&gt;

&lt;h4&gt;
  
  
  Canonicals on Hashnode
&lt;/h4&gt;

&lt;p&gt;You can set a canonical URL on a Hashnode article under the "Are you republishing?" section on the "Article settings" view for an article. (See &lt;a href="https://support.hashnode.com/en/articles/6556971-how-to-set-a-canonical-link"&gt;their docs&lt;/a&gt; for further details.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Canonicalization on HackerNoon
&lt;/h4&gt;

&lt;p&gt;As covered in &lt;a href="https://hackernoon.com/reposting-and-canonical-linking"&gt;HackerNoon's docs&lt;/a&gt;, you can set a canonical URL in the "First Seen At" in your post's settings.&lt;/p&gt;

&lt;h4&gt;
  
  
  You can't canonicalize on FreeCodeCamp!
&lt;/h4&gt;

&lt;p&gt;As noted by &lt;a href="https://townhall.hashnode.com/why-you-should-republish-your-devblog-posts-and-how-to-do-it#-think-twice-before-republishing-posts-on-freecodecamp-"&gt;this Hashnode article&lt;/a&gt;, think twice before republishing your work on FreeCodeCamp since they don't let you set a canonical URL. Indeed, &lt;a href="https://dev.to/ben/i-m-concerned-with-the-move-that-freecodecamp-just-pulled-by-leaving-medium-io8"&gt;this dev.to post&lt;/a&gt; details the negative impact this has made on certain bloggers because of this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publicity done for you
&lt;/h3&gt;

&lt;p&gt;Another benefit of republishing is that some of the legwork for sharing your work on Reddit &amp;amp; other social media will often be done for you. Bots and bloggers who curate articles for particular communities will often find your work through particular tags on blog platforms, and share it on social media.&lt;/p&gt;

&lt;h3&gt;
  
  
  Republishing drawbacks
&lt;/h3&gt;

&lt;p&gt;Republishing isn't without some costs, however:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can be a bit of work to repost on these sites, especially in contrast to the nice GitOps flow when publishing to your own site. (Some automated &amp;amp; semi-automated solutions are available to help things here though.)&lt;/li&gt;
&lt;li&gt;It's possible for republished work to outrank your originals on search engines. Ideally canonical URLs will mean that the republished work is unindexed altogether, but this isn't guaranteed (especially for Bing, in my experience)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore it's worth considering &lt;strong&gt;republishing some time after you publish the original&lt;/strong&gt; (say, one or two weeks, but it's up to you). This has the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced duplication of work making corrections &amp;amp; additions across your original &amp;amp; republications; inevitably, despite your proofreading efforts, you or your readers will spot issues or things you missed - better to have to do this while the article only exists on your own blog&lt;/li&gt;
&lt;li&gt;Search engines have time to index your site first&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why blog?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Improves your writing &amp;amp; communication skills
&lt;/h4&gt;

&lt;p&gt;While thankfully Agile means that software development rarely involves producing reams &amp;amp; reams of documents like it may have done in previous eras, I think that the pendulum has swung a bit too far the other way and so good documentation which helps to introduce newcomers to a system or repo is sadly neglected. And as you progress in your career as a developer you'll have more and more need to distil your thinking and explain complicated concepts &amp;amp; systems in a clear &amp;amp; concise way.&lt;/p&gt;

&lt;p&gt;Blogging gives you a great way to practice these skills. In particular, having an audience is a great motivating factor!&lt;/p&gt;

&lt;h4&gt;
  
  
  Gives you a way to prove you know what you're talking about
&lt;/h4&gt;

&lt;p&gt;It's one thing to have a list of technologies you know listed on your resume; it's quite another to prove that you really understand them by being able to link to articles you've written that clearly demonstrate that you know your craft inside &amp;amp; out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Helps you market yourself &amp;amp; creates opportunities
&lt;/h4&gt;

&lt;p&gt;Jeff Atwood met Joel Spolsky in part through his blog, &lt;a href="https://blog.codinghorror.com/"&gt;Coding Horror&lt;/a&gt;, and they then went on to create a little thing called Stack Overflow.&lt;/p&gt;

&lt;p&gt;You can't guarantee such things will happen simply through creating a blog, but it certainly doesn't hurt either. And especially in niche technology areas, you might find your name might stick out to someone through them having read an article of yours in the past.&lt;/p&gt;

&lt;h3&gt;
  
  
  A quick comparison of SSGs
&lt;/h3&gt;

&lt;p&gt;Here are my thoughts on a few popular SSGs, and how I came to settle on Hugo. See &lt;a href="https://jamstack.org/generators/"&gt;jamstack.org&lt;/a&gt; for a much bigger list.&lt;/p&gt;

&lt;h4&gt;
  
  
  Jekyll
&lt;/h4&gt;

&lt;p&gt;Jekyll used to be very popular but it is less so now. It powers GitHub pages and is very blog-oriented. I initially considered it, but was put off by its strict URL structure which insists that blog pages have a date component embedded within the URL, which is something I wanted to avoid for my own blog.&lt;/p&gt;

&lt;p&gt;This can be avoided by making writing posts as part of a more abstract &lt;a href="https://jekyllrb.com/docs/collections/"&gt;"collection"&lt;/a&gt; type, but then that would lose many of the benefits of working within its own blog posts abstraction.&lt;/p&gt;

&lt;h4&gt;
  
  
  Eleventy
&lt;/h4&gt;

&lt;p&gt;Eleventy is an SSG that has been gaining a lot of popularity. It is powered by node.js, and is very flexible; you can customise the templating engine and markdown renderer, even allowing you to use different options for different pages within the same site.&lt;/p&gt;

&lt;p&gt;The main drawback I found with this is that it doesn't come with any built-in templates, so you can't easily just generate a blog straight out of the box. Also of note is that runs on node, making its installation more complicated; not a deal-breaker by any means, but not as convenient has Hugo.&lt;/p&gt;

&lt;h4&gt;
  
  
  Gatsby
&lt;/h4&gt;

&lt;p&gt;Gatsby is also becoming very popular lately. However, it generates react-based single-page apps rather than static sites. This is a valid option, I wanted the simplicity of pure HTML &amp;amp; CSS when creating my blog. Moreover, I've seen that &lt;a href="https://www.brycewray.com/posts/2019/07/why-staying-with-hugo/"&gt;this writer has had a poor experience&lt;/a&gt; using it, recommending Hugo instead.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hugo
&lt;/h4&gt;

&lt;p&gt;Lastly, we get to Hugo, the SSG I use for my own blog. It is written in Go meaning that using it means installing a single binary. This has advantages from a hosting point of view; you can just specify the Hugo version on my provider, and you can be sure that it will behave just the same way as it would locally.&lt;/p&gt;

&lt;p&gt;It is blazing fast. While this makes little difference when your blog contains just a few posts, it's good to know that the build time will remain reliably fast as your site grows.&lt;/p&gt;

&lt;p&gt;It is the most popular SSG &lt;a href="https://jamstack.org/generators/"&gt;(judging by GitHub stars)&lt;/a&gt;, meaning that it is well-used &amp;amp; documented, and well-supported by pretty much any static site hosting platform.&lt;/p&gt;

&lt;p&gt;Above all it has templates to get you started. Though you will likely want to create your own templates at some point (or at least modify them to your own taste) - it at least gives you something to get started out of the box.&lt;/p&gt;

&lt;p&gt;Its main downside is that it uses Go's templating language; it's not terrible by any means, but it's a little clunky compared to some, and if you're keen on another template language then you'd be out of luck here. If this is a problem for you then Eleventy might be worth a look.&lt;/p&gt;

</description>
      <category>blog</category>
      <category>blogging</category>
      <category>tutorial</category>
      <category>writing</category>
    </item>
    <item>
      <title>Emacs Lisp Cheat Sheet for Clojure Developers</title>
      <dc:creator>Robert Johnson</dc:creator>
      <pubDate>Fri, 04 Aug 2023 10:55:30 +0000</pubDate>
      <link>https://dev.to/robertmjohnson/emacs-lisp-cheat-sheet-for-clojure-developers-2h4p</link>
      <guid>https://dev.to/robertmjohnson/emacs-lisp-cheat-sheet-for-clojure-developers-2h4p</guid>
      <description>&lt;p&gt;If you're a Clojure dev and you're looking to learn Elisp, you have a great head start since you know a lisp dialect already. Here's a concise guide which focuses on the main differences between the two languages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Numeric types&lt;/li&gt;
&lt;li&gt;Character types&lt;/li&gt;
&lt;li&gt;Conditional logic&lt;/li&gt;
&lt;li&gt;Equality&lt;/li&gt;
&lt;li&gt;Functions&lt;/li&gt;
&lt;li&gt;Let forms&lt;/li&gt;
&lt;li&gt;Comments&lt;/li&gt;
&lt;li&gt;Namespaces and access modifiers&lt;/li&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;li&gt;Symbols, values &amp;amp; lambdas&lt;/li&gt;
&lt;li&gt;Threading macros &amp;amp; higher-order functions&lt;/li&gt;
&lt;li&gt;
False friends

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;concat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Aliases

&lt;ul&gt;
&lt;li&gt;Suggested aliases&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Going further&lt;/li&gt;
&lt;li&gt;Acknowledgements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Numeric types
&lt;/h3&gt;

&lt;p&gt;Elisp has integers and floats. These can be mixed in arithmetic functions how you'd usually expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 2&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 2.0&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn't have Clojure's ratio type though, so integer division works similarly to most other languages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integers can be expressed in multiple ways; the printed representation of REPL output looks odd at first, but it's simply showing you the alternate representations of the integer (I've omitted this elsewhere for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 16 (#o20, #x10, ?\C-p)&lt;/span&gt;
&lt;span class="c1"&gt;;; Shows decimal, octal, hex and character value&lt;/span&gt;
&lt;span class="c1"&gt;;; (see 'character types' section below)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Floats can be expressed in scientific notation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="mf"&gt;1e2&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 100.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integers can be arbitrarily large;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;10000000000000000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;; =&amp;gt; 10000000000000000001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Character types
&lt;/h3&gt;

&lt;p&gt;Elisp has strings and characters. Strings are essentially arrays of characters. They are represented and escaped in much the same way as Clojure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="s"&gt;"This is a simple string"&lt;/span&gt;
&lt;span class="s"&gt;"A newline character: \n"&lt;/span&gt;

&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;Escaped&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt; &lt;span class="nv"&gt;breaks&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;are&lt;/span&gt; &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="s"&gt;"
;; =&amp;gt; "&lt;/span&gt;&lt;span class="nv"&gt;Escaped&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt; &lt;span class="nv"&gt;breaks&lt;/span&gt; &lt;span class="nv"&gt;are&lt;/span&gt; &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Character literals a displayed using a &lt;code&gt;?&lt;/code&gt; prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;?f&lt;/span&gt; &lt;span class="nv"&gt;?o&lt;/span&gt; &lt;span class="nv"&gt;?o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; "foo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Characters are in fact simply integers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="nv"&gt;?A&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 65 (#o101, #x41, ?A)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="mi"&gt;111&lt;/span&gt; &lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; "foo"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;?f&lt;/span&gt; &lt;span class="nv"&gt;?o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 213 (#o325, #xd5)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Though not all integers are valid characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="mi"&gt;12356789&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can format strings much the same as in Clojure using the &lt;code&gt;format&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"I have %s bananas"&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "I have 3 bananas"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also display messages (also with optional formatting elements) in the minibuffer using the &lt;code&gt;message&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"Hello, world!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"Show a message to %s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such messages are also sent to the &lt;code&gt;*Messages*&lt;/code&gt; buffer, making &lt;code&gt;message&lt;/code&gt; helpful for debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional logic
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;nil&lt;/code&gt; and the empty list &lt;code&gt;'()&lt;/code&gt; are falsy; everything else is truthy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="s"&gt;"truthy"&lt;/span&gt; &lt;span class="s"&gt;"falsey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "falsey"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="s"&gt;"truthy"&lt;/span&gt; &lt;span class="s"&gt;"falsey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "falsey"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="s"&gt;"truthy"&lt;/span&gt; &lt;span class="s"&gt;"falsey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "truthy"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="s"&gt;"truthy"&lt;/span&gt; &lt;span class="s"&gt;"falsey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "truthy"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="s"&gt;"truthy"&lt;/span&gt; &lt;span class="s"&gt;"falsey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "truthy"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="s"&gt;"truthy"&lt;/span&gt; &lt;span class="s"&gt;"falsey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "truthy"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elisp doesn't have a separate Boolean type with &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; values; instead, the symbol &lt;code&gt;t&lt;/code&gt; is used to represent true and &lt;code&gt;nil&lt;/code&gt; is used to represent false. Predicate functions conventionally have a &lt;code&gt;p&lt;/code&gt; suffix (rather than &lt;code&gt;?&lt;/code&gt; as in Clojure):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;integerp&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;integerp&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Elisp will accept symbol names containing &lt;code&gt;?&lt;/code&gt;, so you can name your own predicates in the Clojure style if you want.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Equality
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;=&lt;/code&gt; is specifically for checking comparisons on number types (integers &amp;amp; floats):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; nil&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;string=&lt;/code&gt; is for comparing strings and characters. It is case sensitive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string=&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string=&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="ss"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string=&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="s"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; nil&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string=&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; nil&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;equal&lt;/code&gt; does value-based comparisons. It works for numbers &amp;amp; string types too, but it doesn't compare across ints &amp;amp; floats and strings &amp;amp; symbols like &lt;code&gt;=&lt;/code&gt; &amp;amp; &lt;code&gt;string=&lt;/code&gt; do. It too is case sensitive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;equal&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;equal&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; t&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;equal&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; nil&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;equal&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;eq&lt;/code&gt; Performs reference-based equality (like Clojure's &lt;code&gt;identical&lt;/code&gt; fn):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(See &lt;a href="https://www.emacswiki.org/emacs/ComparisonFunctions"&gt;Comparison Functions&lt;/a&gt; on the Emacs Wiki for further info.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Functions
&lt;/h3&gt;

&lt;p&gt;Functions are defined using &lt;code&gt;defun&lt;/code&gt;. The arguments are enclosed in a parens (rather than in brackets):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docstrings are a bit different in Elisp though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they go &lt;strong&gt;after&lt;/strong&gt; the argument list&lt;/li&gt;
&lt;li&gt;line breaks are preserved, but so is any indentation-related whitespace;
therefore the convention is to simply remove indentation for docstrings for all
but the first line.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"This is a docstring, continuing
onto the next line"&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variadic functions (AKA functions with "rest args") can be created using&lt;br&gt;
&lt;code&gt;&amp;amp;rest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;bar&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;rest&lt;/span&gt; &lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"First arg: %s; rest: %s"&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "First arg: 1; rest: (2 3)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can't create true multi-arity functions in Elisp, but you can get close enough by using optional arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;person1&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;optional&lt;/span&gt; &lt;span class="nv"&gt;person2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;person2&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"Hello, %s and %s!"&lt;/span&gt;
               &lt;span class="nv"&gt;person1&lt;/span&gt; &lt;span class="nv"&gt;person2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"Hello, %s!"&lt;/span&gt; &lt;span class="nv"&gt;person1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;greet&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "Hello, Alice!"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;greet&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt; &lt;span class="s"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "Hello, Alice and Bob!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Let forms
&lt;/h3&gt;

&lt;p&gt;For let bindings, use &lt;code&gt;let*&lt;/code&gt;. The bindings are placed in an (unquoted) list, and each binding pair has to be placed in its own list too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;let&lt;/code&gt; also exists, but it doesn't let you use earlier bindings within later ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Line comments still work just as you expect - e.g. &lt;code&gt;;; this is a comment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;There isn't a &lt;a href="https://practical.li/clojure/reference/clojure-syntax/comments/#comment-forms-with-the-comment-reader-macro"&gt;comment reader macro&lt;/a&gt; ( &lt;code&gt;#_&lt;/code&gt; )&lt;/li&gt;
&lt;li&gt;There isn't a built-in &lt;a href="https://practical.li/clojure/reference/clojure-syntax/comments/#comment-forms-with-the-comment-reader-macro"&gt;"Rich comment" block
macro&lt;/a&gt; -
BUT thankfully you can create one with a macro:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defmacro&lt;/span&gt; &lt;span class="nv"&gt;comment&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;&amp;amp;rest&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;comment&lt;/span&gt;
 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blow-up-the-world&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Namespaces and access modifiers
&lt;/h3&gt;

&lt;p&gt;Emacs doesn't have namespaces, and so doesn't have access modifiers either.&lt;/p&gt;

&lt;p&gt;By convention, packages prefix all their functions &amp;amp; variables with a prefix, &lt;code&gt;packagename-&lt;/code&gt;; e.g. all functions in the &lt;code&gt;org&lt;/code&gt; package are prefixed with &lt;code&gt;org-&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You do still have to &lt;code&gt;require&lt;/code&gt; packages in order to ensure they are loaded before using them. E.g. &lt;code&gt;(require 'org)&lt;/code&gt; would ensure that the code for the &lt;code&gt;org&lt;/code&gt; package is loaded. Depending on your emacs config / distribution, many packages may already be required for you.&lt;/p&gt;

&lt;p&gt;Functions intended as private are by convention signified with an extra &lt;code&gt;-&lt;/code&gt; in the prefix; e.g. &lt;code&gt;org--get-local-tags&lt;/code&gt; is a private function in the &lt;code&gt;org&lt;/code&gt; package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;

&lt;p&gt;Variables can be created &amp;amp; updated using &lt;code&gt;setq&lt;/code&gt; (short for "set quoted"):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;my-variable&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;my-variable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;defvar&lt;/code&gt; will create a variable, but won't update it if it's already been set; you can basically think of it as Clojure's &lt;code&gt;defonce&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defvar&lt;/span&gt; &lt;span class="nv"&gt;cheese&lt;/span&gt; &lt;span class="s"&gt;"brie"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"cheese = %s"&lt;/span&gt; &lt;span class="nv"&gt;cheese&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "cheese = brie"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defvar&lt;/span&gt; &lt;span class="nv"&gt;cheese&lt;/span&gt; &lt;span class="s"&gt;"stilton"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"cheese = %s"&lt;/span&gt; &lt;span class="nv"&gt;cheese&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "cheese = brie"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;cheese&lt;/span&gt; &lt;span class="s"&gt;"stilton"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"cheese = %s"&lt;/span&gt; &lt;span class="nv"&gt;cheese&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "cheese = stilton"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Symbols, values &amp;amp; lambdas
&lt;/h3&gt;

&lt;p&gt;One of the more confusing parts of Elisp is that its symbols point to multiple storage locations; &lt;strong&gt;a single symbol can refer to both a function and a variable&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;baz&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;baz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"baz as value: %s"&lt;/span&gt; &lt;span class="nv"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "baz as value: 123"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;baz&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that most of the time when passing around functions you have to quote them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; Not quoting + here - will error&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt; &lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; error: (void-variable +)&lt;/span&gt;

&lt;span class="c1"&gt;;; Quoting + here - OK&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt; &lt;span class="ss"&gt;'+&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create anonymous functions using &lt;code&gt;lambda&lt;/code&gt; - but because lambdas are stored in variables rather than &lt;code&gt;defun&lt;/code&gt;s, you can't call them directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;times-2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;times-2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; error: (void-function times-2)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, you need to use &lt;code&gt;funcall&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;inc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dec&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"result for inc: %s"&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;funcall&lt;/span&gt; &lt;span class="nv"&gt;inc&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;"result for dec: %s"&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;funcall&lt;/span&gt; &lt;span class="nv"&gt;dec&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;;; result for inc: 11&lt;/span&gt;
&lt;span class="c1"&gt;;; result for dec: 9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Threading macros &amp;amp; higher-order functions
&lt;/h3&gt;

&lt;p&gt;A lot of Clojure-style goodness can be provided by using the excellent &lt;a href="https://github.com/magnars/dash.el"&gt;dash package&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; (if not installed already)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;package-install&lt;/span&gt; &lt;span class="ss"&gt;'dash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="ss"&gt;'dash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; Threading macros:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 22&lt;/span&gt;

&lt;span class="c1"&gt;;; Higher order functions:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;inc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;-map&lt;/span&gt; &lt;span class="nv"&gt;inc&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; (2 3 4)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;evenp&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;-filter&lt;/span&gt; &lt;span class="nb"&gt;evenp&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that Emacs often has its own built-in versions for these - e.g. &lt;code&gt;thread-first&lt;/code&gt; &amp;amp; &lt;code&gt;thread-last&lt;/code&gt; for threading, &lt;code&gt;mapcar&lt;/code&gt; as an equivalent to Clojure's &lt;code&gt;map&lt;/code&gt; - but as a Clojurist you'll probably find what you're looking for quicker by using &lt;code&gt;dash&lt;/code&gt;'s functions instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  False friends
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;concat&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Elisp's &lt;code&gt;concat&lt;/code&gt; not only concatenates but also converts everything received into a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt; &lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="s"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "foobar"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="mi"&gt;66&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt; &lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt; "ABCD"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;string&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Elisp's &lt;code&gt;string&lt;/code&gt; function - unlike Clojure's &lt;code&gt;str&lt;/code&gt; - only works on characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;?f&lt;/span&gt; &lt;span class="nv"&gt;?o&lt;/span&gt; &lt;span class="nv"&gt;?o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; "foo"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="s"&gt;"f"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can operate on integers, but it converts them to the character corresponding to its charcode (assuming it has one):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; "B"&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt; &lt;span class="mi"&gt;123456789&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Aliases
&lt;/h3&gt;

&lt;p&gt;You can create aliases using &lt;code&gt;defalias&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;defalias&lt;/span&gt; &lt;span class="ss"&gt;'bar&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 4&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NB we linked to the function cell of foo using &lt;code&gt;#'&lt;/code&gt; above, so we can redef &lt;code&gt;foo&lt;/code&gt;, and &lt;code&gt;bar&lt;/code&gt; will pick up the change too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 2&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; =&amp;gt; 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Suggested aliases
&lt;/h4&gt;

&lt;p&gt;Here are some Clojure-style aliases for their Emacs builtin equivalents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;defalias&lt;/span&gt; &lt;span class="ss"&gt;'do&lt;/span&gt; &lt;span class="ss"&gt;'progn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;defalias&lt;/span&gt; &lt;span class="ss"&gt;'when-not&lt;/span&gt; &lt;span class="ss"&gt;'unless&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Going further
&lt;/h3&gt;

&lt;p&gt;If you'd like to put some Elisp into practice, you could also check out &lt;a href="https://robjohnson.dev/posts/everyday-editor-extensions-in-emacs/"&gt;my intro to extending Emacs using Elisp&lt;/a&gt;. The &lt;a href="https://github.com/p3r7/awesome-elisp"&gt;Awesome Elisp&lt;/a&gt; GitHub repo is also a great resource.&lt;/p&gt;

&lt;h3&gt;
  
  
  Acknowledgements
&lt;/h3&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.reddit.com/user/ambirdsall/"&gt;ambirdsall&lt;/a&gt;, &lt;a href="https://www.reddit.com/user/hvis/"&gt;hvis&lt;/a&gt; &amp;amp; Philip Kaludercic for suggestions. See &lt;a href="https://www.reddit.com/r/emacs/comments/155upms/emacs_lisp_cheat_sheet_for_clojure_developers/"&gt;Reddit&lt;/a&gt; for discussion.&lt;/p&gt;

</description>
      <category>emacs</category>
      <category>cheatsheet</category>
      <category>clojure</category>
    </item>
    <item>
      <title>Everyday editor extensions in Emacs</title>
      <dc:creator>Robert Johnson</dc:creator>
      <pubDate>Thu, 01 Jun 2023 23:37:53 +0000</pubDate>
      <link>https://dev.to/robertmjohnson/everyday-editor-extensions-in-emacs-mmf</link>
      <guid>https://dev.to/robertmjohnson/everyday-editor-extensions-in-emacs-mmf</guid>
      <description>&lt;p&gt;A good number of years ago, back when I had first started using Emacs, I came across &lt;a href="https://www.martinfowler.com/bliki/InternalReprogrammability.html"&gt;an article describing the joy of customising your development workflow using Emacs:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...I could now create empty lines above with a single keystroke. It took just a couple of minutes, I didn't have to install any plugins, or restart the editor - this is normal everyday business for an emacs user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was curious about experiencing this for myself but it was only relatively recently that I took the plunge into Emacs scripting; I was more than happy with the fine dev experience that &lt;a href="https://www.spacemacs.org/doc/DOCUMENTATION"&gt;Spacemacs&lt;/a&gt; had given me for Emacs, and I had always assumed that it would be too hard and boring to get to grips with Elisp. But I don't know why I held off for so long - you can get a lot of bang for your back with just a little Elisp, and what's more it's pretty fun too!&lt;/p&gt;

&lt;p&gt;In this post I want to do a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encourage any Emacs users that haven't used Elisp yet to give it a try&lt;/li&gt;
&lt;li&gt;Highlight some key useful Elisp functions&lt;/li&gt;
&lt;li&gt;Provide some examples &amp;amp; inspiration for creating your own extensions (whether you're an Elisp newbie or not)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm not going to explain Elisp completely from scratch, since this post would be far too long otherwise; take a look at &lt;a href="https://learnxinyminutes.com/docs/elisp/"&gt;the 'Learn X in Y minutes' page for Elisp&lt;/a&gt; if you need a primer (or if you know Clojure, &lt;a href="https://robjohnson.dev/posts/elisp-cheat-sheet-for-clojure-devs"&gt;my cheat sheet for Clojure devs&lt;/a&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TL;DR&lt;/li&gt;
&lt;li&gt;Web bookmarks&lt;/li&gt;
&lt;li&gt;Parameterising by prompt&lt;/li&gt;
&lt;li&gt;Parameterising by editor contents&lt;/li&gt;
&lt;li&gt;Fetching web data into your editor&lt;/li&gt;
&lt;li&gt;Running shell scripts&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;There's loads of &lt;strong&gt;easy, everyday&lt;/strong&gt; extensions that you can write; they're quick to write, and what's more these are the sorts of extensions that you can find yourself coming back to again and again. Every so often I write some more complex Elisp, but 90% of the time it's just some simple scripting that can really smooth over some bumps in my workflow.&lt;/p&gt;

&lt;p&gt;In my experience, such extensions involve some or all of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grabbing some text from the context of your editor&lt;/li&gt;
&lt;li&gt;Performing some simple text transformation&lt;/li&gt;
&lt;li&gt;Sending that text somewhere - e.g. your browser, a shell or a web API&lt;/li&gt;
&lt;li&gt;Displaying results in an Emacs buffer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shouldn't be too surprising; the main advantage of extending your editor over writing a shell script is that &lt;em&gt;you don't need to leave your editor&lt;/em&gt; - or at least, you don't need to copy &amp;amp; paste information to and from it by hand.&lt;/p&gt;

&lt;p&gt;The main bits of Elisp I'll show you for this are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to create &lt;code&gt;interactive&lt;/code&gt; functions&lt;/li&gt;
&lt;li&gt;Functions for capturing input, such as

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;read-string&lt;/code&gt; for prompting&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;thingatpt&lt;/code&gt; package for grabbing data from your buffer&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;format&lt;/code&gt; function for formatting strings&lt;/li&gt;
&lt;li&gt;Functions for sending formatted strings around, such as

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;browse-url&lt;/code&gt; for opening a link in your default web browser&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/alphapapa/plz.el"&gt;the &lt;code&gt;plz&lt;/code&gt; package&lt;/a&gt; for simple web requests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shell-command-to-string&lt;/code&gt; for running (you guessed it!) shell commands&lt;/li&gt;
&lt;li&gt;Some basic buffer-handling functions: &lt;code&gt;generate-new-buffer&lt;/code&gt;, &lt;code&gt;set-buffer&lt;/code&gt;, &lt;code&gt;insert&lt;/code&gt; &amp;amp; &lt;code&gt;switch-to-buffer-other-window&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Web bookmarks
&lt;/h2&gt;

&lt;p&gt;One of the simplest things we can do is to create interactive functions that serve as web bookmarks. So simple, the word "extension" seems a bit too fancy to describe these - and yet I find them so useful to make. The advantage of adding them to your Emacs setup is that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you get to take advantage of fuzzy-searching your bookmarks (using Helm or similar)&lt;/li&gt;
&lt;li&gt;you don't even have to leave your editor to search for them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is as simple as adding the following to your &lt;code&gt;init.el&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;open-github-spacemacs&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;browse-url&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/syl20bnr/spacemacs"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To eval the function (i.e make it available to run), simply hover your cursor somewhere within it then &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/M_002dx.html"&gt;execute&lt;/a&gt; &lt;code&gt;M-x eval-defun&lt;/code&gt; (i.e. hold the &lt;code&gt;alt&lt;/code&gt; &amp;amp; &lt;code&gt;x&lt;/code&gt; keys together, release, then type &lt;code&gt;eval-defun&lt;/code&gt;, then press &lt;code&gt;enter&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;Once that's done, we can execute &lt;code&gt;M-x open-github-spacemacs&lt;/code&gt; to take us to the Spacemacs GitHub page. But then, using Helm, you don't even need to type all that - something like &lt;code&gt;M-x op git sp RET&lt;/code&gt; would be enough.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;interactive&lt;/code&gt; function requires a little explanation - basically, if your function contains a call to &lt;code&gt;interactive&lt;/code&gt;, then emacs will make it available to be ran interactively (i.e. via &lt;code&gt;M-x&lt;/code&gt;) as opposed to only runnable from other Elisp code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parameterising by prompt
&lt;/h2&gt;

&lt;p&gt;So, to start getting a little more interesting - let's start looking at parameterised bookmarks. For example, taking us to a GitHub issue with a particular ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;open-github-spacemacs-issue&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;browse-url&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/syl20bnr/spacemacs/issues/%s"&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;read-string&lt;/span&gt; &lt;span class="s"&gt;"Issue ID: "&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing &lt;code&gt;M-x open-github-spacemacs-issue&lt;/code&gt;, Emacs will prompt us to enter an issue ID - e.g. &lt;code&gt;1234&lt;/code&gt;; once we press enter, Emacs will open a new tab with that issue ID (assuming it exists).&lt;/p&gt;

&lt;p&gt;If you prefer more of a top-to-bottom approach, you can &lt;a href="https://emacs.stackexchange.com/questions/42449"&gt;make use of &lt;code&gt;let*&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;open-github-spacemacs-issue&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;issue&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;read-string&lt;/span&gt; &lt;span class="s"&gt;"Issue ID: "&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/syl20bnr/spacemacs/issues/%s"&lt;/span&gt;
                      &lt;span class="nv"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;browse-url&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parameterising by editor contents
&lt;/h2&gt;

&lt;p&gt;Getting yet more interesting (and useful): we can parameterise a bit like the above, but instead of being prompted &lt;strong&gt;we can make use of the text our cursor happens to be on&lt;/strong&gt;. We get this functionality from the super-helpful &lt;code&gt;thingatpt&lt;/code&gt; ("thing at point") package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="ss"&gt;'thingatpt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;open-github-spacemacs-issue-from-pt&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;browse-url&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/syl20bnr/spacemacs/issues/%s"&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;int-to-string&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;thing-at-point&lt;/span&gt; &lt;span class="ss"&gt;'number&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eval both forms, then enter &lt;code&gt;1234&lt;/code&gt; into a buffer somewhere, and then hover your cursor somewhere over it; execute &lt;code&gt;M-x open-github-spacemacs-issue-from-pt&lt;/code&gt;, then you'll get taken to the corresponding issue in GitHub.&lt;/p&gt;

&lt;p&gt;Now we're cooking on gas! This is where we can really start to take advantage of the fact that we're running commands &lt;em&gt;from our editor&lt;/em&gt;, rather than from within a shell script.&lt;/p&gt;

&lt;p&gt;Where this can be especially useful is when you have some sort of global ID that is used to correlate requests or transactions across multiple systems, especially when those systems have web APIs you can query for said ID. I often find myself opening a log file in Emacs, then simply by hovering over an UUID I can take myself to many different diagnostic systems that care about it.&lt;/p&gt;

&lt;p&gt;Speaking of UUIDs, you'll need to use &lt;code&gt;thing-at-point&lt;/code&gt; slightly differently in order to grab one from your buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;open-widget-page&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;browse-url&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"https://postman-echo.com/get?my-id=%s"&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;thing-at-point&lt;/span&gt; &lt;span class="ss"&gt;'uuid&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if the "thing at point" isn't a valid UUID, then &lt;code&gt;(thing-at-point 'uuid)&lt;/code&gt; will be &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's a good idea to combine both the prompting &amp;amp; "thing at point" techniques; that way if your cursor isn't pointing to a valid "thing", you can still enter it in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;open-widget-page&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;browse-url&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"https://postman-echo.com/get?my-id=%s"&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;thing-at-point&lt;/span&gt; &lt;span class="ss"&gt;'uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;read-string&lt;/span&gt; &lt;span class="s"&gt;"Enter ID: "&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As per the documentation for the thingatpt package, the types of "thing" you can specify include:&lt;br&gt;
&lt;code&gt;symbol&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;sexp&lt;/code&gt;, &lt;code&gt;defun&lt;/code&gt;, &lt;code&gt;filename&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;uuid&lt;/code&gt;, &lt;code&gt;word&lt;/code&gt;, &lt;code&gt;sentence&lt;/code&gt;, &lt;code&gt;whitespace&lt;/code&gt;, &lt;code&gt;line&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, and &lt;code&gt;page&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you happen to be working with UUIDs, then grabbing them in your editor in this way is particularly handy since it's a lot easier than selecting them using your mouse; and in most GUIs, double-clicking any part of the UUID generally doesn't select all of it.&lt;/p&gt;

&lt;p&gt;(For the rest of this guide I'll use &lt;code&gt;(thing-at-point 'word)&lt;/code&gt; since it's easier to experiment with.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Fetching web data into your editor
&lt;/h2&gt;

&lt;p&gt;So far so good, but all this switching to and from our browser window is starting to get a little old... Let's improve things by grabbing the data into an editor window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; only need to run this the first time&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;package-install&lt;/span&gt; &lt;span class="ss"&gt;'plz&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="ss"&gt;'plz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;show-widget-data&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;thing-at-point&lt;/span&gt; &lt;span class="ss"&gt;'word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;read-string&lt;/span&gt; &lt;span class="s"&gt;"Enter ID: "&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"https://postman-echo.com/get?my-id=%s"&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="c1"&gt;;; create the name we'll use for the buffer we'll create.&lt;/span&gt;
         &lt;span class="c1"&gt;;; The asterisks aren't required; they're merely there as&lt;/span&gt;
         &lt;span class="c1"&gt;;; convention to help indicate that it's a generated buffer&lt;/span&gt;
         &lt;span class="c1"&gt;;; not associated with a file&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;buffer-name&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"*widget-%s*"&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="c1"&gt;;; create a new buffer with the above name&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;generate-new-buffer&lt;/span&gt; &lt;span class="nv"&gt;buffer-name&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="c1"&gt;;; Tell emacs to set the current buffer - i.e. the&lt;/span&gt;
    &lt;span class="c1"&gt;;; buffer that buffer-related operations will work on -&lt;/span&gt;
    &lt;span class="c1"&gt;;; to the buffer we've just created&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;set-buffer&lt;/span&gt; &lt;span class="nv"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;;; Download the data from the URL and write it into the&lt;/span&gt;
    &lt;span class="c1"&gt;;; buffer. We're doing this synchronously; async&lt;/span&gt;
    &lt;span class="c1"&gt;;; callbacks are possible, but this simple approach is&lt;/span&gt;
    &lt;span class="c1"&gt;;; more than enough most of the time&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;insert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;plz&lt;/span&gt; &lt;span class="ss"&gt;'get&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="ss"&gt;:as&lt;/span&gt; &lt;span class="ss"&gt;'string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;;; An optional step - we've downloading JSON in this example,&lt;/span&gt;
    &lt;span class="c1"&gt;;; so we may as well automatically enable the JSON mode&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;json-mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;;; Display the buffer in another window&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;switch-to-buffer-other-window&lt;/span&gt; &lt;span class="nv"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to before, we can run this interactively via &lt;code&gt;M-x show-widget-data&lt;/code&gt; when hovering over a suitable item of data with our cursor; this time though the data will be opened in a new buffer.&lt;/p&gt;

&lt;p&gt;The main functions of note in the above are &lt;code&gt;plz&lt;/code&gt; for fetching the data, and the various buffer-handling functions: &lt;code&gt;generate-new-buffer&lt;/code&gt;, &lt;code&gt;set-buffer&lt;/code&gt;, &lt;code&gt;insert&lt;/code&gt; &amp;amp; &lt;code&gt;switch-to-buffer-other-window&lt;/code&gt;. Working with Emacs buffers in Elisp can feel a bit arcane at first, but thankfully it's fine once you get started. See the &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Buffers.html"&gt;elisp docs on buffers&lt;/a&gt; if you want to go deeper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running shell scripts
&lt;/h2&gt;

&lt;p&gt;We can still take advantage of the context of our editor when running shell commands; we can use parameterised input as we've been doing, run a shell command, then display the results in another buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;run-widget&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;thing-at-point&lt;/span&gt; &lt;span class="ss"&gt;'word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;read-string&lt;/span&gt; &lt;span class="s"&gt;"Enter ID: "&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;buffer-name&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"*widget-%s*"&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;generate-new-buffer&lt;/span&gt; &lt;span class="nv"&gt;buffer-name&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;set-buffer&lt;/span&gt; &lt;span class="nv"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;insert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;shell-command-to-string&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="s"&gt;"cowsay %s"&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;switch-to-buffer-other-window&lt;/span&gt; &lt;span class="nv"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful if you're new to elisp; you can create a script in your favourite language to do the heavy lifting, and just use the simple glue code above to make use of it seamlessly within emacs. For example, for downloading data it can be easier to handle things like authentication tokens and connection pooling in a more modern scripting language. (As a Clojure dev, I love &lt;a href="https://babashka.org/"&gt;Babashka&lt;/a&gt; for this!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Hopefully this has shown you how to get started in the wonderful world of Emacs scripting, and given you plenty of inspiration for how you can make your day-to-day development worklow better with just a little bit of Elisp. And what's more, you'll have experienced just how quickly you can try out these sorts of changes - all without even restarting Emacs!&lt;/p&gt;

&lt;p&gt;If you want to go deeper into Elisp, I highly recommend checking out &lt;a href="https://github.com/p3r7/awesome-Elisp"&gt;awesome Elisp&lt;/a&gt;, which lists many helpful tutorials and libraries (which is where I came across the &lt;code&gt;plz&lt;/code&gt; library I used earlier).&lt;/p&gt;

&lt;p&gt;As always, please feel free to ask any questions in the comments.&lt;/p&gt;

</description>
      <category>emacs</category>
      <category>tutorial</category>
      <category>spacemacs</category>
      <category>editor</category>
    </item>
    <item>
      <title>Thin (ish) Clojure jars for better Docker containers</title>
      <dc:creator>Robert Johnson</dc:creator>
      <pubDate>Mon, 22 May 2023 21:53:32 +0000</pubDate>
      <link>https://dev.to/robertmjohnson/thin-ish-clojure-jars-for-better-docker-containers-46bn</link>
      <guid>https://dev.to/robertmjohnson/thin-ish-clojure-jars-for-better-docker-containers-46bn</guid>
      <description>&lt;p&gt;Recently &lt;a href="https://robjohnson.dev/posts/thin-jars/"&gt;I've been creating "thin" jars for my Java applications&lt;/a&gt;, in order to avoid the waste caused by putting uberjars in containers. Unlike an uberjar, a "thin" jar contains only our application code, with all the dependencies split out and referenced in the uberjar's manifest file. This lets us store the app code in a separate &lt;a href="https://docs.docker.com/build/guide/layers/"&gt;docker layer&lt;/a&gt;, allowing us to save all the storage &amp;amp; network IO that we'd otherwise waste re-transferring all those app dependencies.&lt;/p&gt;

&lt;p&gt;Despite this, it's still common practice to deploy Clojure apps as uberjars, so I thought I'd try implementing the same dependency/app-jar separation for my Clojure apps. Wondering why such an option isn't already a built-in function for &lt;a href="https://clojure.org/guides/tools_build"&gt;tools.build&lt;/a&gt;, I thought it would be as simple as recreating Maven's copy-dependencies step in Clojure.&lt;/p&gt;

&lt;p&gt;The bad news: we can't get &lt;em&gt;all&lt;/em&gt; the way there; the good news: we can get pretty close - at the very least, a concrete improvement over containerized uberjars.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Given that both Java &amp;amp; Clojure apps ultimately get complied down to JVM bytecode, in theory we should be able to apply to Clojure apps the same tricks we can use for Java. There's an unexpected catch though: Clojure's complier is non-deterministic in its output, i.e. &lt;a href="https://ask.clojure.org/index.php/12925"&gt;it can output different class files across different runs for the same input&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Any differences in this output don't affect the behaviour of the compiled program, but just the difference of even a single byte here invalidates any layer caching. Therefore we could create a "thin" jar and split it into a different layer of our docker image, but we wouldn't actually benefit because each new build would require transfer &amp;amp; storage of the same amount of data as an uberjar, losing the very benefits we are hoping to achieve.&lt;/p&gt;

&lt;h2&gt;
  
  
  A (partial) Solution
&lt;/h2&gt;

&lt;p&gt;All is not lost, however; we can still split out any jars containing already-compiled bytecode, creating a slimmer uberjar containing only bytecode generated from our Clojure application code and any Clojure libraries it uses.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;build.clj&lt;/code&gt; file below, we&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work out which libraries contain already-compiled code (as opposed to only &lt;code&gt;.clj&lt;/code&gt; source files)&lt;/li&gt;
&lt;li&gt;Copy the precompiled library jars to a separate &lt;code&gt;lib&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Compile any remaining Clojure dependencies &amp;amp; our app code, and put just those into an uberjar&lt;/li&gt;
&lt;li&gt;Reference the jars in the &lt;code&gt;lib&lt;/code&gt; directory in the uberjar's manifest file
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clojure.tools.build.api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clojure.string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clojure.java.io&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java.util.zip.ZipFile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contains-class?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;absolute-path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;with-open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;zip-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ZipFile.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;absolute-path&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;iterator-seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zip-file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.isDirectory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.getName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str/ends-with?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;".class"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy-dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_basis&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mapcat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;comp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;doseq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/lib/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.getName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;io/file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)))]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/copy-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;})))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;split-basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;grouped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;group-by&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;_sym&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_lib&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;contains-class?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:libs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grouped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:libs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grouped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)))]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jar&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;precompiled-basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uncompiled-basis&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;split-basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/create-basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Copying source..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/copy-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:src-dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:target-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/classes"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Copying precompiled dependency jars..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;copy-dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;precompiled-basis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Compiling "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/compile-clj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uncompiled-basis&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:class-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/classes"&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:src-dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:ns-compile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;my-app.core&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Building jar"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/uber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uncompiled-basis&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:class-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/classes"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;my-app.core&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Class-Path"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.listFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;io/file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/lib"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                                           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"lib/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.getName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
                                           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str/join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:uber-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/my-app.jar"&lt;/span&gt;&lt;span class="p"&gt;}))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us a smaller app jar, alongside its jars that contain compiled bytecode. (NB this includes Clojure core!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── my-app.jar
└── lib
    ├── asm-9.2.jar
    ├── avro-1.11.0.jar
    ├── checker-qual-3.8.0.jar
    ├── clojure-1.11.1.jar
    ├── commons-compress-1.21.jar
    ...
    └── zstd-jni-1.5.2-1.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, we just need to make sure our Dockerfile copies over the libs in a separate layer to the uberjar, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=app-build /app/target/lib /app/lib&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=app-build /app/target/my-app.jar /app/my-app.jar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, each time our CI server builds a new Docker image we go from something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5f62416f87c9: Pushing [==================================================&amp;gt;]  31.95MB
1668bc9c2910: Layer already exists
e6857d4762fd: Layer already exists
3af14c9a24c9: Layer already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acd5c6ddc038: Pushing [==================================================&amp;gt;]  4.705MB
8bf418842124: Layer already exists
1668bc9c2910: Layer already exists
e6857d4762fd: Layer already exists
3af14c9a24c9: Layer already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;i.e. each new build results in less network IO and image repo storage; the cost is an extra image layer, and a slightly more involved &lt;code&gt;build.clj&lt;/code&gt;. YMMV of course, dependending on your app's dependencies - but Clojure apps tend to depend upon a large stack of Java dependencies, and so savings like the above wouldn't be uncommon.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about deploying Clojure sources?
&lt;/h2&gt;

&lt;p&gt;Of course, an alternative to all this for which we could definitely achieve a truly thin application image layer would be to not bother with AOT compilation and deploy Clojure sources. The tradeoff here is between build &amp;amp; infrastructure costs against application startup time. This is a tradeoff potentially worth making - but if you're already deploying uberjars, then you're probably doing so because you want to avoid that compilation at application start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Compared to containerizing an uberjar, we get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster app upgrade times&lt;/li&gt;
&lt;li&gt;Reduced image registry storage and costs&lt;/li&gt;
&lt;li&gt;No compromise to app start startup time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While we sadly can't achieve the same image size efficiency gains as compared to a Java application, there are definitely still gains to be had - and they come at just the cost of some extra lines of code in our &lt;code&gt;build.clj&lt;/code&gt; file.&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>docker</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to deploy thin jars for better Docker caching</title>
      <dc:creator>Robert Johnson</dc:creator>
      <pubDate>Fri, 12 May 2023 22:30:09 +0000</pubDate>
      <link>https://dev.to/robertmjohnson/how-to-deploy-thin-jars-for-better-docker-caching-4i44</link>
      <guid>https://dev.to/robertmjohnson/how-to-deploy-thin-jars-for-better-docker-caching-4i44</guid>
      <description>&lt;p&gt;There's plenty of advice around on how to create small Docker containers, but what is often missed is the value of making your main application layer as small as possible. In this article I'm going to show you how to how to do this for a Java application, and why it matters.&lt;/p&gt;

&lt;p&gt;I'm assuming that you're already familiar with Docker layer caching; if not, then &lt;a href="https://docs.docker.com/build/cache/" rel="noopener noreferrer"&gt;the official docs&lt;/a&gt; will give you a good overview.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Instead of a single uberjar creation build step, split it into two steps:

&lt;ol&gt;
&lt;li&gt; Copy dependencies to a &lt;code&gt;lib&lt;/code&gt; folder (e.g. via Maven's &lt;code&gt;copy-dependencies&lt;/code&gt; plugin)&lt;/li&gt;
&lt;li&gt; Create a jar, ensuring that the &lt;code&gt;lib&lt;/code&gt; folder is added to its manifest&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;  The resulting docker image is much the same size - but Docker can now cache the dependencies, meaning that each subsequent build requires only a few extra kilobytes to store &amp;amp; transfer, rather than tens of MBs&lt;/li&gt;
&lt;li&gt;  As an added benefit to splitting out the libraries, it makes it easier to trim down our dependency jars, such as removing useless alternate native libraries from JNI jars&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The downsides of uberjars for Docker
&lt;/h2&gt;

&lt;p&gt;For a long time the standard way to deploy Java applications has been to deploy the humble uberjar, which contains both our compiled code and all our application's dependencies. Packaging Java apps in this made sense back when deploying an application meant transferring files to and from servers directly, but it's not so useful in this age of containerized apps.&lt;/p&gt;

&lt;p&gt;The reason for this is that for every single change to your application's source, all those dependencies bundled inside the uberjar end up inside the same docker layer as your app - and so &lt;strong&gt;each subsequent build effectively duplicates all of those third-party dependencies within your Docker repository&lt;/strong&gt;. This is especially wasteful when we consider that your application code is what is going to change the vast majority of the time.&lt;/p&gt;

&lt;p&gt;Suppose we have a nicely slimmed-down dockerfile, using an Alpine base image,&lt;br&gt;
and even &lt;a href="https://adoptium.net/blog/2021/10/jlink-to-produce-own-runtime/" rel="noopener noreferrer"&gt;creating a tiny "custiom" JRE using jlink&lt;/a&gt;. However, suppose we&lt;br&gt;
have a lot of dependencies - 77MB in this example.&lt;br&gt;
The key part of our &lt;code&gt;Dockerfile&lt;/code&gt; will look like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "/app/my-app.jar"]&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=app-build /app/target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar /app/my-app.jar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the docker layers created will look something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker history my-app
IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
548d9d7dc060   28 seconds ago       COPY /app/target/my-app-1.0-SNAPSHOT-jar-wit…   77.7MB    buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      About a minute ago   CMD ["java" "-jar" "/app/my-app.jar"]           0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      About a minute ago   WORKDIR /app                                    0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      About a minute ago   RUN /bin/sh -c apk add --no-cache libstdc++ …   2.55MB    buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      About a minute ago   COPY /javaruntime /opt/java/openjdk # buildk…   37.1MB    buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      About a minute ago   ENV PATH=/opt/java/openjdk/bin:/usr/local/sb…   0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      About a minute ago   ENV JAVA_HOME=/opt/java/openjdk                 0B        buildkit.dockerfile.v0
&amp;lt;missing&amp;gt;      3 months ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
&amp;lt;missing&amp;gt;      3 months ago         /bin/sh -c #(nop) ADD file:40887ab7c06977737…   7.05MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The total image size is 128mb, and after pushing the first version, this takes up 96MB in my container registry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjr0l30spf7si61gye0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjr0l30spf7si61gye0j.png" alt="container registry - 1 image tag, 96MB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On each subsequent build &amp;amp; push of the new image, &lt;strong&gt;we're pushing up a whole layer including all our dependencies all over again&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The push refers to repository [registry.digitalocean.com/my-repo/my-app]
f78ba99c16ec: Pushing  77.74MB/77.74MB
76d6a32df414: Layer already exists
a7b85e634de7: Layer already exists
0eea560269c8: Layer already exists
7cd52847ad77: Layer already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So after just 5 pushes, we've already used up 386MB (!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa98pmoxycqyv0megw20m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa98pmoxycqyv0megw20m.png" alt="Container registry - 5 tags, 386MB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting apart our dependencies from our application code
&lt;/h2&gt;

&lt;p&gt;Using Maven, we simply replace any uberjar creating step(s) with the following 2 steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; to copy the dependencies to &lt;code&gt;target/lib&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; create the jar containing our compiled code only - but set up to look for its dependencies in &lt;code&gt;./lib&lt;/code&gt; :
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- (other plugins...) --&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Copy runtime dependencies to lib folder --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-dependency-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;copy-dependencies&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;prepare-package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;copy-dependencies&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;${project.build.directory}/lib&lt;span class="nt"&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;includeScope&amp;gt;&lt;/span&gt;runtime&lt;span class="nt"&gt;&amp;lt;/includeScope&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Creates the jar and configures classpath to use lib folder --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-jar-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;archive&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;manifest&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;addClasspath&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/addClasspath&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;classpathPrefix&amp;gt;&lt;/span&gt;lib/&lt;span class="nt"&gt;&amp;lt;/classpathPrefix&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;mainClass&amp;gt;&lt;/span&gt;path.to.MainClass&lt;span class="nt"&gt;&amp;lt;/mainClass&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/archive&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All we need to do to our &lt;code&gt;Dockerfile&lt;/code&gt; is add an extra build step to copy the libs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "/app/my-app.jar"]&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=app-build /app/target/lib /app/lib&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=app-build /app/target/my-app-1.0-SNAPSHOT.jar /app/my-app.jar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;strong&gt;NB&lt;/strong&gt; It's important here that the lib directory copy happens &lt;strong&gt;before&lt;/strong&gt; the jar copy; if not, then application updates would invalidate the layer cache for the lib directory, thus losing the benefit!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of the split
&lt;/h2&gt;

&lt;p&gt;Upon pushing the first version of this build, there's not much difference to what we had before - the overall image size stored in the repository is almost the same. The benefit comes when we push subsequent new builds where we've changed only our source code; after 5 builds (where only our app source has changed), the total size in our image repo is only a few kb larger:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3z3w4yv213m71hgh1r2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3z3w4yv213m71hgh1r2r.png" alt="Container registry - split jar, 5 tags, 90MB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During each push, we only have to push a few more kb each time; &lt;strong&gt;now that the libs are in their own layer, which remains unchanged, the image repository is able to re-use the dependencies pushed in previous builds&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;b54d889308d2: Pushing   16.9kB
8a9ec9b0baaa: Layer already exists
76d6a32df414: Layer already exists
a7b85e634de7: Layer already exists
0eea560269c8: Layer already exists
7cd52847ad77: Layer already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now push hundreds of new image builds without getting anywhere near to the limit of the free tier of this image repository, whereas before we'd hit the limit after not even 10 builds!&lt;/p&gt;

&lt;h2&gt;
  
  
  How the thin jar works
&lt;/h2&gt;

&lt;p&gt;All we're doing with the above build configuration is creating a jar with just our compiled code in it, but telling it where to find the dependencies. If you're not familiar with the structure of a jar file, it's basically a zip file that contains&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  a load of compiled java classes (&lt;code&gt;.class&lt;/code&gt; files)&lt;/li&gt;
&lt;li&gt;  a "manifest" file, there to tell java things like where your main class is and where any external dependencies are, when you try to run it via &lt;code&gt;java -jar&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uberjars simplify things by putting &lt;em&gt;all&lt;/em&gt; the dependency compiled code into the jar along with your own app. But as described in &lt;a href="https://docs.oracle.com/javase/tutorial/deployment/jar/downman.html" rel="noopener noreferrer"&gt;the official docs for jar manifests&lt;/a&gt;, all we need to do to split out the dependencies is to specify them in a &lt;code&gt;Class-Path\&lt;/code&gt; entry in our manifest. With the split in place, our manifest file will look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 20
Class-Path: lib/kafka-clients-3.4.0.jar lib/zstd-jni-1.5.2-1.jar lib/lz4
-java-1.8.0.jar lib/slf4j-api-2.0.6.jar lib/slf4j-simple-2.0.6.jar
Main-Class: my.app.Main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that annoyingly we have to manually specify each jar file explicitly in the manifest; thankfully, the &lt;code&gt;maven-jar-plugin&lt;/code&gt; does this work for us (as should other build tools).&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus - Slimming down JNI dependencies
&lt;/h2&gt;

&lt;p&gt;An added bonus to splitting out our library jars is that it makes it easier to further trim down the contents of JNI (Java Native Interface) dependencies. JNI dependencies often contain native libraries for multiple platforms - more waste! With an extra command in our Dockerfile, we can remove these unwanted native libraries from within those jars to trim down the image size even further.&lt;/p&gt;

&lt;p&gt;The example application I'm working with is a Kafka Streams app, and so has a transitive dependency on rocksdbjni. This is a whopping 53MB! Within our Dockerfile we can remove the unwanted libraries from it using &lt;code&gt;zip --delete&lt;/code&gt; (recall that jar files are just zip files), like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;zip &lt;span class="nt"&gt;--delete&lt;/span&gt; ./target/lib/rocksdbjni-7.1.2.jar &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux32-musl.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux32.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux64.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux-ppc64le.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux-ppc64le-musl.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux-aarch64.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux-aarch64-musl.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux-s390x.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-linux-s390x-musl.so &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-win64.dll &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-osx-arm64.jnilib &lt;span class="se"&gt;\
&lt;/span&gt;    librocksdbjni-osx-x86_64.jnilib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This trimmed-down rocksdbjni jar is only 4MB - nice!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We get a lot of bang for our buck here: storage &amp;amp; network costs converted from megabytes to kilobytes with just a few tweaks to our app &amp;amp; container builds. This might sound rather academic, but as we've seen here it can make the difference between hitting your repository storage limit VS having headroom for hundreds of builds.&lt;/p&gt;

&lt;p&gt;Give it a try, and let me know what you think!&lt;/p&gt;

</description>
      <category>java</category>
      <category>docker</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Hi, I'm Robert Johnson</title>
      <dc:creator>Robert Johnson</dc:creator>
      <pubDate>Mon, 17 Apr 2017 09:16:06 +0000</pubDate>
      <link>https://dev.to/robertmjohnson/hi-im-robert-johnson</link>
      <guid>https://dev.to/robertmjohnson/hi-im-robert-johnson</guid>
      <description>&lt;p&gt;I have been coding for 6 years.&lt;/p&gt;

&lt;p&gt;You can find me on GitHub as &lt;a href="https://github.com/robert-m-johnson" rel="noopener noreferrer"&gt;robert-m-johnson&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I live in Poole, Dorset, UK working for Siemens.&lt;/p&gt;

&lt;p&gt;In my day job I use Java but in my spare time I like playing with Clojure.&lt;/p&gt;

&lt;p&gt;Nice to meet you.&lt;/p&gt;

</description>
      <category>introduction</category>
    </item>
  </channel>
</rss>
