<?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: jldec</title>
    <description>The latest articles on DEV Community by jldec (@jldec).</description>
    <link>https://dev.to/jldec</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%2F570769%2Ff03d21e1-3fec-4f08-bc7c-ea805f4004f4.jpeg</url>
      <title>DEV Community: jldec</title>
      <link>https://dev.to/jldec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jldec"/>
    <language>en</language>
    <item>
      <title>It's early days for Agents</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Mon, 13 Oct 2025 22:52:18 +0000</pubDate>
      <link>https://dev.to/jldec/its-early-days-for-agents-3bl2</link>
      <guid>https://dev.to/jldec/its-early-days-for-agents-3bl2</guid>
      <description>&lt;p&gt;&lt;a href="https://openai.com/index/introducing-openai-o1-preview/" rel="noopener noreferrer"&gt;Reasoning&lt;/a&gt; and &lt;a href="https://openai.com/index/function-calling-and-other-api-updates/" rel="noopener noreferrer"&gt;function calling&lt;/a&gt; both marked significant breakthroughs in the road to making agents more capable.&lt;/p&gt;

&lt;p&gt;Developer coding agents like &lt;a href="https://ampcode.com/home" rel="noopener noreferrer"&gt;Amp&lt;/a&gt; and &lt;a href="https://www.claude.com/product/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; now leverage those capabilities to generate real value, for which users are willing to pay. They are also pushing the boundaries of what is possible with agents, e.g. by delegating tasks to specialized subagents and carefully managing the context over extended periods.&lt;/p&gt;

&lt;p&gt;But coding agents are not general purpose. If we continue to rely on hand-coded agentic apps for specialized domains, that means that we haven't fully grokked the &lt;a href="https://web.archive.org/web/20251006184925/http://www.incompleteideas.net/IncIdeas/BitterLesson.html" rel="noopener noreferrer"&gt;bitter lesson&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To deliver value at scale, Agents need to learn on the job.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Agents should get smarter over time, constantly refining their knowledge based on what they learn from their interactions at inference-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two approaches
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;AI researchers tend to approach the problem from the perspective of &lt;a href="https://deeprlcourse.github.io/guests/richard_sutton/" rel="noopener noreferrer"&gt;reinforcement learning&lt;/a&gt; or other forms post-training. This makes sense coming from the "models-learn" perspective.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI engineers are getting better behavior out of LLMs with &lt;a href="https://x.com/karpathy/status/1937902205765607626" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt;. Capturing specialized knowledge, and including relevant details in the context used for inference is a natural way for prior conversations to shape the generated output, without additional training of the model.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A combination of both approaches will be required eventually, but in the short term there is lots of room for agent framework providers to innovate without pushing the problem back into the labs&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remember all conversations.&lt;/li&gt;
&lt;li&gt;Provide search interfaces to map new tasks onto existing knowledge.&lt;/li&gt;
&lt;li&gt;Refine existing knowledge by subjecting it to feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this is done well, it will also facilitate sharing and learning amongst teams of agents and humans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents FTW
&lt;/h2&gt;

&lt;p&gt;I am optimistic that even with these modest first steps, we can start shipping agentic systems that are capable of learning and improving over time.&lt;/p&gt;

&lt;p&gt;In the meantime, innovations in how users interact with agents (like &lt;a href="https://solve.it.com" rel="noopener noreferrer"&gt;solveit&lt;/a&gt;), and how agents interact with their environment (like &lt;a href="https://blog.cloudflare.com/code-mode/" rel="noopener noreferrer"&gt;codemode&lt;/a&gt;) are exciting evidence of how fertile this space is.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀🚀🚀
&lt;/h2&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Quick overview of Model Context Protocol (MCP)</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Sat, 15 Mar 2025 22:36:20 +0000</pubDate>
      <link>https://dev.to/jldec/quick-overview-of-model-context-protocol-mcp-123c</link>
      <guid>https://dev.to/jldec/quick-overview-of-model-context-protocol-mcp-123c</guid>
      <description>&lt;p&gt;MCP is a protocol for integrating AI applications with tools and external services. It was created by Anthropic as a way for &lt;a href="https://claude.ai/download" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; to access resources on the desktop, taking inspiration from &lt;a href="https://microsoft.github.io/language-server-protocol/" rel="noopener noreferrer"&gt;LSP&lt;/a&gt; for editor extensions.&lt;/p&gt;

&lt;p&gt;MCP servers can be coded do almost anything, including network calls to other services. This has led to MCP being talked about as a foundation for building AI agents, even though the original desktop-centric transport is far from the Web protocols you might expect.&lt;/p&gt;

&lt;p&gt;What follows is very a quick overview of MCP. For deeper dives, I suggest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=m2VqaNKstGc" rel="noopener noreferrer"&gt;How MCP was born&lt;/a&gt; interview with &lt;a href="https://x.com/dsp_" rel="noopener noreferrer"&gt;@dsp_&lt;/a&gt; and &lt;a href="https://x.com/jspahrsummers" rel="noopener noreferrer"&gt;@jspahrsummers&lt;/a&gt; - April 3 '25&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=kQmXtrmQ5Zg" rel="noopener noreferrer"&gt;Workshop&lt;/a&gt; from the AI engineer summit - Feb '25&lt;/li&gt;
&lt;li&gt;MCP &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;User guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP &lt;a href="https://spec.modelcontextprotocol.io/specification//2024-11-05/" rel="noopener noreferrer"&gt;Spec&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;TypeScript SDK&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example &lt;a href="https://github.com/modelcontextprotocol/servers" rel="noopener noreferrer"&gt;MCP servers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Swyx's &lt;a href="https://www.latent.space/p/why-mcp-won" rel="noopener noreferrer"&gt;spicy take&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcila5mq22wi2nr1bl0u0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcila5mq22wi2nr1bl0u0.png" alt="MCP clients and servers" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;To make a tool call, the &lt;a href="https://modelcontextprotocol.io/clients" rel="noopener noreferrer"&gt;MCP client&lt;/a&gt; exchanges JSON-RPC messages with the &lt;a href="https://modelcontextprotocol.io/examples" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A desktop client will spawn servers as subprocesses so that it can communicate with them over &lt;a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio" rel="noopener noreferrer"&gt;stdio&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Besides &lt;a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/" rel="noopener noreferrer"&gt;tool calls&lt;/a&gt;, the protocol also supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server-to-client &lt;a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/messages/#notifications" rel="noopener noreferrer"&gt;notifications&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;URL-based &lt;a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/" rel="noopener noreferrer"&gt;resources&lt;/a&gt; with subscriptions for change notifications&lt;/li&gt;
&lt;li&gt;Server-provided &lt;a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/" rel="noopener noreferrer"&gt;Prompt templates&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nested LLM calls or &lt;a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/client/sampling/" rel="noopener noreferrer"&gt;sampling&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are &lt;a href="https://modelcontextprotocol.io/docs/tools/debugging" rel="noopener noreferrer"&gt;debugging&lt;/a&gt; tools and &lt;a href="https://modelcontextprotocol.io/sdk" rel="noopener noreferrer"&gt;SDKs&lt;/a&gt; for Python, Typescript, Java, and Kotlin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why are people excited?
&lt;/h3&gt;

&lt;p&gt;Talking to your own tools feels like magic.&lt;/p&gt;

&lt;p&gt;Using MCP, AI appplications (clients) can connect to tools (servers) without custom integration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5rb89656ur39fpa1hmjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5rb89656ur39fpa1hmjd.png" alt="Screenshot of MCP connecting to the local filesystem" width="800" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=vGajZpl_9yA" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is another example from Cloudflare.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next?
&lt;/h3&gt;

&lt;p&gt;The buzz is real, but it's early days and AI agent platforms are just getting started.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I'm optimistic for &lt;a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse" rel="noopener noreferrer"&gt;HTTP&lt;/a&gt; to supplant stdio, especially with the &lt;a href="https://youtu.be/kQmXtrmQ5Zg?t=4409" rel="noopener noreferrer"&gt;recent announcement&lt;/a&gt; of remote servers with auth. This is particularly important for scaling agents on the network and supporting multiple clients per server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;a href="https://youtu.be/kQmXtrmQ5Zg?t=4920" rel="noopener noreferrer"&gt;registry&lt;/a&gt; should accelerate the network effects of MCP, making servers and their capabilities more discoverable e.g. by other agents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://youtu.be/kQmXtrmQ5Zg?t=5958" rel="noopener noreferrer"&gt;.well-known/mcp.json&lt;/a&gt; provides a way for agents to find AI interfaces on websites without the need for a central registry.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://youtu.be/kQmXtrmQ5Zg?t=6095" rel="noopener noreferrer"&gt;Stateless requests, streaming, and namespacing&lt;/a&gt; - all good things adopted from Web protocols.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀
&lt;/h2&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>automation</category>
    </item>
    <item>
      <title>3 essential elements for Web publishing</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Mon, 13 Jan 2025 22:40:58 +0000</pubDate>
      <link>https://dev.to/jldec/3-essential-elements-for-web-publishing-30n1</link>
      <guid>https://dev.to/jldec/3-essential-elements-for-web-publishing-30n1</guid>
      <description>&lt;p&gt;Content &lt;strong&gt;editing&lt;/strong&gt; tools with reusable &lt;strong&gt;themes&lt;/strong&gt; enable creators to publish professional-looking websites without the need for custom development.&lt;/p&gt;

&lt;p&gt;This is the long tail of web publishing dominated by &lt;a href="https://wordpress.com/theme/twentytwentyfour" rel="noopener noreferrer"&gt;WordPress&lt;/a&gt; and platforms like &lt;a href="https://wordpress.com/theme/twentytwentyfour" rel="noopener noreferrer"&gt;Wix&lt;/a&gt; and &lt;a href="https://themes.shopify.com/" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Web frameworks like &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; start with developers first. Even when they are integrated with a &lt;a href="https://www.sanity.io/" rel="noopener noreferrer"&gt;CMS&lt;/a&gt;, the dependency on developers remains.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An ideal web publishing architecture incorporates 3 key elements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;theme&lt;/strong&gt; that ensures content looks great on all devices.&lt;/li&gt;
&lt;li&gt;A usable content &lt;strong&gt;editor&lt;/strong&gt; with built-in theme awareness.&lt;/li&gt;
&lt;li&gt;Fast, reliable web &lt;strong&gt;hosting&lt;/strong&gt; for the rendered content.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Publishers need efficient editing tools. If you're building tools for web publishing, don't forget the editor experience.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>contentwriting</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Astro v5 Blog starter</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Sun, 29 Dec 2024 21:16:22 +0000</pubDate>
      <link>https://dev.to/jldec/astro-v5-blog-starter-4n01</link>
      <guid>https://dev.to/jldec/astro-v5-blog-starter-4n01</guid>
      <description>&lt;h1&gt;
  
  
  Astro v5 blog starter
&lt;/h1&gt;

&lt;p&gt;I recently moved a friend's blog to &lt;a href="https://astro.build/blog/astro-5/" rel="noopener noreferrer"&gt;Astro v5&lt;/a&gt;. The motivation behind selecting Astro was its first-class support for markdown content.&lt;/p&gt;

&lt;p&gt;With just a minimal amount of boilerplate, Astro will validate markdown frontmatter, generate static pages for each post, and optimize all the images in your posts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://astro-v5-blog-starter.jldec.me/blog/first-post" rel="noopener noreferrer"&gt;https://astro-v5-blog-starter.jldec.me/blog/first-post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Markdown
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2024-12-01'&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;My&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;First&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Blog&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Post'&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../images/birch-trees.webp'&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## Markdown is&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; a lightweight markup language
&lt;span class="p"&gt;-&lt;/span&gt; for decorating plain text
&lt;span class="p"&gt;-&lt;/span&gt; with things like headings, lists, links, and blockquotes
&lt;span class="p"&gt;-&lt;/span&gt; making minimal assumptions about formatting.

&lt;span class="gu"&gt;#### Here is an inline image:&lt;/span&gt;

&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;sunset&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;../images/sunset-cambridge.jpg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HTML
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_astro/birch-trees.DrRha0ED_9bfrU.webp"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"2016"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"955"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
  &lt;span class="na"&gt;decoding=&lt;/span&gt;&lt;span class="s"&gt;"async"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-60 object-cover object-bottom"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"prose mx-auto p-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My First Blog Post&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&amp;amp;lt;&lt;/span&gt; Back&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"markdown-is"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Markdown is&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;a lightweight markup language&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;for decorating plain text&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;with things like headings, lists, links, and blockquotes&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;making minimal assumptions about formatting.&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h4&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"here-is-an-inline-image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Here is an inline image:&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
      &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"sunset"&lt;/span&gt;
      &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1343"&lt;/span&gt;
      &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"683"&lt;/span&gt;
      &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
      &lt;span class="na"&gt;decoding=&lt;/span&gt;&lt;span class="s"&gt;"async"&lt;/span&gt;
      &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_astro/sunset-cambridge.7ZAluiBF_15WAVg.webp"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Starter Repo
&lt;/h2&gt;

&lt;p&gt;Here is the result of extracting these key features into a new blog starter. The repo does not include a lot of design - just the configuration and a minimal amount of code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/jldec/astro-v5-blog-starter" rel="noopener noreferrer"&gt;https://github.com/jldec/astro-v5-blog-starter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The project includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.astro.build/en/guides/integrations-guide/tailwind/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; with &lt;a href="https://docs.astro.build/en/recipes/tailwind-rendered-markdown/#setting-up-tailwindcsstypography" rel="noopener noreferrer"&gt;typography&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Image optimization with the &lt;a href="https://docs.astro.build/en/guides/images/#display-optimized-images-with-the-image--component" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt;&lt;/a&gt; component&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.astro.build/en/guides/content-collections/#building-for-static-output-default" rel="noopener noreferrer"&gt;Static&lt;/a&gt; builds (SSG) for Cloudflare Pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  File Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── LICENSE
├── README.md
├── astro.config.mjs
├── package.json
├── public
│   ├── _headers
│   └── favicon.svg
├── src
│   ├── assets
│   │   └── astro.svg
│   ├── components
│   │   └── AstroLogo.astro
│   ├── content
│   │   ├── blog
│   │   │   ├── 2nd-post.md
│   │   │   └── first-post.md
│   │   └── images
│   │       ├── birch-trees.webp
│   │       └── sunset-cambridge.jpg
│   ├── content.config.ts
│   ├── layouts
│   │   └── Layout.astro
│   ├── pages
│   │   ├── 404.astro
│   │   ├── blog
│   │   │   └── [id].astro
│   │   └── index.astro
│   └── styles
│       └── global.css
├── tailwind.config.mjs
├── tsconfig.json
└── wrangler.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Markdown driven blog
&lt;/h2&gt;

&lt;p&gt;Markdown blog posts live in &lt;a href="https://github.com/jldec/astro-v5-blog-starter/tree/main/src/content/blog" rel="noopener noreferrer"&gt;src/content/blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The schema for the &lt;code&gt;blog&lt;/code&gt; collection is defined in &lt;a href="https://github.com/jldec/astro-v5-blog-starter/blob/main/src/content.config.ts" rel="noopener noreferrer"&gt;content.config.ts&lt;/a&gt;. This file also includes utility functions for sorting and filtering posts. E.g. if the &lt;code&gt;draft&lt;/code&gt; flag is set, a post will only be included during dev, but not published with the production build.&lt;/p&gt;

&lt;p&gt;The home page is defined in &lt;a href="https://github.com/jldec/astro-v5-blog-starter/blob/main/src/pages/index.astro" rel="noopener noreferrer"&gt;src/pages/index.astro&lt;/a&gt; which lists posts in date order.&lt;/p&gt;

&lt;p&gt;Posts are rendered by the dynamic route &lt;a href="https://github.com/jldec/astro-v5-blog-starter/blob/main/src/pages/blog/%5Bid%5D.astro" rel="noopener noreferrer"&gt;src/pages/blog/[id].astro&lt;/a&gt;. In order for Astro to pre-render static pages, dynamic routes export a &lt;code&gt;getStaticPaths&lt;/code&gt; function which returns a list of params and props for each rendered route.&lt;/p&gt;

&lt;h3&gt;
  
  
  src/pages/blog/[id].astro
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro:assets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;filterAllPosts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/content.config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/layouts/Layout.astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticPaths&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filterAllPosts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-60 object-cover object-bottom"&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"prose mx-auto p-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&amp;amp;lt;&lt;/span&gt; Back&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Image optimization
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;src&lt;/code&gt; path and &lt;code&gt;alt&lt;/code&gt; text can be declared for images in markdown frontmatter, or inline in markdown.&lt;/p&gt;

&lt;p&gt;These are passed into the &lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt; component which inspects each image, and generates &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags with &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes, thereby reducing layout shifts in the browser.&lt;/p&gt;

&lt;p&gt;Images are converted to webp format, and stored in &lt;code&gt;dist/_astro&lt;/code&gt; with unique (cacheable) names during the build process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing on Cloudflare Pages
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.astro.build/en/guides/integrations-guide/cloudflare/" rel="noopener noreferrer"&gt;@astrojs/cloudflare&lt;/a&gt; adapter is not needed for static sites.&lt;/p&gt;

&lt;p&gt;Cloudflare Pages &lt;a href="https://developers.cloudflare.com/pages/configuration/serving-pages/#route-matching" rel="noopener noreferrer"&gt;matches routes&lt;/a&gt; with &lt;code&gt;.html&lt;/code&gt; files. To avoid trailing slashes, &lt;a href="https://github.com/jldec/astro-v5-blog-starter/blob/main/astro.config.mjs#L8-L11" rel="noopener noreferrer"&gt;configure&lt;/a&gt; the build to generate &lt;code&gt;&amp;lt;route&amp;gt;.html&lt;/code&gt; files instead of &lt;code&gt;&amp;lt;route&amp;gt;/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/jldec/astro-v5-blog-starter/blob/main/public/_headers" rel="noopener noreferrer"&gt;_headers&lt;/a&gt; file adds cache control headers for immutable content.&lt;/p&gt;

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

&lt;p&gt;Astro v5 is a great choice for a markdown driven blog, as long as you're fine with doing occasional maintenance to update dependencies.&lt;/p&gt;

&lt;p&gt;Here are some ideas for future improvements to this starter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sitemap&lt;/li&gt;
&lt;li&gt;Menu component for desktop and mobile&lt;/li&gt;
&lt;li&gt;Nicer fonts&lt;/li&gt;
&lt;li&gt;Icons for social links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you find this starter useful. Please reach out &lt;a href="https://x.com/jldec" rel="noopener noreferrer"&gt;on X&lt;/a&gt; if you have any feedback or suggestions.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>GitHub Flavored Callouts</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Mon, 18 Dec 2023 00:09:07 +0000</pubDate>
      <link>https://dev.to/jldec/github-flavored-callouts-234n</link>
      <guid>https://dev.to/jldec/github-flavored-callouts-234n</guid>
      <description>&lt;p&gt;I enjoyed adding support for callouts in &lt;a href="https://zaui.zeroasic.com/" rel="noopener noreferrer"&gt;zaui&lt;/a&gt; today.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu5apw4u7fhf8zhnkw5ns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu5apw4u7fhf8zhnkw5ns.png" alt="Screenshot with standard callout" width="600" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the markdown source,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gt"&gt;&amp;gt; [!TIP]&lt;/span&gt;
&lt;span class="gt"&gt;&amp;gt; The [zaui guide](https://zaui.zeroasic.com/guide/markdown-extensions) has more details.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The markdown format matches the recently introduced GitHub Markdown &lt;a href="https://github.blog/changelog/2023-12-14-new-markdown-extension-alerts-provide-distinctive-styling-for-significant-content/" rel="noopener noreferrer"&gt;Alerts extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition to the now GitHub standard [!&lt;code&gt;NOTE&lt;/code&gt;|&lt;code&gt;TIP&lt;/code&gt;|&lt;code&gt;IMPORTANT&lt;/code&gt;|&lt;code&gt;CAUTION&lt;/code&gt;|&lt;code&gt;WARNING&lt;/code&gt;] you can also follow the [!&lt;code&gt;{something}&lt;/code&gt;] pattern, to make up your own callouts, perhaps with an emoji.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gt"&gt;&amp;gt; [!💥 zaui]&lt;/span&gt;
&lt;span class="gt"&gt;&amp;gt; Getting a little better every day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2F4p19fkbx2qp4qx4j0d7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4p19fkbx2qp4qx4j0d7z.png" alt="Screenshot with custom zaui callout" width="600" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Careful, this&lt;/em&gt; 👆 &lt;em&gt;only works with zaui, not on GitHub&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Along with this change, zaui also gained a &lt;strong&gt;next&lt;/strong&gt; button in the Post layout.&lt;/p&gt;

&lt;p&gt;This should make it easier to navigate to the next page in the guide, and to the next post in the blog.&lt;/p&gt;

&lt;p&gt;You can try it out at the top of &lt;a href="https://jldec.me/blog/github-flavored-callouts" rel="noopener noreferrer"&gt;this page...&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3fzks45sjcxuwdktmyxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3fzks45sjcxuwdktmyxu.png" alt="Screenshot with a " width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💥
&lt;/h2&gt;

</description>
      <category>markdown</category>
      <category>github</category>
      <category>webdev</category>
      <category>news</category>
    </item>
    <item>
      <title>Getting started with Python Packaging</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Wed, 22 Feb 2023 16:43:30 +0000</pubDate>
      <link>https://dev.to/jldec/getting-started-with-python-526k</link>
      <guid>https://dev.to/jldec/getting-started-with-python-526k</guid>
      <description>&lt;h2&gt;
  
  
  Let's do some Python
&lt;/h2&gt;

&lt;p&gt;In preparation for using a lot more Python, I decided to refresh my Python knowedge and publish my first Python module at &lt;a href="https://pypi.org/project/shortscale/" rel="noopener noreferrer"&gt;https://pypi.org/project/shortscale/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some readers may recognize &lt;a href="https://jldec.me/forays-from-node-to-rust" rel="noopener noreferrer"&gt;shortscale&lt;/a&gt; from earlier explorations in &lt;a href="https://github.com/jldec/shortscale" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt;, &lt;a href="https://github.com/jldec/shortscale-rs" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;, and &lt;a href="https://github.com/jldec/shortscale-go" rel="noopener noreferrer"&gt;Go&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post covers the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Python on macOS&lt;/li&gt;
&lt;li&gt;Write the skeleton code, with just a one-line function.&lt;/li&gt;
&lt;li&gt;Build and publish the incomplete &lt;a href="https://github.com/jldec/shortscale-py/tree/bb9b026b9097ce9c601e632a9d1f74a7da6adf29" rel="noopener noreferrer"&gt;v0.1&lt;/a&gt; module.&lt;/li&gt;
&lt;li&gt;Complete the logic &lt;a href="https://github.com/jldec/shortscale-py/commit/6ab4a4b541590a60ebe2944473094465ba8f14f5" rel="noopener noreferrer"&gt;v1.0.0&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Benchmarks&lt;/li&gt;
&lt;li&gt;Python in the browser&lt;/li&gt;
&lt;li&gt;Jupyter notebooks&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Install python v3.10 (the hard way)
&lt;/h2&gt;

&lt;p&gt;Installing Python on macOS is easiest with &lt;a href="https://www.python.org/downloads/macos/" rel="noopener noreferrer"&gt;the official installer&lt;/a&gt; or &lt;a href="https://realPython.com/installing-python/#how-to-install-from-homebrew" rel="noopener noreferrer"&gt;with homebrew&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I wanted a way to switch between Python versions, so I followed the instructions for &lt;a href="https://github.com/pyenv/pyenv" rel="noopener noreferrer"&gt;pyenv&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;NOTE: This does a full local build of CPython, and requires dependencies different from the macOS command line tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. Install pyenv 
# from https://github.com/pyenv/pyenv#set-up-your-shell-environment-for-pyenv
git clone https://github.com/pyenv/pyenv.git $HOME/.pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"

# 2. Fix dependencies for macOS 
# from https://github.com/pyenv/pyenv/wiki#suggested-build-environment
brew install openssl readline sqlite3 xz zlib tcl-tk

# 3. After the brew install, fix LDFLAGS, CPPFLAGS and add tcl-tk/bin onto PATH 
export LDFLAGS="$LDFLAGS -L$HOME/homebrew/opt/openssl@3/lib -L$HOME/homebrew/opt/readline/lib -L$HOME/homebrew/opt/sqlite/lib -L$HOME/homebrew/opt/zlib/lib -L$HOME/homebrew/opt/tcl-tk/lib -L$HOME/homebrew/opt/openssl@3/lib -L$HOME/homebrew/opt/readline/lib -L$HOME/homebrew/opt/sqlite/lib -L$HOME/homebrew/opt/zlib/lib -L$HOME/homebrew/opt/tcl-tk/lib"
export CPPFLAGS="$CPPFLAGS -I$HOME/homebrew/opt/openssl@3/include -I$HOME/homebrew/opt/readline/include -I$HOME/homebrew/opt/sqlite/include -I$HOME/homebrew/opt/zlib/include -I$HOME/homebrew/opt/tcl-tk/include -I$HOME/homebrew/opt/openssl@3/include -I$HOME/homebrew/opt/readline/include -I$HOME/homebrew/opt/sqlite/include -I$HOME/homebrew/opt/zlib/include -I$HOME/homebrew/opt/tcl-tk/include"
export PATH=$HOME/homebrew/opt/tcl-tk/bin:$PATH

# 4. Use pyenv to build and install python v3.10 and make it the global default
pyenv install 3.10
pyenv global 3.10

# Point to the installed version in  .bash_profile (instead of depending on the pyenv shim)
export PATH=$HOME/.pyenv/versions/3.10.9/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Virtual environments and pip
&lt;/h2&gt;

&lt;p&gt;Python &lt;a href="https://docs.python.org/3/tutorial/modules.html" rel="noopener noreferrer"&gt;modules&lt;/a&gt; and their dependencies can be installed from &lt;a href="https://pypi.org/" rel="noopener noreferrer"&gt;pypi.org&lt;/a&gt; using &lt;code&gt;pip install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Configuring a &lt;a href="https://docs.python.org/3/library/venv.html" rel="noopener noreferrer"&gt;virtual environment&lt;/a&gt; will isolate modules under a &lt;code&gt;.venv&lt;/code&gt; directory, which is easy to clean up, rather than installing everything globally.&lt;/p&gt;

&lt;p&gt;I created a venv under my home directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv ~/.venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of "activating" the venv, which changes the prompt, I prepended the .venv/bin directory onto my PATH.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH=$HOME/.venv/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a new module called shortscale
&lt;/h2&gt;

&lt;p&gt;First I wrote a skeleton &lt;code&gt;shortscale&lt;/code&gt; function which just returns a string with the input.&lt;/p&gt;

&lt;p&gt;The rest of the code is boilerplate, to make the function callable on the command line. Passing base=0 to &lt;a href="https://docs.python.org/3/library/functions.html#int" rel="noopener noreferrer"&gt;int()&lt;/a&gt; enables numeric literal input with different bases. &lt;/p&gt;

&lt;h4&gt;
  
  
  shortscale.py
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;English conversion from number to string&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;__version__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shortscale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{} ({} bits)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Usage: shortscale num&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shortscale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python shortscale.py 0x42
66 &lt;span class="o"&gt;(&lt;/span&gt;7 bits&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I built and published this incomplete &lt;a href="https://github.com/jldec/shortscale-py/tree/bb9b026b9097ce9c601e632a9d1f74a7da6adf29" rel="noopener noreferrer"&gt;v0.1&lt;/a&gt; shortscale module.&lt;/p&gt;

&lt;p&gt;Unlike the npm JavaScript ecosystem, you can't just use pip to publish a module to the pypi repository. There are &lt;a href="https://packaging.python.org/en/latest/tutorials/packaging-projects/#creating-pyproject-toml" rel="noopener noreferrer"&gt;different build tools&lt;/a&gt; to choose from.&lt;/p&gt;

&lt;p&gt;I chose &lt;code&gt;setuptools&lt;/code&gt; because it appears to be the recommended tool, and shows what it's doing. This meant installing &lt;a href="https://pypi.org/project/build/" rel="noopener noreferrer"&gt;build&lt;/a&gt; and &lt;a href="https://pypi.org/project/build/" rel="noopener noreferrer"&gt;twine&lt;/a&gt;.   &lt;/p&gt;

&lt;p&gt;Python packages are described in a &lt;code&gt;pyproject.toml&lt;/code&gt;. Note that project.scripts points to the CLI entrypoint at main().&lt;/p&gt;

&lt;h4&gt;
  
  
  pyproject.toml
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shortscale"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"English conversion from number to string"&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;[{name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Jürgen Leschner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jldec@users.noreply.github.com"&lt;/span&gt;&lt;span class="err"&gt;}]&lt;/span&gt;
&lt;span class="py"&gt;readme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"README.md"&lt;/span&gt;
&lt;span class="py"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"LICENSE"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;classifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"License :: OSI Approved :: MIT License"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;dynamic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[project.urls]&lt;/span&gt;
&lt;span class="py"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/jldec/shortscale-py"&lt;/span&gt;

&lt;span class="nn"&gt;[project.scripts]&lt;/span&gt;
&lt;span class="py"&gt;shortscale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shortscale:main"&lt;/span&gt;

&lt;span class="nn"&gt;[build-system]&lt;/span&gt;
&lt;span class="py"&gt;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["setuptools&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;61.0&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;build-backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"setuptools.build_meta"&lt;/span&gt;

&lt;span class="nn"&gt;[tool.setuptools.dynamic]&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;attr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shortscale.__version__"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build the module
&lt;/h3&gt;

&lt;p&gt;The build tool creates 2 module bundles (source and runnable code) in the ./dist directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python -m build
...
Successfully built shortscale-0.1.0.tar.gz and shortscale-0.1.0-py3-none-any.whl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Publish to pypi.org
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python -m twine upload dist/*
Uploading distributions to https://upload.pypi.org/legacy/
Uploading shortscale-0.1.0-py3-none-any.whl
Uploading shortscale-0.1.0.tar.gz
...
View at:
https://pypi.org/project/shortscale/0.1.0/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install and run in a venv
&lt;/h2&gt;

&lt;p&gt;The moment of truth. Install the module in a new venv, and invoke it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir test&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd test&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

&lt;span class="o"&gt;(&lt;/span&gt;.venv&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$ &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;shortscale
...
Successfully installed shortscale-0.1.0

&lt;span class="o"&gt;(&lt;/span&gt;.venv&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$ &lt;/span&gt;shortscale 0xffffffffffff
281474976710655 &lt;span class="o"&gt;(&lt;/span&gt;48 bits&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;deactivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Complete the logic
&lt;/h2&gt;

&lt;p&gt;Python still amazes me with its terseness and readability. &lt;/p&gt;

&lt;p&gt;The first iteration had &lt;a href="https://github.com/jldec/shortscale-py/blob/513ce04fae86245a98e1086847ae64b5c6b922e1/shortscale.py" rel="noopener noreferrer"&gt;3 functions&lt;/a&gt;, of which the longest had 30 lines with generous spacing. &lt;/p&gt;

&lt;p&gt;One of those functions decomposes a number into powers of 1000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;powers_of_1000&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Return list of (n, exponent) for each power of 1000.
    List is ordered highest exponent first.
    n = 0 - 999.
    exponent = 0,1,2,3...
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;p_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;exponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;p_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
        &lt;span class="n"&gt;exponent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p_list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playing aound in a Jupyter notebook, I was able to eliminate the extra function (and the list which it returns), simply by reversing the order of building the shortscale output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7pyeihdofyqt4d0v8y51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7pyeihdofyqt4d0v8y51.png" alt="Screenshot of a Jupyter notebook in VS Code exploring shortscale" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using a Jupyter environment in VS Code is a clear win. The &lt;a href="https://github.com/jldec/shortscale-py/pull/3" rel="noopener noreferrer"&gt;result&lt;/a&gt; was simpler and faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;There is nice support for Python testing and debugging in VS Code. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/jldec/shortscale-py/blob/main/tests/test_shortscale.py" rel="noopener noreferrer"&gt;function&lt;/a&gt; to run unit tests took just 3 lines. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6jt7t1w4wm9g1t8e3a3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6jt7t1w4wm9g1t8e3a3v.png" alt="Screenshot of VS Code Test integration for pytest" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;I was pleased with the &lt;a href="https://github.com/jldec/shortscale-py/blob/main/tests/bench_shortscale.py" rel="noopener noreferrer"&gt;benchmarks&lt;/a&gt; as well. For this string manipulation micro-benchmark, CPython 3.11 is only 1.5x slower than V8 JavaScript! &lt;/p&gt;

&lt;p&gt;Compiled languages like Go and Rust will outperform that, but again, not by a huge amount.   &lt;/p&gt;

&lt;p&gt;The results below are from my personal M1 arm64 running macOS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Python v3.11.2
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python tests/bench_shortscale.py

 50000 calls,    5000000 bytes,     1264 ns/call
100000 calls,   10000000 bytes,     1216 ns/call
200000 calls,   20000000 bytes,     1216 ns/call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Python v3.10.9
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python tests/bench_shortscale.py

 50000 calls,    5000000 bytes,     1811 ns/call
100000 calls,   10000000 bytes,     1808 ns/call
200000 calls,   20000000 bytes,     1809 ns/call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Javascript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node test/bench.js

20000 calls, 2000000 bytes, 796 ns/call
20000 calls, 2000000 bytes, 790 ns/call
20000 calls, 2000000 bytes, 797 ns/call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go test -bench . -benchmem

BenchmarkShortscale-8        4227788           252.0 ns/op       248 B/op          5 allocs/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rust
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cargo bench

running 2 tests
test a_shortscale                        ... bench:         182 ns/iter (+/- 3)
test b_shortscale_string_writer_no_alloc ... bench:          63 ns/iter (+/- 2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's run shortscale in the browser
&lt;/h2&gt;

&lt;p&gt;Open your browser on &lt;a href="https://pyodide.org/en/stable/console.html" rel="noopener noreferrer"&gt;https://pyodide.org/en/stable/console.html&lt;/a&gt; and paste the following python commands into the python REPL, line by line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;micropip&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;micropip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shortscale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shortscale&lt;/span&gt;

&lt;span class="n"&gt;shortscale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shortscale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xffff0000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;shortscale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bench_shortscale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Femr2emu1idvkcaryysx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Femr2emu1idvkcaryysx1.png" alt="Screenshot of browser at https://pyodide.org/en/stable/console.html running shortscale" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks like Python in WASM in the browser is about 2 to 3 times slower than native CPython.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub and Google Colaboratory
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://github.com/jldec/shortscale-py/blob/main/shortscale.ipynb" rel="noopener noreferrer"&gt;shortscale Jupyter notebook&lt;/a&gt; from GitHub in a &lt;a href="https://colab.research.google.com/github/jldec/shortscale-py/blob/main/shortscale.ipynb" rel="noopener noreferrer"&gt;Google Colaboratory&lt;/a&gt; environment&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F130bhkog4kt3z6m8iljx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F130bhkog4kt3z6m8iljx.png" alt="Screenshot of ipynb file in GitHub" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://colab.research.google.com/github/jldec/shortscale-py/blob/main/shortscale.ipynb" rel="noopener noreferrer"&gt;https://colab.research.google.com/github/jldec/shortscale-py/blob/main/shortscale.ipynb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fv94xjo4k8m2d6r1zayeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fv94xjo4k8m2d6r1zayeu.png" alt="Screenshot of Google Colaboratory running at https://colab.research.google.com/github/jldec/shortscale-py/blob/main/shortscale.ipynb" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep on learning 🚀
&lt;/h2&gt;

</description>
      <category>embeds</category>
    </item>
    <item>
      <title>What is git LFS?</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Wed, 09 Feb 2022 18:23:45 +0000</pubDate>
      <link>https://dev.to/jldec/what-is-git-lfs-28db</link>
      <guid>https://dev.to/jldec/what-is-git-lfs-28db</guid>
      <description>&lt;h2&gt;
  
  
  What is git LFS?
&lt;/h2&gt;

&lt;p&gt;Git large file storage or LFS is a way to store binaries outside your git repo, but still work with them as if they were part of the repo.&lt;/p&gt;

&lt;p&gt;It works by replacing the actual file with a small text file which contains a pointer to the real thing. Every problem in computer science can be solved by another level of indirection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;screenshot.png 
version https://git-lfs.github.com/spec/v1
oid sha256:8ded39c045d36d4f745e46525b5c8b6a29baceb89006a3e85148ce66db9c187d
size 280654
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a brief introduction to LFS see &lt;a href="https://git-lfs.github.com/" rel="noopener noreferrer"&gt;https://git-lfs.github.com/&lt;/a&gt; or watch this &lt;a href="https://www.youtube.com/watch?v=uLR1RNqJ1Mw" rel="noopener noreferrer"&gt;video&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Installation requires 2 steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Place &lt;code&gt;git-lfs&lt;/code&gt; on the path.&lt;br&gt;&lt;br&gt;
I pulled the latest binary for macOS arm64 from &lt;a href="https://github.com/git-lfs/git-lfs/releases" rel="noopener noreferrer"&gt;GitHub releases&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure git to use LFS with &lt;code&gt;git lfs install&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both of these steps are included in the &lt;code&gt;install.sh&lt;/code&gt; script, in the GitHub release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is the binary called 'git-lfs'?
&lt;/h2&gt;

&lt;p&gt;A git extension is a binary prefixed with &lt;code&gt;git-&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;git lfs&lt;/code&gt; invokes &lt;code&gt;git-lfs&lt;/code&gt;, which gives you a nice summary of the available commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;To get started with Git LFS, the following commands can be used.

 1. Setup Git LFS on your system. You only have to do this once per
    repository per machine:

        git lfs install

 2. Choose the type of files you want to track, for examples all ISO
    images, with git lfs track:

        git lfs track "*.iso"

 3. The above stores this information in gitattributes(5) files, so
    that file need to be added to the repository:

        git add .gitattributes

 4. Commit, push and work with the files normally:

        git add file.iso
        git commit -m "Add disk image"
        git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What does 'git lfs install' do?
&lt;/h2&gt;

&lt;p&gt;The command &lt;code&gt;git lfs install&lt;/code&gt; configures a git filter, which calls git-lfs to convert between pointer files and the actual blobs.&lt;br&gt;
This filter configuration lives in your git config (e.g. at &lt;code&gt;~/.gitconfig&lt;/code&gt;) and applies to all your repos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[filter "lfs"]
    clean = git-lfs clean -- %f
    smudge = git-lfs smudge -- %f
    process = git-lfs filter-process
    required = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer not to run these hooks all the time, you can uninstall them with &lt;code&gt;git lfs uninstall&lt;/code&gt; after finishing your work on an LFS git repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is a repo configured to use LFS?
&lt;/h2&gt;

&lt;p&gt;The command &lt;code&gt;git lfs track '*.png'&lt;/code&gt; adds the following line to the &lt;code&gt;.gitattributes&lt;/code&gt; file in the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.png filter=lfs diff=lfs merge=lfs -text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This associates git-lfs with &lt;code&gt;.png&lt;/code&gt; files.&lt;br&gt;
Once the association is made, you can work with those files normally, and git-lfs will handle the LFS interactions with GitHub automatically, in the background.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example repo
&lt;/h2&gt;

&lt;p&gt;This repo lives at &lt;a href="https://github.com/jldec/lfs-test" rel="noopener noreferrer"&gt;github.com/jldec/lfs-test&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git init
Initialized empty Git repository &lt;span class="k"&gt;in&lt;/span&gt; /Users/jldec/lfs-test/.git/

&lt;span class="nv"&gt;$ &lt;/span&gt;git lfs track &lt;span class="s1"&gt;'*.png'&lt;/span&gt;
Tracking &lt;span class="s2"&gt;"*.png"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git lfs track
Listing tracked patterns
    &lt;span class="k"&gt;*&lt;/span&gt;.png &lt;span class="o"&gt;(&lt;/span&gt;.gitattributes&lt;span class="o"&gt;)&lt;/span&gt;
Listing excluded patterns

&lt;span class="nv"&gt;$ &lt;/span&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'initial commit'&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main &lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt; c25b950] initial commit
 2 files changed, 4 insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
 create mode 100644 .gitattributes
 create mode 100644 screenshot.png

&lt;span class="nv"&gt;$ &lt;/span&gt;git lfs ls-files
ba2a8c5285 &lt;span class="k"&gt;*&lt;/span&gt; screenshot.png

&lt;span class="nv"&gt;$ &lt;/span&gt;git remote add origin git@github.com:jldec/lfs-test.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
Uploading LFS objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;1/1&lt;span class="o"&gt;)&lt;/span&gt;, 146 KB | 0 B/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Enumerating objects: 4, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;4/4&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Delta compression using up to 8 threads
Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;3/3&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Writing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;4/4&lt;span class="o"&gt;)&lt;/span&gt;, 408 bytes | 408.00 KiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Total 4 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;, reused 0 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
To github.com:jldec/lfs-test.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch]      main -&amp;gt; main
Branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track remote branch &lt;span class="s1"&gt;'main'&lt;/span&gt; from &lt;span class="s1"&gt;'origin'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git status
On branch main
Your branch is up to &lt;span class="nb"&gt;date &lt;/span&gt;with &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Changes not staged &lt;span class="k"&gt;for &lt;/span&gt;commit:
  &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git add &amp;lt;file&amp;gt;..."&lt;/span&gt; to update what will be committed&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git restore &amp;lt;file&amp;gt;..."&lt;/span&gt; to discard changes &lt;span class="k"&gt;in &lt;/span&gt;working directory&lt;span class="o"&gt;)&lt;/span&gt;
        modified:   screenshot.png

no changes added to commit &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git add"&lt;/span&gt; and/or &lt;span class="s2"&gt;"git commit -a"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'update lfs file screenshot.png'&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;main e695f5f] update lfs file screenshot.png
 1 file changed, 2 insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;, 2 deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git push
Uploading LFS objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;1/1&lt;span class="o"&gt;)&lt;/span&gt;, 281 KB | 121 KB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Enumerating objects: 5, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;5/5&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Delta compression using up to 8 threads
Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;3/3&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Writing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;3/3&lt;span class="o"&gt;)&lt;/span&gt;, 405 bytes | 405.00 KiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Total 3 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;, reused 0 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
To github.com:jldec/lfs-test.git
   c25b950..e695f5f  main -&amp;gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>git</category>
      <category>github</category>
      <category>beginners</category>
    </item>
    <item>
      <title>First impressions of the new GitHub Projects Beta</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Sat, 06 Nov 2021 17:19:33 +0000</pubDate>
      <link>https://dev.to/jldec/first-impressions-of-the-new-github-projects-beta-nca</link>
      <guid>https://dev.to/jldec/first-impressions-of-the-new-github-projects-beta-nca</guid>
      <description>&lt;h2&gt;
  
  
  Issues with GitHub issues
&lt;/h2&gt;

&lt;p&gt;GitHub issues have historically provided a simple yet powerful way to track work in your GitHub repositories. Each issue includes a description, assignee(s), and a timeline with a comment thread and automatically-generated references to related issues and PRs. Issues can be categorized using labels, and the issues list can be searched or filtered in many ways.&lt;/p&gt;

&lt;p&gt;But as projects get larger and more complex, working with issues also has its challenges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to turn large "Epic" issues into smaller issues?&lt;/li&gt;
&lt;li&gt;How to prioritize and organize issues into iterations?&lt;/li&gt;
&lt;li&gt;How to track issues across multiple repositories?&lt;/li&gt;
&lt;li&gt;How best to incorporate community contributions and other feedback?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub has incrementally tried to address some of these challenges. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.github.com/en/issues/tracking-your-work-with-issues/about-task-lists" rel="noopener noreferrer"&gt;Task lists&lt;/a&gt; add convenient checkboxes to markdown lists in issue descriptions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/about-milestones" rel="noopener noreferrer"&gt;Milestones&lt;/a&gt; provide a simple way to collect and prioritize issues within a repo. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.github.com/en/issues/organizing-your-work-with-project-boards/managing-project-boards" rel="noopener noreferrer"&gt;Projects&lt;/a&gt; started as single-repo kanban boards, with issues or notes moving vertically or sideways. They later acquired cross-repo and limited automation capabilities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates" rel="noopener noreferrer"&gt;Issue templates&lt;/a&gt; help contributors to include specific information in an issue.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The new GitHub Issues (Beta)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;With their Issues announcement in June, GitHub signalled a less-incremental approach.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://github.blog/changelog/2021-06-23-whats-new-with-github-issues/" rel="noopener noreferrer"&gt;announcement&lt;/a&gt; included two parts&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A powerful new way to &lt;a href="https://docs.github.com/en/issues/trying-out-the-new-projects-experience/creating-a-project" rel="noopener noreferrer"&gt;create&lt;/a&gt; and &lt;a href="https://docs.github.com/en/issues/trying-out-the-new-projects-experience/customizing-your-project-views" rel="noopener noreferrer"&gt;group issues&lt;/a&gt; into projects.&lt;/li&gt;
&lt;li&gt;Ways to grow from ideas expressed as text, into collections of issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/label/issues/" rel="noopener noreferrer"&gt;More announcements&lt;/a&gt; have followed, and last week the &lt;a href="https://github.blog/changelog/2021-10-27-the-new-github-issues-public-beta/" rel="noopener noreferrer"&gt;Beta opened up&lt;/a&gt; to all users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beta Projects
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/issues/trying-out-the-new-projects-experience/about-projects" rel="noopener noreferrer"&gt;Project tables&lt;/a&gt; are spreadsheet-like views where each row is a real or draft issue. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/orgs/github/projects/4247/views/7" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2F77a8ah8k4g446w4z4qz3.png" alt="Screenshot of GitHub project table" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rows (issues) can be grouped by field value. This includes custom fields whose values are maintained in the project instead of on issues in a repo. Maintaining custom field data inside projects is key to their power.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rather than polluting your issues with all possible categories of tags, new categories can be scoped inside a project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since Beta Projects also support kanban views, I expect them to replace the existing Projects once they reach feature parity.&lt;/p&gt;

&lt;h2&gt;
  
  
  From text to issues
&lt;/h2&gt;

&lt;p&gt;Auto-creating issues from &lt;a href="https://docs.github.com/en/issues/tracking-your-work-with-issues/about-task-lists#about-issue-task-lists" rel="noopener noreferrer"&gt;task lists&lt;/a&gt; makes it easier to break down "epic" issues into smaller sub-issues. The task list item is checked off when the issue is resolved, and the child-issue links to the parent-issue with the task list.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gitpod-io/gitpod/issues/3065" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2Fpetvdco031zibsnf04ah.png" alt="Screenshot of GitHub issue with a task list" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, &lt;a href="https://docs.github.com/en/issues/trying-out-the-new-projects-experience/creating-a-project#adding-items-to-your-project" rel="noopener noreferrer"&gt;draft issues&lt;/a&gt; which are simply rows entered as text in a project table, can also be &lt;a href="https://docs.github.com/en/issues/trying-out-the-new-projects-experience/creating-a-project#converting-draft-issues-to-issues" rel="noopener noreferrer"&gt;converted&lt;/a&gt; into issues. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/orgs/github/projects/4247/views/7" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2Fem9e6iw6cu6ix2doyxap.png" alt="Screenshot of converting draft issue to issue in GitHub project table" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This makes task lists and project tables two convenient ways to brainstorm ideas, and break them down into smaller issues.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The GitHub public roadmap features a &lt;a href="https://github.com/orgs/github/projects/4247/views/7" rel="noopener noreferrer"&gt;planning view&lt;/a&gt; specific to issues. &lt;/p&gt;

&lt;p&gt;A number of informative talks at the recent &lt;a href="https://www.githubuniverse.com/2021/" rel="noopener noreferrer"&gt;GitHub Universe 2021&lt;/a&gt; also provided hints about what the team is planning.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember that the new projects and issues features are still in Beta.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>github</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Using Gitpod to create a PR</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Sun, 24 Oct 2021 23:02:21 +0000</pubDate>
      <link>https://dev.to/jldec/using-gitpod-to-create-a-pr-3cba</link>
      <guid>https://dev.to/jldec/using-gitpod-to-create-a-pr-3cba</guid>
      <description>&lt;h2&gt;
  
  
  Gitpod Workspaces
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.gitpod.io/" rel="noopener noreferrer"&gt;Gitpod&lt;/a&gt; hosts &lt;strong&gt;workspaces&lt;/strong&gt; for developers.&lt;/p&gt;

&lt;p&gt;Think of each &lt;a href="https://www.gitpod.io/docs#your-computer-in-the-cloud" rel="noopener noreferrer"&gt;workspace&lt;/a&gt; as your own Linux container in the cloud, with a fully functional development environment, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clone of your git repo and your git working tree - the files you're working on.&lt;/li&gt;
&lt;li&gt;The tools you need while coding - compilers, SDKs, runtimes.&lt;/li&gt;
&lt;li&gt;Your editor - the default is &lt;a href="https://www.gitpod.io/blog/openvscode-server-launch" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; + extensions - reachable through a browser.&lt;/li&gt;
&lt;li&gt;Shell access to run commands in the container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of laboriously maintaining your local development environment with everything you need, you simply open a new workspace every time you start working on a new project or branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR for the Gitpod website
&lt;/h2&gt;

&lt;p&gt;Here's how I created a PR for the &lt;a href="https://www.gitpod.io/" rel="noopener noreferrer"&gt;Gitpod website&lt;/a&gt; in just a few minutes.&lt;/p&gt;

&lt;p&gt;Starting from the &lt;a href="https://github.com/gitpod-io/website/issues/1139" rel="noopener noreferrer"&gt;issue&lt;/a&gt;, I opened a new workspace in Gitpod by prefixing the GitHub url like so: &lt;code&gt;https://gitpod.io/#https://github.com/gitpod-io/website/issues/1139&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;That &lt;a href="https://gitpod.io/#https://github.com/gitpod-io/website/issues/1139" rel="noopener noreferrer"&gt;url&lt;/a&gt; opened the workspace in my browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fr9wfi1v58qcgdif4udht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fr9wfi1v58qcgdif4udht.png" alt="Screenshot of full VS Code window in Gitpod workspace" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;git branch -vv&lt;/code&gt; in a workspace terminal showed that git was already set to a new branch, conveniently named with my username and the description and id of the GitHub issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyxbhlx435as1al4rs3xy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyxbhlx435as1al4rs3xy.png" alt="Screenshot of VS Code terminal in Gitpod workspace showing new git branch" width="800" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workspace started up with npm modules already installed by a &lt;a href="https://www.gitpod.io/docs/prebuilds" rel="noopener noreferrer"&gt;prebuild&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The repo was also &lt;a href="https://www.gitpod.io/docs/config-gitpod-file" rel="noopener noreferrer"&gt;configured&lt;/a&gt; to start a dev server listening on port 3000 in the workspace container. The open ports can be seen in the Remote Explorer sidebar on the left.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1xrp8wcp9dk4welqqy03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1xrp8wcp9dk4welqqy03.png" alt="Screenshot of VS Code Remote Explorer sidebar in Gitpod workspace showing open ports" width="696" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used the &lt;code&gt;Open Browser&lt;/code&gt; icon to open the website in another browser window, and watched my changes taking effect each time I modified the code. &lt;/p&gt;

&lt;p&gt;Finally, I pushed a commit with my changes on the new branch to GitHub, and proceeded to create my PR as usual. No localhost interaction other than running my browser was required.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This blogpost was written from a Gitpod workspace.&lt;br&gt;
🚀 &lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>productivity</category>
      <category>git</category>
      <category>vscode</category>
      <category>gitpod</category>
    </item>
    <item>
      <title>Preventing concurrent GitHub Actions</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Sun, 13 Jun 2021 22:49:22 +0000</pubDate>
      <link>https://dev.to/jldec/preventing-concurrent-github-actions-2n3l</link>
      <guid>https://dev.to/jldec/preventing-concurrent-github-actions-2n3l</guid>
      <description>&lt;h2&gt;
  
  
  GitHub Actions Workflows
&lt;/h2&gt;

&lt;p&gt;What happens when you trigger a &lt;a href="https://dev.to/github-actions-101"&gt;GitHub Actions&lt;/a&gt; workflow which is already running? Workflows which depend on being run one-at-a-time might fail.&lt;/p&gt;

&lt;p&gt;I recently encountered this with a &lt;a href="https://github.com/jldec/cloudflare-pages-test/blob/main/.github/workflows/generate.yaml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; for publishing a static website. This workflow generates HTML files which are pushed to another git repo for publishing by &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Focc88489u0tqlayurx8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Focc88489u0tqlayurx8z.png" alt="Screenshot of Github Actions log showing failed git push" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When two workflows try to push to a checked-out repo at the same time, one will fail because it is missing the last commit from the other.&lt;/p&gt;

&lt;p&gt;This is just one example where concurrent workflows are problematic. Workflows which &lt;a href="https://github.community/t/serializing-workflow-runs-in-the-context-of-continuous-deployment/17559" rel="noopener noreferrer"&gt;automate&lt;/a&gt; &lt;a href="https://github.community/t/how-to-limit-concurrent-workflow-runs/16844" rel="noopener noreferrer"&gt;deployments&lt;/a&gt; have the same &lt;a href="https://github.community/t/serializing-queueing-deployment-workflows-aws-re-invent/17152" rel="noopener noreferrer"&gt;problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A number of 3rd party &lt;a href="https://github.com/softprops/turnstyle" rel="noopener noreferrer"&gt;solutions&lt;/a&gt; exist, but these introduce additional waiting costs or other issues. For one of my projects, I host a lock-service, just to force concurrent workflows to exit quietly, and then auto-trigger re-runs.&lt;/p&gt;

&lt;p&gt;Finally, on April 19, 2021, &lt;a href="https://github.blog/changelog/2021-04-19-github-actions-limit-workflow-run-or-job-concurrency/" rel="noopener noreferrer"&gt;this&lt;/a&gt; appeared in the GitHub Blog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fab4g8ngotnprbhnnyw37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fab4g8ngotnprbhnnyw37.png" alt="Screenshot of GitHub Blog from April 19, 2021 announcing the new concurrency key in GitHub Actions" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the case of using actions to generate a GitHub Pages website, &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency" rel="noopener noreferrer"&gt;the feature works&lt;/a&gt; exactly as required.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first workflow will run to completion.&lt;/li&gt;
&lt;li&gt;Subsequent concurrent workflows will either be delayed or cancelled.&lt;/li&gt;
&lt;li&gt;In the end only the first and last of the overlapping workflows will be run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And all you need is &lt;a href="https://github.com/jldec/cloudflare-pages-test/blob/main/.github/workflows/generate.yaml#L5-L6" rel="noopener noreferrer"&gt;2 lines of yaml&lt;/a&gt;.&lt;br&gt;
This is from the workflow which generates &lt;a href="https://jldec.me/first-steps-using-cloudflare-pages#github-pages" rel="noopener noreferrer"&gt;jldec.uk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jldec/cloudflare-pages-test/blob/main/.github/workflows/generate.yaml#L5-L6" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2Fv48sfd7coyg2a93f35j1.png" alt="Screenshot of yaml for GitHub Action with concurrency group" width="794" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;group&lt;/code&gt; can be any string - workflows in the same group are effectively serialized.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thank you GitHub!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>github</category>
      <category>actions</category>
      <category>jamstack</category>
      <category>pages</category>
    </item>
    <item>
      <title>Getting started with Goroutines and channels</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Sun, 25 Apr 2021 15:01:05 +0000</pubDate>
      <link>https://dev.to/jldec/getting-started-with-goroutines-and-channels-fc6</link>
      <guid>https://dev.to/jldec/getting-started-with-goroutines-and-channels-fc6</guid>
      <description>&lt;h2&gt;
  
  
  Golang
&lt;/h2&gt;

&lt;p&gt;This is part 3 of my experience as a new user of Go, focusing on concurrency with Goroutines and channels.&lt;/p&gt;

&lt;p&gt;For installation, testing, and packages, see &lt;a href="https://jldec.me/getting-started-with-go" rel="noopener noreferrer"&gt;Getting started with Go&lt;/a&gt;, and for pointers see &lt;a href="https://jldec.me/getting-started-with-go-part-2-pointers" rel="noopener noreferrer"&gt;Getting started with Go pointers&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counting HTTP requests
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/jldec/racey-go/blob/main/main.go" rel="noopener noreferrer"&gt;server&lt;/a&gt; below counts HTTP requests, and returns the latest count on each request. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;To follow along, clone &lt;a href="https://github.com/jldec/racey-go" rel="noopener noreferrer"&gt;https://github.com/jldec/racey-go&lt;/a&gt;, and start the server with 'go run .'&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go listening on port 3000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":3000"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:3000
1
&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:3000
2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try sending multiple requests at the same time. This command invokes curl with urls from a file using xargs to spawn 4 processes at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;urls.txt | xargs &lt;span class="nt"&gt;-P&lt;/span&gt; 4 &lt;span class="nt"&gt;-n&lt;/span&gt; 1 curl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/jldec/racey-go/blob/main/urls.txt" rel="noopener noreferrer"&gt;file&lt;/a&gt; contains 100 lines, but instead of ending on a nice round number, on systems with more than 1 core you may see  something like this (e.g. after 3 runs)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;289
292
291
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the Go server with '&lt;a href="https://github.com/jldec/racey-go/blob/main/server.js" rel="noopener noreferrer"&gt;node server.js&lt;/a&gt;' to compare the results (e.g. after 3 runs again)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;298
299
300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now repeat the experiment with the &lt;a href="https://golang.org/doc/articles/race_detector" rel="noopener noreferrer"&gt;race detector&lt;/a&gt; turned on. The detector will report a problem on &lt;a href="https://github.com/jldec/racey-go/blob/main/main.go#L12" rel="noopener noreferrer"&gt;line 12&lt;/a&gt; of main.go which is &lt;code&gt;count++&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go run -race .
Go listening on port 3000
==================
WARNING: DATA RACE
Read at 0x00c000138280 by goroutine 7:
  main.main.func1()
      /Users/jleschner/pub/racey-go/main.go:12 +0x4a
  net/http.HandlerFunc.ServeHTTP()
      /Users/jleschner/go1.16.3/src/net/http/server.go:2069 +0x51
  net/http.(*ServeMux).ServeHTTP()
      /Users/jleschner/go1.16.3/src/net/http/server.go:2448 +0xaf
  net/http.serverHandler.ServeHTTP()
      /Users/jleschner/go1.16.3/src/net/http/server.go:2887 +0xca
  net/http.(*conn).serve()
      /Users/jleschner/go1.16.3/src/net/http/server.go:1952 +0x87d

Previous write at 0x00c000138280 by goroutine 9:
  main.main.func1()
      /Users/jleschner/pub/racey-go/main.go:12 +0x64
  net/http.HandlerFunc.ServeHTTP()
      /Users/jleschner/go1.16.3/src/net/http/server.go:2069 +0x51
  net/http.(*ServeMux).ServeHTTP()
      /Users/jleschner/go1.16.3/src/net/http/server.go:2448 +0xaf
  net/http.serverHandler.ServeHTTP()
      /Users/jleschner/go1.16.3/src/net/http/server.go:2887 +0xca
  net/http.(*conn).serve()
      /Users/jleschner/go1.16.3/src/net/http/server.go:1952 +0x87d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Data races
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://golang.org/doc/articles/race_detector" rel="noopener noreferrer"&gt;race detector&lt;/a&gt; docs:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's clear that 'count++' modifies the count, but what are goroutines and where are they in this case?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Goroutines
&lt;/h2&gt;

&lt;p&gt;Goroutines provide low-overhead threading. They are easy to create, and scale well on multi-core processors.&lt;/p&gt;

&lt;p&gt;The Go runtime can schedule many concurrent goroutines across a small number of OS threads. Under the covers, this is how the &lt;a href="https://golang.org/src/net/http/server.go#L3013" rel="noopener noreferrer"&gt;http&lt;/a&gt; library handles concurrent web requests.&lt;/p&gt;

&lt;p&gt;Let's start with an example. You can run it in the &lt;a href="https://play.golang.org/p/HdH4UQEEXuU" rel="noopener noreferrer"&gt;Go Playground&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// start 2 countdowns in parallel goroutines&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;countdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"crew-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;countdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"crew-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// block waiting to receive 1st string&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// block waiting to receive 2nd string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;countdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="s"&gt;"blastoff "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each 'go countdown()' starts a new &lt;a href="https://tour.golang.org/concurrency/1" rel="noopener noreferrer"&gt;goroutine&lt;/a&gt;. Notice how the countdowns are interleaved in the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
crew-1 3
crew-2 3
crew-2 2
crew-1 2
crew-1 1
crew-2 1
blastoff crew-2
blastoff crew-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Channels
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tour.golang.org/concurrency/2" rel="noopener noreferrer"&gt;Channels&lt;/a&gt; allow goroutines to communicate and coordinate.&lt;/p&gt;

&lt;p&gt;In the example above, &lt;code&gt;&amp;lt;-ch&lt;/code&gt; (receive) will block until another goroutine uses &lt;code&gt;ch &amp;lt;-&lt;/code&gt; to send a string to the channel. This happens at the end of each countdown.&lt;/p&gt;

&lt;p&gt;Sends will also block if there are no receivers, but that is not the case here.&lt;/p&gt;

&lt;p&gt;There are many other variations for how to use channels, including &lt;a href="https://tour.golang.org/concurrency/3" rel="noopener noreferrer"&gt;buffered channels&lt;/a&gt; which only block sends when the buffer is full.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atomicity
&lt;/h2&gt;

&lt;p&gt;Given that &lt;a href="https://pkg.go.dev/net/http" rel="noopener noreferrer"&gt;net/http&lt;/a&gt; requests are handled by goroutines, can we explain why there is a data race when the function which handles a request increments a shared counter?&lt;/p&gt;

&lt;p&gt;The reason is that &lt;code&gt;count++&lt;/code&gt; requires a read followed by write, and these are not automatically synchronized. One goroutine may overwrite the increment of another, resulting in lost writes.&lt;/p&gt;

&lt;p&gt;To fix this, the counter has be protected to make the increment operation atomic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counter-go
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jldec/counter-go" rel="noopener noreferrer"&gt;github.com/jldec/counter-go&lt;/a&gt; demonstrates 3 different implementations of a threadsafe global counter.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CounterAtomic&lt;/strong&gt; uses &lt;code&gt;atomic.AddUint64&lt;/code&gt; and &lt;code&gt;atomic.LoadUint64&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CounterMutex&lt;/strong&gt; uses &lt;code&gt;sync.RWMutex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CounterChannel&lt;/strong&gt; serializes all reads and writes inside 1 goroutine with 2 channels.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All 3 types implement a Counter interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;uint32&lt;/span&gt; &lt;span class="c"&gt;// get current counter value&lt;/span&gt;
    &lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c"&gt;// increment by 1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/jldec/racey-go/blob/fix-with-counter-go/main.go" rel="noopener noreferrer"&gt;modified server&lt;/a&gt; will work with any of the 3 implementations, and no data race should be detected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="s"&gt;"github.com/jldec/counter-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CounterAtomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// count := new(counter.CounterMutex)&lt;/span&gt;
    &lt;span class="c"&gt;// count := counter.NewCounterChannel()&lt;/span&gt;

    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go listening on port 3000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":3000"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Coordination with channels
&lt;/h3&gt;

&lt;p&gt;Of the 3 implementations, &lt;a href="https://github.com/jldec/counter-go/blob/main/counter_channel.go" rel="noopener noreferrer"&gt;CounterChannel&lt;/a&gt; is the most interesting. All access to the counter goes through 1 goroutine which uses a &lt;a href="https://tour.golang.org/concurrency/5" rel="noopener noreferrer"&gt;select&lt;/a&gt; to wait for either a read or a write on one of two channels.&lt;/p&gt;

&lt;p&gt;Can you tell why neither &lt;code&gt;Inc()&lt;/code&gt; nor &lt;code&gt;Get()&lt;/code&gt; should block?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;

&lt;span class="c"&gt;// Thread-safe counter&lt;/span&gt;
&lt;span class="c"&gt;// Uses 2 Channels to coordinate reads and writes.&lt;/span&gt;
&lt;span class="c"&gt;// Must be initialized with NewCounterChannel().&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CounterChannel&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;readCh&lt;/span&gt;  &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;
    &lt;span class="n"&gt;writeCh&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// NewCounterChannel() is required to initialize a Counter.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewCounterChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CounterChannel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;CounterChannel&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;readCh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;writeCh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// The actual counter value lives inside this goroutine.&lt;/span&gt;
    &lt;span class="c"&gt;// It can only be accessed for R/W via one of the channels.&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// Reading from readCh is equivalent to reading count.&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readCh&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="c"&gt;// Writing to the writeCh increments count.&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeCh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;count&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="p"&gt;}()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Increment counter by pushing an arbitrary int to the write channel.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CounterChannel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeCh&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Get current counter value from the read channel.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CounterChannel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readCh&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CounterChannel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;check&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="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readCh&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Uninitialized Counter, requires NewCounterChannel()"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benchmarks
&lt;/h3&gt;

&lt;p&gt;All 3 &lt;a href="https://github.com/jldec/counter-go" rel="noopener noreferrer"&gt;implementations&lt;/a&gt; are fast. Serializing everything through a goroutine with channels, costs only a few hundred ns for a single read or write. When constrained to a single OS thread, the cost of goroutines is even lower.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
goos: darwin
goarch: amd64
pkg: github.com/jldec/counter-go
cpu: Intel&lt;span class="o"&gt;(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt; Core&lt;span class="o"&gt;(&lt;/span&gt;TM&lt;span class="o"&gt;)&lt;/span&gt; i7-9750H CPU @ 2.60GHz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Simple: 1 op = 1 Inc() in same thread
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;BenchmarkCounter_1/Atomic-12                 195965660          6 ns/op
BenchmarkCounter_1/Mutex-12                   54177086         22 ns/op
BenchmarkCounter_1/Channel-12                  4499144        286 ns/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Concurrent: 1 op = 1 Inc() across each of 10 goroutines
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;BenchmarkCounter_2/Atomic_no_reads-12          7298484        191 ns/op
BenchmarkCounter_2/Mutex_no_reads-12           1966656        621 ns/op
BenchmarkCounter_2/Channel_no_reads-12          256842       4771 ns/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Concurrent: 1 op = [ 1 Inc() + 10 Get() ] across each of 10 goroutines
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;BenchmarkCounter_2/Atomic_10_reads-12          3922029        286 ns/op
BenchmarkCounter_2/Mutex_10_reads-12            416354       2844 ns/op
BenchmarkCounter_2/Channel_10_reads-12           21506      55733 ns/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Constrained to single thread
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

BenchmarkCounter_1/Atomic                    197135869          6 ns/op
BenchmarkCounter_1/Mutex                      55698454         22 ns/op
BenchmarkCounter_1/Channel                     5689788        214 ns/op

BenchmarkCounter_2/Atomic_no_reads            19519166         60 ns/op
BenchmarkCounter_2/Mutex_no_reads              4702759        254 ns/op
BenchmarkCounter_2/Channel_no_reads             530554       2197 ns/op

BenchmarkCounter_2/Atomic_10_reads             6269979        189 ns/op
BenchmarkCounter_2/Mutex_10_reads               927439       1354 ns/op
BenchmarkCounter_2/Channel_10_reads              47889      25054 ns/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🚀 - code safe - 🚀&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Getting started with Go pointers</title>
      <dc:creator>jldec</dc:creator>
      <pubDate>Sun, 18 Apr 2021 19:24:42 +0000</pubDate>
      <link>https://dev.to/jldec/getting-started-with-go-part-2-pointers-4a47</link>
      <guid>https://dev.to/jldec/getting-started-with-go-part-2-pointers-4a47</guid>
      <description>&lt;h2&gt;
  
  
  Golang
&lt;/h2&gt;

&lt;p&gt;This is part 2 of my experience as a new user of Go, focusing on the quirks and gotchas of pointers. For installation, testing, and packages, see &lt;a href="https://jldec.me/getting-started-with-go" rel="noopener noreferrer"&gt;Getting started with Go&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you'd like to follow along, and try out out the code in this article, all you need is the Go &lt;a href="https://play.golang.org/p/-UiUJFrloVT" rel="noopener noreferrer"&gt;playground&lt;/a&gt; to run the examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pointers
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/jldec/shortscale-go/blob/main/shortscale.go" rel="noopener noreferrer"&gt;shortscale&lt;/a&gt; package which I covered last time, uses a string &lt;a href="https://pkg.go.dev/strings#Builder" rel="noopener noreferrer"&gt;Builder&lt;/a&gt;. Here is the example from the Builder docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"%d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ignition"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;var b&lt;/code&gt; is an instance of the Builder. When you run the code, it will output: &lt;em&gt;3...2...1...ignition&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pointer receiver methods and interfaces
&lt;/h2&gt;

&lt;p&gt;The first argument to fmt.Fprintf is &lt;code&gt;&amp;amp;b&lt;/code&gt;, a &lt;a href="https://tour.golang.org/moretypes/1" rel="noopener noreferrer"&gt;pointer&lt;/a&gt; to b. This is necessary, because &lt;a href="https://pkg.go.dev/fmt#Fprintf" rel="noopener noreferrer"&gt;fmt.Fprintf&lt;/a&gt; expects an &lt;a href="https://pkg.go.dev/io#Writer" rel="noopener noreferrer"&gt;io.Writer&lt;/a&gt; interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Writer&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://pkg.go.dev/strings#Builder.Write" rel="noopener noreferrer"&gt;Builder.Write&lt;/a&gt; method matches the io.Writer interface. Notice the pointer syntax in the method receiver after the &lt;code&gt;func&lt;/code&gt; keyword.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was tempted to replace &lt;code&gt;Fprintf(&amp;amp;b, ...)&lt;/code&gt; with &lt;code&gt;Fprintf(b, ...)&lt;/code&gt;, to make it more consistent with the &lt;code&gt;b.WriteString()&lt;/code&gt; and &lt;code&gt;b.String()&lt;/code&gt; further down, but doing this causes the compiler to complain:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"cannot use b (type strings.Builder) as type io.Writer in argument to fmt.Fprintf: strings.Builder does not implement io.Writer (&lt;strong&gt;Write method has pointer receiver&lt;/strong&gt;)"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Value vs. pointer function arguments
&lt;/h2&gt;

&lt;p&gt;What if, instead of depending on the Writer interface, we called our own &lt;code&gt;write()&lt;/code&gt; function?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ignition"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the code above in the &lt;a href="https://pkg.go.dev/strings#Builder" rel="noopener noreferrer"&gt;example sandbox&lt;/a&gt; outputs just the word &lt;em&gt;ignition&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;The 3 calls to &lt;code&gt;write(b)&lt;/code&gt; do not modify the builder declared at the top.&lt;/p&gt;

&lt;p&gt;This makes sense, because passing a struct to a function &lt;a href="https://tour.golang.org/methods/4" rel="noopener noreferrer"&gt;copies&lt;/a&gt; the struct value.&lt;/p&gt;

&lt;p&gt;To fix this, we have to use a pointer to pass the struct by reference, and we have to invoke the function with &lt;code&gt;write(&amp;amp;b, ...)&lt;/code&gt;. This works, but it doesn't make the code any more consistent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ignition"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why do the method calls work?
&lt;/h2&gt;

&lt;p&gt;Why are we allowed to use &lt;code&gt;b&lt;/code&gt; instead of &lt;code&gt;&amp;amp;b&lt;/code&gt; in front of &lt;a href="https://pkg.go.dev/strings#Builder.WriteString" rel="noopener noreferrer"&gt;b.WriteString&lt;/a&gt; and &lt;a href="https://pkg.go.dev/strings#Builder.String" rel="noopener noreferrer"&gt;b.String&lt;/a&gt;? This is explained in &lt;a href="https://tour.golang.org/methods/6" rel="noopener noreferrer"&gt;the tour&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"...even though v is a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statement v.Scale(5) as (&amp;amp;v).Scale(5) since the Scale method has a pointer receiver."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a pointer
&lt;/h2&gt;

&lt;p&gt;If all this mixing of values and pointers feels inconsistent, why not start with a pointer from the beginning?&lt;/p&gt;

&lt;p&gt;The following code will compile just fine, but can you tell what's wrong with it?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"%d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ignition"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The declaration above results in a nil pointer panic at run time, because b is uninitialized.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4991c7]&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Create the Builder with new()
&lt;/h2&gt;

&lt;p&gt;Here is one way to initialize a pointer so that it references a new Builder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"%d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ignition"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;new(strings.Builder)&lt;/code&gt; returns a pointer to a freshly allocated Builder, which we can use for both functions and pointer receiver methods. This is the pattern which I now use in &lt;a href="https://github.com/jldec/shortscale-go/blob/2485be23ef48660d8913b2ac884030220dc82d74/shortscale.go#L17-L24" rel="noopener noreferrer"&gt;shortscale-go&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An alternative, which does the same thing, is the more explicit &lt;a href="https://tour.golang.org/moretypes/5" rel="noopener noreferrer"&gt;struct literal&lt;/a&gt; shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;There's no avoiding pointers in Go.&lt;br&gt;
Learn the quirks and the gotchas today.&lt;br&gt;
✨ Keep learning! ✨&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
