{"openapi":"3.1.0","info":{"title":"Medium API","version":"1.0.0","description":"Read Medium posts in real time — no login or key needed. Pull the recent stories of any Medium user (@handle), any topic/tag, or any publication, straight from Medium's own public RSS feeds. Each post comes with its title, author, canonical link, publish date, categories/tags, a clean text excerpt and the full content HTML. Pass user = the @handle, tag = a topic slug, or publication = a publication slug; every call is live (no cache). 4 endpoints. Built for content aggregation, author/topic monitoring and reading-list back-ends. A Medium reader API. No upstream key, no cache.","contact":{"name":"PremiumApi","url":"https://www.oanor.com/by/premiumapi"}},"servers":[{"url":"https://api.oanor.com/medium-api","description":"oanor gateway"}],"tags":[{"name":"Medium"},{"name":"Meta"},{"name":"Feeds"}],"components":{"securitySchemes":{"oanorKey":{"type":"apiKey","in":"header","name":"x-oanor-key","description":"Get your key at https://www.oanor.com/developer/keys"}}},"security":[{"oanorKey":[]}],"paths":{"/v1/publication":{"get":{"operationId":"get_v1_publication","tags":["Medium"],"summary":"Publication posts","description":"","parameters":[{"name":"publication","in":"query","required":true,"description":"Publication slug","schema":{"type":"string"},"example":"better-programming"}],"security":[{"oanorKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"feed":{"link":"https://betterprogramming.pub?source=rss----d0b105d10f0a---4","image":"https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png","title":"Better Programming - Medium","description":"Advice for programmers. - Medium"},"count":10,"items":[{"guid":"https://medium.com/p/bf37baef8f27","link":"https://medium.com/better-programming/let-a-thousand-programming-publications-bloom-bf37baef8f27?source=rss----d0b105d10f0a---4","title":"Let a thousand programming publications bloom.","author":"Tony Stubblebine","excerpt":"I’m putting Better Programming on hiatus to make room for other programming publications. I get that this is a big pivot given that we switched to a new editor recently. But things are changing at Medium and I think this will ultimately be a boon for everyone, authors, readers and publications. I wo","updated":"2023-11-27T17:53:37.557Z","published":"Fri, 10 Nov 2023 18:18:10 GMT","categories":[],"content_html":"<h4>I’m putting Better Programming on hiatus to make room for other programming publications.</h4><p>I<em> get that this is a big pivot given that we switched to a new editor recently. But things are changing at Medium and I think this will ultimately be a boon for everyone, authors, readers and publications.</em></p><p>I would like to inspire some (but not all) of you to start a publication and give you some guidelines on how to do it well. If you are an author, there are many other publications to write for and hopefully there will soon be even more (check the comments for suggestions).</p><p>Medium has always had publications that acted as something in between a group blog and a sub-reddit. Publication editors help set a quality bar, give feedback on your posts, and bring you an audience. Publications are a pillar of the Medium experience.</p><p>But the publication opportunities that (I think) are exciting are changing. In the past, the way to have a successful publication was to publish on anything and everything. So Medium was dominated by broad, high volume publications. Better Programming was one of those pubs and we published on topics that might not have a lot of overlapping readers. How many of you are currently programming in all of these languages: Go, Rust, Javascript, Ruby, Python, Swift, Kotlin, and Dart?</p><p>Better Programming has published stories on all of those topics and more, and so by definition we were often publishing stories that a lot of you don’t want to read. The direction Medium is heading is to optimize for publications that are more focused than Better Programming has been.</p><p>There are two types of focuses that I’m personally excited about. One is that publications are de facto communities of enthusiasts. The other is that publications bring a level of expertise to Medium’s boost program. <em>Caveat: these are just what I’m excited about — maybe you have more creative ideas than I do.</em></p><p>Both cases beg for publications that are focused.</p><p>If you want to build an enthusiast community of people who love Kotlin, who want to write about their Kotlin projects and what they are learning, then you don’t also need authors in your publication who are writing about Swift.</p><p>Similarly, Medium is leaning on the expertise of publication editors to contribute as nominators in the Boost program. It’s hard to bring credible expertise when your focus is too broad. Most nominators also have first hand expertise beyond what they publish. So, if I were to run Better Programming myself, I think I could credibly nominate within Rails (I’ve built several companies on that stack) and Regular Expressions (I wrote a book), but I’m clueless on nearly everything else.</p><p>Running a publication isn’t for everyone and it isn’t a get-rich-quick scheme. The best publications are run out of authentic interest in a topic and nothing more. In technical topics, there can be some financial rewards, which I’ll get to. But mainly it’s best to think of this as a way to harness a passion you may have. I know that the community building impulse is strong in many of you because I’ve seen how many people have started publications on Medium over the years.</p><p>For any of you who are interested, I’m going to give you some tips on starting a publication. These aren’t exactly a recipe, but I’ll try to arrange them in order.</p><ol><li>Pubs are easy to start and at minimum you have yourself as a possible author to fill the pub with stories. <a href=\"https://medium.com/new-publication\">Here’s a link to get going</a>.</li><li>If you want to accept other authors then you need to setup instructions. Almost all publications that accept other authors setup a “write for us” page with instructions, make it a tab on the publication, write a style guide, and then create a Google form to handle new author applications. <a href=\"https://betterprogramming.pub/write-for-us-5c4bcba59397\">Copy ours</a>.</li><li>Do you want to focus on inclusivity? If so, then your role is probably more about support and encouragement and less about setting a high editorial bar. People get squeamish about being judged but the thing I’ve long recognized is that all writing was useful to the writer and is often useful to at least a few people, but very little writing is going to trend on Reddit or HackerNews.</li><li>Do you want to focus on exclusivity, i.e. finding the best of the best ideas and information on a topic? Medium’s Boost program gives publication editors a tool to recruit authors: “I can help boost your stories to more readers.” You can’t just boost anything, it has to be the best of the best. And so focusing on that is a very exclusive approach. I often think of a publication here about Runners where the editor is using his access to the Boost to work with professional running coaches, professional runners, and the former editor-in-chief of Runners World. That must be so fun for him! The programming equivalent is different for each programming languange so I’ll use an example from the language I got started in: if I started a publication for Perl, I’d use the boost as a way to recruit Larry Wall.</li><li>Consider becoming a Boost nominator but also consider that doing that will require having a strong nose for the best of the best. Of course every story on Medium is “high quality” but there are certain stories that are important, accurate, helpful and maybe even more than that. This isn’t official policy, but unofficially, it would be reasonable to <a href=\"https://blog.medium.com/a-nosy-faq-about-nominating-stories-for-our-boost-44bbf79549c\">submit an application to be a Boost Nominator</a> once you have a publication with three authors and ten stories.</li><li>Getting a publication started requires recruiting authors. Hopefully you know some already, even if they aren’t on Medium. I think that if you don’t know a subject well enough that you also already know other people with similar enthusiasm and expertise in that subject, then starting a publication isn’t for you. That’s not a hard rule, but I’m saying it from experience. After recruiting from your own network, the way almost every other publication has recruited authors is by monitoring relevant tags on Medium and then using the private note feature to invite recently published stories into your publication.</li><li>Lets talk money. If you are a publication that Boosts stories you will get paid an honorarium. Plus if you build an audience, your own stories might make more money. But, you are missing the big picture if this is the most important thing to you. Writing and editing is a form of portfolio building. The software engineering field pays so much money, way beyond what Medium pays for writing. So focusing on getting paid from Medium is the ultimate example of a local maxima because the you can make 1000x more by building a reputation and using it to get a job or raise. This is just fact.</li></ol><p>If you do start a programming publication that is looking for authors or you’ve already started a programming publication like that, post a link in the responses along with a link to your submission guidelines.</p><p>Authors: I looked up Better Programming’s stats. 4.6k authors have published 16.8k stories to Better Programming. Those stories generated 151M page views. Not all of them were behind the paywall, but the ones that were earned authors $999 thousand dollars. It’s been a huge honor to play a role in that and my thanks go out to the editors who’ve made it happen and to all of you for writing. Medium is still a great home for you, it’s just that you should find new places to publish.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*tKikPWjE4MZ5WgdfZuyvEg.png\" /></figure><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf37baef8f27\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/let-a-thousand-programming-publications-bloom-bf37baef8f27\">Let a thousand programming publications bloom.</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/3f456a51ff99","link":"https://medium.com/better-programming/calling-aws-bedrock-from-code-3f456a51ff99?source=rss----d0b105d10f0a---4","title":"Calling AWS Bedrock from code","author":"Thomas Reid","updated":"2023-11-10T17:35:02.467Z","published":"Fri, 10 Nov 2023 17:35:02 GMT","categories":["bedrock","python","llm","boto3","ai"]},{"guid":"https://medium.com/p/3e51432ae0e9","link":"https://medium.com/better-programming/pandas-v-psycopg-a-postgres-database-speed-test-who-wins-3e51432ae0e9?source=rss----d0b105d10f0a---4","title":"Pandas v Psycopg:","author":"Thomas Reid","updated":"2023-11-10T17:34:53.793Z","published":"Fri, 10 Nov 2023 17:34:53 GMT","categories":["speed-test","psycopg2","postgres","performance","pandas"]},{"guid":"https://medium.com/p/e1f66a3b0530","link":"https://medium.com/better-programming/automating-your-devops-writing-scripts-that-save-time-and-headaches-e1f66a3b0530?source=rss----d0b105d10f0a---4","title":"Automating Your DevOps: Writing Scripts that Save Time and Headaches","author":"Ulas Can Cengiz","updated":"2023-11-10T17:34:20.767Z","published":"Fri, 10 Nov 2023 17:34:20 GMT","categories":["software-development","development","devops","engineering","automation"]},{"guid":"https://medium.com/p/c5f79fc8019e","link":"https://medium.com/better-programming/integrating-psychology-into-software-development-c5f79fc8019e?source=rss----d0b105d10f0a---4","title":"Integrating Psychology into Software Development","author":"Ulas Can Cengiz","updated":"2023-11-10T17:34:07.833Z","published":"Fri, 10 Nov 2023 17:34:07 GMT","categories":["engineering","architecture","software-design","software-development","psychology"]},{"guid":"https://medium.com/p/ccbd1d3f9fd7","link":"https://medium.com/better-programming/gpt-function-calling-5-underrated-use-cases-ccbd1d3f9fd7?source=rss----d0b105d10f0a---4","title":"GPT Function Calling: 5 Underrated Use Cases","author":"Max Brodeur-Urbas","excerpt":"OpenAI’s backend converting messy unstructured data to structured data via functions OpenAI’s “Function Calling” might be the most groundbreaking yet under appreciated feature released by any software company… ever. What are GPT Functions Functions allow you to turn unstructured data into structured","updated":"2023-11-20T20:55:28.826Z","published":"Fri, 10 Nov 2023 17:33:58 GMT","categories":["automation","llm","gpt","chatgpt","openai"],"content_html":"<figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*doghMb44J43LgPYtkUCByw.jpeg\" /><figcaption>OpenAI’s backend converting messy unstructured data to structured data via functions</figcaption></figure><p>OpenAI’s “Function Calling” might be the most groundbreaking yet under appreciated feature released by any software company… ever.</p><h3>What are GPT Functions</h3><p>Functions allow you to <strong>turn unstructured data into structured data</strong>. This might not sound all that groundbreaking but when you consider that 90% of data processing and data entry jobs worldwide exist for this exact reason, it’s quite a revolutionary feature that went somewhat unnoticed.</p><p>Have you ever found yourself <em>begging</em> GPT (3.5 or 4) to spit out the answer you want and absolutely nothing else? No “Sure, here is your…” or any other useless fluff surrounding the core answer. GPT Functions are the solution you’ve been looking for.</p><h4>How are Functions meant to work?</h4><blockquote>OpenAI’s docs on function calling are extremely limited. You’ll find yourself digging through their developer forum for examples of how to use them. I dug around the forum for you and have many example coming up.</blockquote><p>Here’s one of the only examples you’ll be able to find in their docs:</p><pre>functions = [<br>        {<br>            &quot;name&quot;: &quot;get_current_weather&quot;,<br>            &quot;description&quot;: &quot;Get the current weather in a given location&quot;,<br>            &quot;parameters&quot;: {<br>                &quot;type&quot;: &quot;object&quot;,<br>                &quot;properties&quot;: {<br>                    &quot;location&quot;: {<br>                        &quot;type&quot;: &quot;string&quot;,<br>                        &quot;description&quot;: &quot;The city and state, e.g. San Francisco, CA&quot;,<br>                    },<br>                    &quot;unit&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;enum&quot;: [&quot;celsius&quot;, &quot;fahrenheit&quot;]},<br>                },<br>                &quot;required&quot;: [&quot;location&quot;],<br>            },<br>        }<br>    ]</pre><p>A function definition is a rigid JSON format that defines a function name, description and parameters. In this case, the function is meant to get the current weather. Obviously GPT isn’t able to call this actual API (since it doesn’t exist) but using this structured response you’d be able to connect the real API hypothetically.</p><p>At a high level however, functions provide two layers of inference:</p><h4>Picking the function itself:</h4><p>You may notice that functions are passed into the OpenAI API call as an array. The reason you provide a name and description to each function are so GPT can decide which to use based on a given prompt. Providing multiple functions in your API call is like giving GPT a Swiss army knife and asking it to cut a piece of wood in half. It knows that even though it has a pair of pliers, scissors and a knife, it should use the saw!</p><p><strong>Function definitions contribute towards your token count</strong>. Passing in hundreds of functions would not only take up the majority of your token limit but also result in a drop in response quality. I often don’t even use this feature and only pass in 1 function that I force it to use. It is very nice to have in certain use cases however.</p><h4>Picking the parameter values based on a prompt:</h4><p>This is the real magic in my opinion. GPT being able to choose the tool in it’s tool kit is amazing and definitely the focus of their feature announcement but I think this applies to more use cases.</p><p><strong>You can imagine a function like handing GPT a form to fill out</strong>. <strong>It uses its reasoning, the context of the situation and field names/descriptions to decide how it will fill out each field.</strong> Designing the form and the additional information you pass in is where you can get creative.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1020/1*YZEdqYdzo_LyCSK2YzQDrQ.png\" /><figcaption>GPT filling out your custom form (function parameters)</figcaption></figure><h3>5 Useful Applications</h3><h3>Data Extraction</h3><p>One of the most common things I use functions for to extract specific values from a large chunk of text. The sender’s address from an email, a founders name from a blog post, a phone number from a landing page.</p><p>I like to imagine I’m searching for a needle in a haystack except the LLM burns the haystack, leaving nothing but the needle(s).</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1023/1*nycFdIgLvj_RVyQ_J28r9A.png\" /><figcaption>GPT Data Extraction Personified.</figcaption></figure><h4>Use case: Processing thousands of contest submissions</h4><p>I built an automation that iterated over thousands of contest submissions. Before storing these in a Google sheet I wanted to extract the email associated with the submission. Heres the function call I used for extracting their email.</p><pre>{<br>   &quot;name&quot;:&quot;update_email&quot;,<br>   &quot;description&quot;:&quot;Updates email based on the content of their submission.&quot;,<br>   &quot;parameters&quot;:{<br>      &quot;type&quot;:&quot;object&quot;,<br>      &quot;properties&quot;:{<br>         &quot;email&quot;:{<br>            &quot;type&quot;:&quot;string&quot;,<br>            &quot;description&quot;:&quot;The email provided in the submission&quot;<br>         }<br>      },<br>      &quot;required&quot;:[<br>         &quot;email&quot;<br>      ]<br>   }<br>}</pre><h3>Scoring</h3><p>Assigning unstructured data a score based on dynamic, natural language criteria is a wonderful use case for functions. You could score comments during sentiment analysis, essays based on a custom grading rubric, a loan application for risk based on key factors. A recent use case I applied scoring to was scoring of sales leads from 0–100 based on their viability.</p><h4>Use Case: Scoring Sales leads</h4><p>We had hundreds of prospective leads in a single google sheet a few months ago that we wanted to tackle from most to least important. Each lead contained info like company size, contact name, position, industry etc.</p><p>Using the following function we scored each lead from 0–100 based on our needs and then sorted them from best to worst.</p><pre>{<br>   &quot;name&quot;:&quot;update_sales_lead_value_score&quot;,<br>   &quot;description&quot;:&quot;Updates the score of a sales lead and provides a justification&quot;,<br>   &quot;parameters&quot;:{<br>      &quot;type&quot;:&quot;object&quot;,<br>      &quot;properties&quot;:{<br>         &quot;sales_lead_value_score&quot;:{<br>            &quot;type&quot;:&quot;number&quot;,<br>            &quot;description&quot;:&quot;An integer value ranging from 0 to 100 that represents the quality of a sales lead based on these criteria. 100 is a perfect lead, 0 is terrible. Ideal Lead Criteria:\\n- Medium sized companies (300-500 employees is the best range)\\n- Companies in primary resource heavy industries are best, ex. manufacturing, agriculture, etc. (this is the most important criteria)\\n- The higher up the contact position, the better. VP or Executive level is preferred.&quot;<br>         },<br>         &quot;score_justification&quot;:{<br>            &quot;type&quot;:&quot;string&quot;,<br>            &quot;description&quot;:&quot;A clear and conscise justification for the score provided based on the custom criteria&quot;<br>         }<br>      }<br>   },<br>   &quot;required&quot;:[<br>      &quot;sales_lead_value_score&quot;, <br>      &quot;score_justification&quot;<br>   ]<br>}</pre><h3>Categorization:</h3><p>Define custom buckets and have GPT thoughtfully consider each piece of data you give it and place it in the correct bucket. This can be used for labelling tasks like selecting the category of youtube videos or for discrete scoring tasks like assigning letter grades to homework assignments.</p><h4><strong>Use Case: Labelling news articles.</strong></h4><p>A very common first step in data processing workflows is separating incoming data into different streams. A recent automation I built did exactly this with news articles scraped from the web. I wanted to sort them based on the topic of the article and include a justification for the decision once again. Here’s the function I used:</p><pre>{<br>   &quot;name&quot;:&quot;categorize&quot;,<br>   &quot;description&quot;:&quot;Categorize the input data into user defined buckets.&quot;,<br>   &quot;parameters&quot;:{<br>      &quot;type&quot;:&quot;object&quot;,<br>      &quot;properties&quot;:{<br>         &quot;category&quot;:{<br>            &quot;type&quot;:&quot;string&quot;,<br>            &quot;enum&quot;:[<br>               &quot;US Politics&quot;,<br>               &quot;Pandemic&quot;,<br>               &quot;Economy&quot;,<br>               &quot;Pop culture&quot;,<br>               &quot;Other&quot;<br>            ],<br>            &quot;description&quot;:&quot;US Politics: Related to US politics or US politicians, Pandemic: Related to the Coronavirus Pandemix, Economy: Related to the economy of a specific country or the world. , Pop culture: Related to pop culture, celebrity media or entertainment., Other: Doesn&#39;t fit in any of the defined categories. &quot;<br>         },<br>         &quot;justification&quot;:{<br>            &quot;type&quot;:&quot;string&quot;,<br>            &quot;description&quot;:&quot;A short justification explaining why the input data was categorized into the selected category.&quot;<br>         }<br>      },<br>      &quot;required&quot;:[<br>         &quot;category&quot;,<br>         &quot;justification&quot;<br>      ]<br>   }<br>}</pre><h3>Option-Selection:</h3><p>Often times when processing data, I give GPT many possible options and want it to select the best one based on my needs. I only want the value it selected, no surrounding fluff or additional thoughts. Functions are perfect for this.</p><h4>Use Case: Finding the “most interesting AI news story” from hacker news</h4><blockquote>I wrote another <a href=\"https://medium.com/better-programming/building-an-autonomous-twitter-account-with-llms-de53f5e519ba\">medium article here</a> about how I automated my entire Twitter account with GPT. Part of that process involves selecting the most relevant posts from the front pages of hacker news. This post selection step leverages functions!</blockquote><p>To summarize the functions portion of the use case, we would scrape the first n pages of hacker news and ask GPT to select the post most relevant to “AI news or tech news”. GPT would return only the headline and the link selected via functions so that I could go on to scrape that website and generate a tweet from it.</p><p>I would pass in the user defined query as part of the message and use the following function definition:</p><pre>{<br>   &quot;name&quot;:&quot;find_best_post&quot;,<br>   &quot;description&quot;:&quot;Determine the best post that most closely reflects the query.&quot;,<br>   &quot;parameters&quot;:{<br>      &quot;type&quot;:&quot;object&quot;,<br>      &quot;properties&quot;:{<br>         &quot;best_post_title&quot;:{<br>            &quot;type&quot;:&quot;string&quot;,<br>            &quot;description&quot;:&quot;The title of the post that most closely reflects the query, stated exactly as it appears in the list of titles.&quot;<br>         }<br>      },<br>      &quot;required&quot;:[<br>         &quot;best_post_title&quot;<br>      ]<br>   }<br>}</pre><h3>Filtering:</h3><p>Filtering is a subset of categorization where you categorize items as either true or false based on a natural language condition. A condition like “is Spanish” will be able to filter out all Spanish comments, articles etc. using a simple function and conditional statement immediately after.</p><h4>Use Case: Filtering contest submission</h4><p>The same automation that I mentioned in the “Data Extraction” section used ai-powered-filtering to weed out contest submissions that didn’t meet the deal-breaking criteria. Things like “must use typescript” were absolutely mandatory for the coding contest at hand. We used functions to filter out submissions and trim down the total set being processed by 90%. Here is the function definition we used.</p><pre>{<br>   &quot;name&quot;:&quot;apply_condition&quot;,<br>   &quot;description&quot;:&quot;Used to decide whether the input meets the user provided condition.&quot;,<br>   &quot;parameters&quot;:{<br>      &quot;type&quot;:&quot;object&quot;,<br>      &quot;properties&quot;:{<br>         &quot;decision&quot;:{<br>            &quot;type&quot;:&quot;string&quot;,<br>            &quot;enum&quot;:[<br>               &quot;True&quot;,<br>               &quot;False&quot;<br>            ],<br>            &quot;description&quot;:&quot;True if the input meets this condition &#39;Does submission meet the ALL these requirements (uses typescript, uses tailwindcss, functional demo)&#39;, False otherwise.&quot;<br>         }<br>      },<br>      &quot;required&quot;:[<br>         &quot;decision&quot;<br>      ]<br>   }<br>}</pre><p>If you’re curious why I love functions so much or what I’ve built with them you should check out <a href=\"https://www.agenthub.dev/\">AgentHub</a>!</p><p><strong>AgentHub is the </strong><a href=\"https://www.ycombinator.com/launches/JcG-agenthub-automate-any-workflow-with-ai\"><strong>Y Combinator-backed startup</strong></a><strong> I co-founded that let’s you automate any repetitive or complex workflow with AI via a simple drag and drop no-code platform.</strong></p><blockquote>“Imagine Zapier but AI-first and on crack.” — Me</blockquote><p>Automations are built with individual nodes called “Operators” that are linked together to create power AI pipelines. We have a catalogue of AI powered operators that leverage functions under the hood.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*U3Cqe59WNC6mFQn3-3O26A.png\" /><figcaption>Our current AI-powered operators that use functions!</figcaption></figure><p>Check out these templates to see examples of function use-cases on AgentHub: <a href=\"https://www.agenthub.dev/templates/sales_crm/sales_leads_scoring_model\">Scoring</a>, <a href=\"https://www.agenthub.dev/templates/media_news/news_story_categorizer\">Categorization</a>, <a href=\"https://www.agenthub.dev/templates/media_news/autonomous_twitter_bot\">Option-Selection</a>,</p><p>If you want to start building <a href=\"https://www.agenthub.dev/\">AgentHub</a> is live and ready to use! We’re very active in our <a href=\"https://discord.gg/SyCVmrzhCc\">discord community</a> and are happy to help you build your automations if needed.</p><p>Feel free to follow the <a href=\"https://twitter.com/AgentHub_AI\">official AgentHub twitter</a> for updates and <a href=\"https://twitter.com/MaxBrodeurUrbas\">myself for AI-related content.</a></p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ccbd1d3f9fd7\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/gpt-function-calling-5-underrated-use-cases-ccbd1d3f9fd7\">GPT Function Calling: 5 Underrated Use Cases</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/8d81fccd8250","link":"https://medium.com/better-programming/webassembly-with-go-taking-web-apps-to-the-next-level-8d81fccd8250?source=rss----d0b105d10f0a---4","title":"WebAssembly with Go: Taking Web Apps to the Next Level","author":"Ege Aytin","excerpt":"You might’ve noticed the increasing chatter around WebAssembly (WASM) in the dev community. Its potential is vast, and we’ve found it invaluable in enhancing our open source project! Hi everyone, I’m part of the team behind Permify , an open-source infra that helps developers to create and manage gr","updated":"2023-11-21T12:18:43.843Z","published":"Fri, 10 Nov 2023 17:31:06 GMT","categories":["javascript","golang","tutorial"],"content_html":"<figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1000/1*b4sFFT3X6e0dVCvKAAtMIg.png\" /></figure><p>You might’ve noticed the increasing chatter around <a href=\"https://webassembly.org/\">WebAssembly (WASM)</a> in the dev community. Its potential is vast, and we’ve found it invaluable in enhancing our open source project!</p><p>Hi everyone, I’m part of the team behind <a href=\"https://github.com/Permify/permify\">Permify</a>, an open-source infra that helps developers to create and manage granular permissions throughout their applications.</p><p>In this article, I’ll demonstrate why and how we integrated WebAssembly (WASM) into our <a href=\"https://play.permify.co/\">Playground</a> and gained benefits from its collaboration with Golang.</p><p>What does this playground do? Well, without diving too deep, its a interactive module of Permify which used for creating and testing authorization models.</p><p>Throughout this post, I’ll be sharing:</p><ul><li><strong><em>A brief explanation of WASM and the benefits of using it with Go.</em></strong></li><li><strong><em>A peek into what spurred our choice to integrate WASM in Permify.</em></strong></li><li><strong><em>WASM Implementation, including</em></strong></li><li><strong><em>Quick Warm Up: WASM Implementation with Go</em></strong></li><li><strong><em>Deeper Dive: Permify’s WASM Code Breakdown</em></strong></li><li><strong><em>Frontend: Steps to Embed Go WASM in a React Application</em></strong></li></ul><p>By the end, you should have a clearer understanding of why and how we leveraged WASM’s capabilities for our project.</p><h3>Understanding WebAssembly</h3><p>WebAssembly (Wasm) has established itself as a pivotal technology, enabling quick and efficient code execution in web browsers and forming a robust bridge between web applications and the high-performance typically associated with native applications.</p><h3>1. Unveiling WebAssembly:</h3><p>Wasm acts as a low-level virtual machine, executing a compact binary code that’s translated from high-level languages.</p><p>Primary Advantages:</p><ul><li>Universal Browser Support: Thanks to its support from all major browsers, Wasm delivers consistent performance across diverse platforms.</li><li>Near-Native Performance: Intentionally designed to execute binary code at a speed akin to native applications, Wasm enhances the responsiveness of web applications considerably.</li></ul><p>In our open-source project, Permify, we strategically incorporated Go (also known as Golang) into its foundational core, selecting it for its widely recognized static typing, concurrency handling, and performance optimization. When the development journey led us to craft the Permify Playground, WebAssembly stepped into the spotlight as a crucial element.</p><h3>2. Blending Go &amp; WebAssembly:</h3><ul><li>Characteristics of Go: Celebrated for its optimal performance and concurrency handling capabilities, Go has carved a sturdy standing within the developer community.</li><li>Synergy with WebAssembly: The translation of Go code into WebAssembly enables developers to effectively utilize Go’s robust performance and concurrency management directly within the browser, propelling the creation of powerful, efficient, and scalable web applications.</li></ul><p>Our journey isn’t just about melding Go and WebAssembly. Moving forward, we’ll unearth why Wasm was pinpointed as the technology of choice for the Permify Playground development and what significant benefits were reaped from this decision.</p><h3>Why WebAssembly?</h3><p>The inception of the Permify Playground brought with it a key question: How to showcase our capabilities without being entwined in the complexities and maintenance woes of traditional server architectures? WebAssembly appeared as a shining answer. Adopting this binary instruction format allowed us to:</p><ul><li>Execute In-Browser: Permify’s playground could operate straight within the browser, sidestepping server maintenance overheads and repetitive API calls, and notably, making ongoing maintenance a breeze in comparison to older server-based approaches.</li><li>Achieve Peak Performance: Employing WebAssembly ensures that our Go application operates with a level of performance that competes with native applications, enhancing user interactions and bolstering response times.</li></ul><h3>Harvesting Technical Benefits and Gathering User Feedback</h3><p>Utilizing WebAssembly in our Permify Playground led us down a path of discernible technical advantages and an embrace from the community:</p><ul><li>Swift Execution: By side-stepping server interactions and deploying WebAssembly in-browser, we’ve been able to deliver ultra-fast response times.</li><li>Uncomplicated User Interface: Centralizing our playground in the browser, we’ve dispelled complexities associated with multi-tool workflows, delivering a clean and straightforward user experience.</li><li>Community Validation: The affirming feedback and positive reception from the developer community stand as validation of our technological choices and implementations.</li></ul><p>Join us in the following sections as we delve deeper into the technicalities, feedback, and learnings from our adventure, providing a thorough exploration of our endeavours with WebAssembly.</p><h3>WASM Implementation with Go</h3><p>Before we explore Permify’s use of WebAssembly (WASM) and Go, let’s understand their combination in a sample app. What follows is a step-by-step guide to bringing them together, setting the stage for our deeper dive into Permify’s implementation.</p><h3>1. Transforming Go into WebAssembly:</h3><ul><li><strong>Steps:</strong></li></ul><ol><li>To get started, ensure you’ve set the WebAssembly build target in Go:</li></ol><pre>GOOS=js GOARCH=wasm go build -o main.wasm main.go</pre><ol><li>Next, apply optimizations to reduce the file size and enhance performance:</li></ol><pre>wasm-opt main.wasm --enable-bulk-memory -Oz -o play.wasm</pre><ul><li><strong>Handling Events:</strong></li></ul><p>Suppose you want your Go function to react to a button click from your web page:</p><pre>package main<br><br>import &quot;syscall/js&quot;<br><br>func registerCallbacks() {<br>    js.Global().Set(&quot;handleClick&quot;, js.FuncOf(handleClick))<br>}<br><br>func handleClick(this js.Value, inputs []js.Value) interface{} {<br>    println(&quot;Button clicked!&quot;)<br>    return nil<br>}</pre><p>In your HTML, after loading your WebAssembly module:</p><pre>&lt;button onclick=&quot;window.handleClick()&quot;&gt;Click me&lt;/button&gt;</pre><h3>2. Integrating with Web Pages:</h3><ul><li><strong>Initializing Wasm:</strong></li></ul><p>Ensure you have the wasm_exec.js script linked, then instantiate your Wasm module:</p><pre>&lt;script src=&quot;wasm_exec.js&quot;&gt;&lt;/script&gt;<br>&lt;script&gt;<br>    const go = new Go();<br>    WebAssembly.instantiateStreaming(fetch(&quot;play.wasm&quot;), go.importObject).then((result) =&gt; {<br>        go.run(result.instance);<br>    });<br>&lt;/script&gt;</pre><ul><li><strong>Interacting with the DOM:</strong></li></ul><p>Accessing and modifying web elements is fundamental. For instance, changing the content of a paragraph element from Go would look something like this:</p><pre>func updateDOMContent() {<br>    document := js.Global().Get(&quot;document&quot;)<br>    element := document.Call(&quot;getElementById&quot;, &quot;myParagraph&quot;)<br>    element.Set(&quot;innerText&quot;, &quot;Updated content from Go!&quot;)<br>}</pre><h3>3. The Gains: Efficiency &amp; Speed:</h3><ul><li><strong>Go’s Goroutines in the Browser:</strong></li></ul><p>Imagine having multiple data fetch operations that can run simultaneously without blocking the main thread:</p><pre>func fetchData(url string, ch chan string) {<br>    // Simulate data fetch.<br>    ch &lt;- &quot;Data from &quot; + url<br>}<br><br>func main() {<br>    ch := make(chan string)<br>    go fetchData(&quot;&lt;https://api.example1.com&gt;&quot;, ch)<br>    go fetchData(&quot;&lt;https://api.example2.com&gt;&quot;, ch)<br><br>    data1 := &lt;-ch<br>    data2 := &lt;-ch<br>    println(data1, data2)<br>}</pre><p>Navigating through Go and WebAssembly (WASM) showcases a powerful union, merging Go’s concurrent processing with WASM’s rapid client-side execution. The depth explored in our sample app lights the way forward into Permify, where we apply these technological strengths into a scalable, real-world authorization system.</p><h3>Deeper Dive: Permify’s WASM Code Breakdown</h3><p>Let’s dive a bit deeper into the heart of our WebAssembly integration by exploring the key segments of our Go-based WASM code.</p><h3>1. Setting up the Go-to-WASM Environment</h3><p>involves preparing and specifying our Go code to be compiled for a WebAssembly runtime.</p><pre>// go:build wasm<br>// +build wasm</pre><p>These lines serve as directives to the Go compiler, signaling that the following code is designated for a WebAssembly runtime environment. Specifically:</p><ul><li>//go:build wasm: A build constraint ensuring the code is compiled only for WASM targets, adhering to modern syntax.</li><li>// +build wasm: An analogous constraint, utilizing older syntax for compatibility with prior Go versions.</li></ul><p>In essence, these directives guide the compiler to include this code segment only when compiling for a WebAssembly architecture, ensuring an appropriate setup and function within this specific runtime.</p><h3>2. Bridging JavaScript and Go with the run Function</h3><pre>package main<br><br>import (<br> &quot;context&quot;<br> &quot;encoding/json&quot;<br> &quot;syscall/js&quot;<br><br> &quot;google.golang.org/protobuf/encoding/protojson&quot;<br><br> &quot;github.com/Permify/permify/pkg/development&quot;<br>)<br><br>var dev *development.Development<br><br>func run() js.Func {<br>  // The `run` function returns a new JavaScript function<br>  // that wraps the Go function.<br> return js.FuncOf(func(this js.Value, args []js.Value) interface{} {<br><br>  // t will be used to store the unmarshaled JSON data.<br>  // The use of an empty interface{} type means it can hold any type of value.<br>  var t interface{}<br><br>  // Unmarshal JSON from JavaScript function argument (args[0]) to Go&#39;s data structure (map).<br>  // args[0].String() gets the JSON string from the JavaScript argument,<br>  // which is then converted to bytes and unmarshaled (parsed) into the map `t`.<br>  err := json.Unmarshal([]byte(args[0].String()), &amp;t)<br><br>  // If an error occurs during unmarshaling (parsing) the JSON,<br>  // it returns an array with the error message &quot;invalid JSON&quot; to JavaScript.<br>  if err != nil {<br>   return js.ValueOf([]interface{}{&quot;invalid JSON&quot;})<br>  }<br><br>  // Attempt to assert that the parsed JSON (`t`) is a map with string keys.<br>  // This step ensures that the unmarshaled JSON is of the expected type (map).<br>  input, ok := t.(map[string]interface{})<br><br>  // If the assertion is false (`ok` is false),<br>  // it returns an array with the error message &quot;invalid JSON&quot; to JavaScript.<br>  if !ok {<br>   return js.ValueOf([]interface{}{&quot;invalid JSON&quot;})<br>  }<br><br>  // Run the main logic of the application with the parsed input.<br>  // It’s assumed that `dev.Run` processes `input` in some way and returns any errors encountered during that process.<br>  errors := dev.Run(context.Background(), input)<br><br>  // If no errors are present (the length of the `errors` slice is 0),<br>  // return an empty array to JavaScript to indicate success with no errors.<br>  if len(errors) == 0 {<br>   return js.ValueOf([]interface{}{})<br>  }<br><br>  // If there are errors, each error in the `errors` slice is marshaled (converted) to a JSON string.<br>  // `vs` is a slice that will store each of these JSON error strings.<br>  vs := make([]interface{}, 0, len(errors))<br><br>  // Iterate through each error in the `errors` slice.<br>  for _, r := range errors {<br>   // Convert the error `r` to a JSON string and store it in `result`.<br>   // If an error occurs during this marshaling, it returns an array with that error message to JavaScript.<br>   result, err := json.Marshal(r)<br>   if err != nil {<br>    return js.ValueOf([]interface{}{err.Error()})<br>   }<br>   // Add the JSON error string to the `vs` slice.<br>   vs = append(vs, string(result))<br>  }<br><br>  // Return the `vs` slice (containing all JSON error strings) to JavaScript.<br>  return js.ValueOf(vs)<br> })<br>}</pre><p>Within the realm of Permify, the run function stands as a cornerstone, executing a crucial bridging operation between JavaScript inputs and Go&#39;s processing capabilities. It orchestrates real-time data interchange in JSON format, safeguarding that Permify&#39;s core functionalities are smoothly and instantaneously accessible via a browser interface.</p><p>Digging into run:</p><ul><li>JSON Data Interchange: Translating JavaScript inputs into a format utilizable by Go, the function unmarshals JSON, transferring data between JS and Go, assuring that the robust processing capabilities of Go can seamlessly manipulate browser-sourced inputs.</li><li>Error Handling: Ensuring clarity and user-awareness, it conducts meticulous error-checking during data parsing and processing, returning relevant error messages back to the JavaScript environment to ensure user-friendly interactions.</li><li>Contextual Processing: By employing dev.Run, it processes the parsed input within a certain context, managing application logic while handling potential errors to assure steady data management and user feedback.</li><li>Bidirectional Communication: As errors are marshaled back into JSON format and returned to JavaScript, the function ensures a two-way data flow, keeping both environments in synchronized harmony.</li></ul><p>Thus, through adeptly managing data, error-handling, and ensuring a fluid two-way communication channel, run serves as an integral bridge, linking JavaScript and Go to ensure the smooth, real-time operation of Permify within a browser interface. This facilitation of interaction not only heightens user experience but also leverages the respective strengths of JavaScript and Go within the Permify environment.</p><h3>3. Main Execution and Initialization</h3><pre>// Continuing from the previously discussed code...<br><br>func main() {<br>  // Instantiate a channel, &#39;ch&#39;, with no buffer, acting as a synchronization point for the goroutine.<br>  ch := make(chan struct{}, 0)<br><br>  // Create a new instance of &#39;Container&#39; from the &#39;development&#39; package and assign it to the global variable &#39;dev&#39;.<br>  dev = development.NewContainer()<br><br>  // Attach the previously defined &#39;run&#39; function to the global JavaScript object,<br>  // making it callable from the JavaScript environment.<br>  js.Global().Set(&quot;run&quot;, run())<br><br>  // Utilize a channel receive expression to halt the &#39;main&#39; goroutine, preventing the program from terminating.<br>  &lt;-ch<br>}</pre><ol><li>ch := make(chan struct{}, 0): A synchronization channel is created to coordinate the activity of goroutines (concurrent threads in Go).</li><li>dev = development.NewContainer(): Initializes a new container instance from the development package and assigns it to dev.</li><li>js.Global().Set(&quot;run&quot;, run()): Exposes the Go run function to the global JavaScript context, enabling JavaScript to call Go functions.</li><li>&lt;-ch: Halts the main goroutine indefinitely, ensuring that the Go WebAssembly module remains active in the JavaScript environment.</li></ol><p>In summary, the code establishes a Go environment running within WebAssembly that exposes specific functionality (run function) to the JavaScript side and keeps itself active and available for function calls from JavaScript.</p><h3>Building the Go Code into a WASM Module</h3><p>Before we delve into Permify’s rich functionalities, it’s paramount to elucidate the steps of converting our Go code into a WASM module, priming it for browser execution.</p><p>For enthusiasts eager to delve deep into the complete Go codebase, don’t hesitate to browse our GitHub repository: <a href=\"https://github.com/Permify/permify/tree/master/pkg/development\">Permify Wasm Code</a>.</p><h3>1. Compiling to WASM</h3><p>Kickstart the transformation of our Go application into a WASM binary with this command:</p><pre>GOOS=js GOARCH=wasm go build -o permify.wasm main.go</pre><p>This directive cues the Go compiler to churn out a .wasm binary attuned for JavaScript environments, with main.go as the source. The output, permify.wasm, is a concise rendition of our Go capabilities, primed for web deployment.</p><h3>2. WASM Exec JS</h3><p>In conjunction with the WASM binary, the Go ecosystem offers an indispensable JavaScript piece named wasm_exec.js. It&#39;s pivotal for initializing and facilitating our WASM module within a browser setting. You can typically locate this essential script inside the Go installation, under misc/wasm.</p><p>However, to streamline your journey, we’ve hosted wasm_exec.js right here for direct access: <a href=\"https://github.com/Permify/permify/tree/master/playground/src/loadWasm\">wasm_exec</a>.</p><pre>cp &quot;$(go env GOROOT)/misc/wasm/wasm_exec.js&quot; .</pre><p>Equipped with these pivotal assets — the WASM binary and its companion JavaScript — the stage is set for its amalgamation into our frontend.</p><h3>Steps to Embed Go WASM in a React Application</h3><h3>1. Setting Up the React Application Structure</h3><p>To kick things off, ensure you have a directory structure that clearly separates your WebAssembly-related code from the rest of your application. Based on your given structure, the loadWasm folder seems to be where all the magic happens:</p><pre>loadWasm/<br>│<br>├── index.tsx            // Your main React component that integrates WASM.<br>├── wasm_exec.js         // Provided by Go, bridges the gap between Go&#39;s WASM and JS.<br>└── wasmTypes.d.ts       // TypeScript type declarations for WebAssembly.</pre><p>To view the complete structure and delve into the specifics of each file, refer to the <a href=\"https://github.com/Permify/permify/tree/master/playground/src/loadWasm\">Permify Playground on GitHub</a>.</p><h3>2. Establishing Type Declarations</h3><p>Inside the wasmTypes.d.ts, global type declarations are made which expand upon the Window interface to acknowledge the new methods brought in by Go&#39;s WebAssembly:</p><pre>declare global {<br>  export interface Window {<br>    Go: any;<br>    run: (shape: string) =&gt; any[];<br>  }<br>}<br>export {};</pre><p>This ensures TypeScript recognizes the Go constructor and the run method when called on the global window object.</p><h3>3. Preparing the WebAssembly Loader</h3><p>In index.tsx, several critical tasks are accomplished:</p><ul><li><strong>Import Dependencies:</strong> First off, we import the required JS and TypeScript declarations:</li></ul><pre>import &quot;./wasm_exec.js&quot;;<br>import &quot;./wasmTypes.d.ts&quot;;</pre><ul><li><strong>WebAssembly Initialization:</strong> The asynchronous function loadWasm takes care of the entire process:</li></ul><pre>async function loadWasm(): Promise&lt;void&gt; {<br>  const goWasm = new window.Go();<br>  const result = await WebAssembly.instantiateStreaming(<br>    fetch(&quot;play.wasm&quot;),<br>    goWasm.importObject<br>  );<br>  goWasm.run(result.instance);<br>}</pre><p>Here, new window.Go() initializes the Go WASM environment. WebAssembly.instantiateStreaming fetches the WASM module, compiles it, and creates an instance. Finally, goWasm.run activates the WASM module.</p><ul><li><strong>React Component with Loader UI:</strong> The LoadWasm component uses the useEffect hook to asynchronously load the WebAssembly when the component mounts:</li></ul><pre>export const LoadWasm: React.FC&lt;React.PropsWithChildren&lt;{}&gt;&gt; = (props) =&gt; {<br>  const [isLoading, setIsLoading] = React.useState(true);<br><br>  useEffect(() =&gt; {<br>    loadWasm().then(() =&gt; {<br>      setIsLoading(false);<br>    });<br>  }, []);<br><br>  if (isLoading) {<br>    return (<br>      &lt;div className=&quot;wasm-loader-background h-screen&quot;&gt;<br>        &lt;div className=&quot;center-of-screen&quot;&gt;<br>          &lt;SVG src={toAbsoluteUrl(&quot;/media/svg/rocket.svg&quot;)} /&gt;<br>        &lt;/div&gt;<br>      &lt;/div&gt;<br>    );<br>  } else {<br>    return &lt;React.Fragment&gt;{props.children}&lt;/React.Fragment&gt;;<br>  }<br>};</pre><p>While loading, SVG rocket is displayed to indicate that initialization is ongoing. This feedback is crucial as users might otherwise be uncertain about what’s transpiring behind the scenes. Once loading completes, children components or content will render.</p><h3>4. Calling WebAssembly Functions</h3><p>Given your Go WASM exposes a method named run, you can invoke it as follows:</p><pre>function Run(shape) {<br>  return new Promise((resolve) =&gt; {<br>    let res = window.run(shape);<br>    resolve(res);<br>  });<br>}</pre><p>This function essentially acts as a bridge, allowing the React frontend to communicate with the Go backend logic encapsulated in the WASM.</p><h3>5. Implementing the Run Button in React</h3><p>To integrate a button that triggers the WebAssembly function when clicked, follow these steps:</p><ol><li><strong>Creating the Button Component</strong></li></ol><p>First, we’ll create a simple React component with a button:</p><pre>import React from &quot;react&quot;;<br><br>type RunButtonProps = {<br>  shape: string;<br>  onResult: (result: any[]) =&gt; void;<br>};<br><br>function RunButton({ shape, onResult }: RunButtonProps) {<br>  const handleClick = async () =&gt; {<br>    let result = await Run(shape);<br>    onResult(result);<br>  };<br><br>  return &lt;button onClick={handleClick}&gt;Run WebAssembly&lt;/button&gt;;<br>}</pre><p>In the code above, the RunButton component accepts two props:</p><ul><li>shape: The shape argument to pass to the WebAssembly run function.</li><li>onResult: A callback function that receives the result of the WebAssembly function and can be used to update the state or display the result in the UI.</li></ul><ol><li>Integrating the Button in the Main Component</li></ol><p>Now, in your main component (or wherever you’d like to place the button), integrate the RunButton:</p><pre>import React, { useState } from &quot;react&quot;;<br>import RunButton from &quot;./path_to_RunButton_component&quot;; // Replace with the actual path<br><br>function App() {<br>  const [result, setResult] = useState&lt;any[]&gt;([]);<br><br>  // Define the shape content<br>  const shapeContent = {<br>    schema: `|-<br>    entity user {}<br><br>    entity account {<br>        relation owner @user<br>        relation following @user<br>        relation follower @user<br><br>        attribute public boolean<br>        action view = (owner or follower) or public  <br>    }<br><br>    entity post {<br>        relation account @account<br><br>        attribute restricted boolean<br><br>        action view = account.view<br><br>        action comment = account.following not restricted<br>        action like = account.following not restricted<br>    }`,<br>    relationships: [<br>      &quot;account:1#owner@user:kevin&quot;,<br>      &quot;account:2#owner@user:george&quot;,<br>      &quot;account:1#following@user:george&quot;,<br>      &quot;account:2#follower@user:kevin&quot;,<br>      &quot;post:1#account@account:1&quot;,<br>      &quot;post:2#account@account:2&quot;,<br>    ],<br>    attributes: [<br>      &quot;account:1$public|boolean:true&quot;,<br>      &quot;account:2$public|boolean:false&quot;,<br>      &quot;post:1$restricted|boolean:false&quot;,<br>      &quot;post:2$restricted|boolean:true&quot;,<br>    ],<br>    scenarios: [<br>      {<br>        name: &quot;Account Viewing Permissions&quot;,<br>        description:<br>          &quot;Evaluate account viewing permissions for &#39;kevin&#39; and &#39;george&#39;.&quot;,<br>        checks: [<br>          {<br>            entity: &quot;account:1&quot;,<br>            subject: &quot;user:kevin&quot;,<br>            assertions: {<br>              view: true,<br>            },<br>          },<br>        ],<br>      },<br>    ],<br>  };<br><br>  return (<br>    &lt;div&gt;<br>      &lt;RunButton shape={JSON.stringify(shapeContent)} onResult={setResult} /&gt;<br>      &lt;div&gt;<br>        Results:<br>        &lt;ul&gt;<br>          {result.map((item, index) =&gt; (<br>            &lt;li key={index}&gt;{item}&lt;/li&gt;<br>          ))}<br>        &lt;/ul&gt;<br>      &lt;/div&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>In this example, App is a component that contains the RunButton. When the button is clicked, the result from the WebAssembly function is displayed in a list below the button.</p><h3>Conclusion</h3><p>Throughout this exploration, the integration of WebAssembly with Go was unfolded, illuminating the pathway toward enhanced web development and optimal user interactions within browsers.</p><p>The journey involved setting up the Go environment, converting Go code to WebAssembly, and executing it within a web context, ultimately giving life to the interactive platform showcased at <a href=\"https://play.permify.co/\">play.permify.co</a>.</p><p>This platform stands not only as an example but also as a beacon, illustrating the concrete and potent capabilities achievable when intertwining these technological domains.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8d81fccd8250\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/webassembly-with-go-taking-web-apps-to-the-next-level-8d81fccd8250\">WebAssembly with Go: Taking Web Apps to the Next Level</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/3ee5d41f81a4","link":"https://medium.com/better-programming/3-fundamental-concepts-to-fully-understand-how-the-fetch-api-works-3ee5d41f81a4?source=rss----d0b105d10f0a---4","title":"3 Fundamental Concepts to Fully Understand how the Fetch API Works","author":"Jay Cruz","updated":"2023-11-10T17:30:24.902Z","published":"Fri, 10 Nov 2023 17:30:24 GMT","categories":["fetch-api","software-development","javascript","web-development","programming"]},{"guid":"https://medium.com/p/48809a853fae","link":"https://medium.com/better-programming/deploy-coreml-models-on-the-server-with-vapor-48809a853fae?source=rss----d0b105d10f0a---4","title":"Deploy CoreML Models on the Server with Vapor","author":"Drew Althage","excerpt":"Get the benefits of Apple’s ML tools server-side. SwiftUI client showing image classification results Recently, at Sovrn , we had an AI Hackathon where we were encouraged to experiment with anything related to machine learning. The Hackathon yielded some fantastic projects from across the company. E","updated":"2023-11-10T17:30:15.819Z","published":"Fri, 10 Nov 2023 17:30:15 GMT","categories":["swift","programming","ios","machine-learning","apple"],"content_html":"<p>Get the benefits of Apple’s ML tools server-side.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*XMjKfWYZYPikQ0pJ391YaA.png\" /><figcaption>SwiftUI client showing image classification results</figcaption></figure><p>Recently, at <a href=\"https://www.sovrn.com/\">Sovrn</a>, we had an AI Hackathon where we were encouraged to experiment with anything related to machine learning. The Hackathon yielded some fantastic projects from across the company. Everything from SQL query generators to chatbots that can answer questions about our products and other incredible work. I thought this would be a great opportunity to learn more about Apple’s ML tools and maybe even build something with real business value.</p><p>A few of my colleagues and I teamed up to play with CreateML and CoreML to see if we could integrate some ML functionality into our iOS app. We got a model trained and integrated into our app in several hours, which was pretty amazing. But we quickly realized that we had a few problems to solve before we could actually ship this thing.</p><ul><li>The model was hefty. It was about 50MB. That’s a lot of space to take up in our app bundle.</li><li>We wanted to update the model without releasing a new app version.</li><li>We wanted to use the model in the web browser as well.</li></ul><p>We didn’t have time to solve all of these problems. But the other day I was exploring the <a href=\"https://vapor.codes/\">Vapor</a> web framework and the thought hit me, “Why not deploy CoreML models on the server?”</p><p>Apple provides a few pre-trained models, so today we’ll deploy an image classification model on the server behind a REST API with Vapor and create a SwiftUI client to consume it.</p><h3>Foreword</h3><p>This prototype is just that, a prototype. It’s not meant to be a production-ready solution. It’s meant to be a proof of concept. There will be warnings in the console, and the code won’t be very clean, but it will work and hopefully get your wheels turning.</p><p>If you want to skip all this, or if you do want to follow along, you can find the source code for this project on <a href=\"https://github.com/drewalth/coreml-web-api\">GitHub</a>.</p><p>Okay, disclaimers over. Let’s get started!</p><h3>Requirements</h3><ul><li>Xcode 15</li><li>macOS 14</li><li>Homebrew</li><li>Apple Developer Account + Physical Device for testing</li></ul><h3>Getting Started</h3><p>First start by creating a new directory that will house our Xcode workspace. We’ll call it coreml-web-api .</p><pre>cd ~/Desktop &amp;&amp; mkdir coreml-web-api &amp;&amp; cd coreml-web-api</pre><p>Now let&#39;s install Vapor and bootstrap a brand new server. See <a href=\"https://docs.vapor.codes/\">the docs</a> for more details.</p><pre>brew install vapor<br>vapor new server -n<br>open Package.swift</pre><p>We want our users to be able to upload images for classification so add a new route called classify that supports this. In server/Sources/App/routes.swift , clear out all that generated boilerplate, and add in the following:</p><pre>import CoreImage<br>import Vapor<br><br>func routes(_ app: Application) throws {<br>    app.post(&quot;classify&quot;) { req -&gt; [ClassifierResult] in<br>        let classificationReq = try req.content.decode(ClassificationRequest.self)<br>        let imageBuffer = classificationReq.file.data<br>        guard let fileData = imageBuffer.getData(at: imageBuffer.readerIndex, length: imageBuffer.readableBytes),<br>              let ciImage = CIImage(data: fileData)<br>        else {<br>            throw Errors.badImageData<br>        }<br><br>        let classifier = Classifier() // we&#39;ll add this in a sec<br><br>        return try classifier.classify(image: ciImage)<br>    }<br>}<br><br>enum Errors: Error {<br>    case badImageData // or whatever<br>}<br><br>struct ClassificationRequest: Content {<br>    var file: File<br>}</pre><p>Also, bump up the max file size allowed for uploads in configure.swift :</p><pre>import Vapor<br><br>// configures your application<br>public func configure(_ app: Application) async throws {<br>    app.routes.defaultMaxBodySize = &quot;10mb&quot;<br><br>    // register routes<br>    try routes(app)<br>}</pre><p>Alright, now let&#39;s write up a Classifier API. First, head over to <a href=\"https://developer.apple.com/machine-learning/models/\">Apple’s ML page</a> to download a pre-trained model of your choosing. In this demo, I’m using the Resnet50 model. We’ll add this to the package in just a moment.</p><p>Add a new file called Classifier and drop in the following:</p><pre>import CoreImage<br>import Vapor<br>import Vision<br><br>struct Classifier {<br>    func classify(image: CIImage) throws -&gt; [ClassifierResult] {<br>        let url = Bundle.module.url(forResource: &quot;Resnet50&quot;, withExtension: &quot;mlmodelc&quot;)!<br>        guard let model = try? VNCoreMLModel(for: Resnet50(contentsOf: url, configuration: MLModelConfiguration()).model) else {<br>            throw Errors.unableToLoadMLModel<br>        }<br><br>        let request = VNCoreMLRequest(model: model)<br><br>        let handler = VNImageRequestHandler(ciImage: image)<br><br>        try? handler.perform([request])<br><br>        guard let results = request.results as? [VNClassificationObservation] else {<br>            throw Errors.noResults<br>        }<br><br>        return results.map { ClassifierResult(label: $0.identifier, confidence: $0.confidence) }<br>    }<br><br>    enum Errors: Error {<br>        case unableToLoadMLModel<br>        case noResults<br>    }<br>}<br><br>struct ClassifierResult: Encodable, Content {<br>    var label: String<br>    var confidence: Float<br>}</pre><p>Let’s break this down.</p><p>First, we load the model. Adding a CoreML model to a package is not super straightforward. We need to compile the .mlmodelourselves and add some files to Sources/. We’ll go over that in a few but this wonkiness explains why loading the model might look slightly different from adding one to a standard Xcode project.</p><p>Once the model is loaded, we prepare the request and the request handler; then we do the classification. To send the results as JSON to the client, we need to remap the results to a structure that conforms to Encodable and Content .</p><h4>Adding the Model to the Package</h4><p>This part definitely took me the longest to figure out. Unfortunately, this step is pretty manual; we can’t just drag and drop the model into the project. So, at the root of the server package, add a new folder called MLModelSource and add the Resnet50.mlmodel file here. Create another folder called Resourcesat server/Sources/App/Resources/ .</p><p>Now, we need to compile the model, add the Swift class to sources, and include the .mlmodelc in the package bundle. The compilation steps are repetitive so we’ll place them in a Makefile target. In the project root, create a Makefile:</p><pre># ~/Desktop/coreml-web-api/<br>touch Makefile</pre><p>And add a compile_ml_modeltarget:</p><pre>compile_ml_model:<br>   cd server/MLModelSource &amp;&amp; \\<br>   xcrun coremlcompiler compile Resnet50.mlmodel ../Sources/App/Resources &amp;&amp; \\<br>   xcrun coremlcompiler generate Resnet50.mlmodel ../Sources/App/Resources --language Swift</pre><p>Next, add this to the executable target inPackage.swift file:</p><pre>resources: [<br>    .copy(&quot;Resources/Resnet50.mlmodelc&quot;),<br>]</pre><p>The target should look like this:</p><pre>.executableTarget(<br>    name: &quot;App&quot;,<br>    dependencies: [<br>        .product(name: &quot;Vapor&quot;, package: &quot;vapor&quot;),<br>    ],<br>    resources: [<br>        .copy(&quot;Resources/Resnet50.mlmodelc&quot;),<br>    ]<br>),</pre><p>Okay, now from the project root, run the compile_ml_model target:</p><pre>make compile_ml_model</pre><p>Awesome!!! Now, we have an amazing server that supports classifying uploaded images using the Resnet50 model. Before we move on to the creating the client, we need to adjust the App scheme to make the server available to a physical device on your network.</p><p>Open up the scheme editor, and add serve --hostname 0.0.0.0 to the run arguments.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*9D7VFpFoUNxS6uZPqCVESw.png\" /></figure><p>Sweet. Now, we’ll create a client to do the uploading.</p><h3>iOS Client</h3><p>OK, in Xcode go to File -&gt; New -&gt; Project and add an iOS app to the workspace. We only need SwiftUI, no tests or SwiftData. I’m giving mine a really clever name of CoreMLWebClient … poetic.</p><p>Great. Now, let&#39;s do a little config work. Since we’re going to be using the camera, we need to update the Info.plist with the Privacy — Camera Usage Description key.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*PVfyDXPGfo9-oeQIercJjg.png\" /></figure><p>Nice! In our client, we want to give users the option of using the camera or selecting from the photo library. Create a new file called ImagePicker.swift and paste in the following:</p><pre>import SwiftUI<br><br>struct ImagePicker: UIViewControllerRepresentable {<br>    @Binding var sourceType: UIImagePickerController.SourceType<br>    @Environment(\\.presentationMode) private var presentationMode<br>    var completion: (UIImage) -&gt; Void<br><br>    func makeUIViewController(context: Context) -&gt; some UIViewController {<br>        let picker = UIImagePickerController()<br>        picker.sourceType = sourceType<br>        picker.delegate = context.coordinator<br>        return picker<br>    }<br><br>    func updateUIViewController(_: UIViewControllerType, context _: Context) {}<br><br>    func makeCoordinator() -&gt; Coordinator {<br>        Coordinator(self)<br>    }<br><br>    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {<br>        var parent: ImagePicker<br><br>        init(_ parent: ImagePicker) {<br>            self.parent = parent<br>        }<br><br>        func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {<br>            if let image = info[.originalImage] as? UIImage {<br>                parent.completion(image)<br>            }<br>            parent.presentationMode.wrappedValue.dismiss()<br>        }<br>    }<br>}</pre><p>We’ll use the sourceType binding to switch between the camera and the library.</p><p>Now, we’ll add a Classifierto handle the image uploading and return the classification results. I’m jumping around a little, but all this will come together in a few. Create a new file called Classifier.swift and add this in:</p><pre>import Foundation<br>import UIKit<br><br>struct Classifier {<br>    /// replace this with your dev machine IP address<br>    /// for testing with a physical device.<br>    private let host = &quot;localhost&quot;<br><br>    func classify(image: UIImage) async throws -&gt; [ClassifierResult] {<br>        // Ensure the URL is valid<br>        guard let uploadURL = URL(string: &quot;http://\\(host):8080/classify&quot;) else {<br>            throw URLError(.badURL)<br>        }<br><br>        // Convert the image to JPEG data<br>        guard let imageData = image.jpegData(compressionQuality: 1.0) else {<br>            throw URLError(.unknown)<br>        }<br><br>        // Generate boundary string using a unique per-app string<br>        let boundary = &quot;Boundary-\\(UUID().uuidString)&quot;<br><br>        // Create a URLRequest object<br>        var request = URLRequest(url: uploadURL)<br>        request.httpMethod = &quot;POST&quot;<br>        request.setValue(&quot;multipart/form-data; boundary=\\(boundary)&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)<br><br>        // Create multipart form body<br>        let body = createMultipartFormData(boundary: boundary, data: imageData, fileName: &quot;photo.jpg&quot;)<br>        request.httpBody = body<br><br>        // Perform the upload task<br>        let (data, response) = try await URLSession.shared.upload(for: request, from: body)<br><br>        // Check the response and throw an error if it&#39;s not a HTTPURLResponse or the status code is not 200<br>        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {<br>            throw URLError(.badServerResponse)<br>        }<br><br>        // Decode the data into an array of ClassifierResult<br>        return try JSONDecoder().decode([ClassifierResult].self, from: data)<br>    }<br><br>    /// Creates a multipart/form-data body with the image data.<br>    /// - Parameters:<br>    ///   - boundary: The boundary string separating parts of the data.<br>    ///   - data: The image data to be included in the request.<br>    ///   - fileName: The filename for the image data in the form-data.<br>    /// - Returns: A `Data` object representing the multipart/form-data body.<br>    private func createMultipartFormData(boundary: String, data: Data, fileName: String) -&gt; Data {<br>        var body = Data()<br><br>        // Add the image data to the raw http request data<br>        body.append(&quot;--\\(boundary)\\r\\n&quot;)<br>        body.append(&quot;Content-Disposition: form-data; name=\\&quot;file\\&quot;; filename=\\&quot;\\(fileName)\\&quot;\\r\\n&quot;)<br>        body.append(&quot;Content-Type: image/jpeg\\r\\n\\r\\n&quot;)<br>        body.append(data)<br>        body.append(&quot;\\r\\n&quot;)<br><br>        // Add the closing boundary<br>        body.append(&quot;--\\(boundary)--\\r\\n&quot;)<br>        return body<br>    }<br><br>    struct ClassifierResult: Decodable, Identifiable {<br>        let id = UUID()<br>        var label: String<br>        var confidence: Float<br>    }<br>}<br><br>// Helper function to append string data to Data object<br>private extension Data {<br>    mutating func append(_ string: String) {<br>        if let data = string.data(using: .utf8) {<br>            append(data)<br>        }<br>    }<br>}</pre><p>Great! Now on to the UI. Back in ContentView , let&#39;s add an enum called RequestStatus to communicate to the user what is going on — this is an easy UX win.</p><pre>enum RequestStatus {<br>    case loading, success, idle, error<br>}</pre><p>Now, we’ll create a view model for ContentView that uses the newly created classifier to upload a photo to the server and share the results with the UI. This is also going to use the new <a href=\"https://developer.apple.com/documentation/observation\">Observation framework</a> ⭐.</p><pre>extension ContentView {<br>    @Observable<br>    class ViewModel {<br>        var requestStatus: RequestStatus = .idle<br>        var results: [Classifier.ClassifierResult] = []<br><br>        private var classifier = Classifier()<br><br>        func upload(_ image: UIImage) {<br>            Task { @MainActor in<br>                do {<br>                    requestStatus = .loading<br>                    results.removeAll()<br>                    results = try await classifier.classify(image: image)<br>                    requestStatus = .success<br>                } catch {<br>                    print(error.localizedDescription)<br>                    requestStatus = .error<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>Now we need to add some state. This stuff should probably go in the view model, but for now, I’m going to add these as member vars to ContentView …</p><pre>// ContentView.swift<br>@State private var selectedImage: UIImage?<br>@State private var isImagePickerPresented = false<br>@State private var viewModel = ViewModel()<br>@State private var sourceType: UIImagePickerController.SourceType = .camera</pre><p>Alright, now we’ll do some more UI building. Replace the body variable with this:</p><pre>    var body: some View {<br>        VStack(spacing: 20) {<br>            HStack(spacing: 20) {<br>                if let image = selectedImage {<br>                    VStack {<br>                        Image(uiImage: image)<br>                            .resizable()<br>                            .scaledToFit()<br>                    }.padding()<br>                        .frame(maxHeight: 350)<br>                }<br>                List {<br>                    ForEach(viewModel.results, id: \\.id) { result in<br>                        VStack(alignment: .leading) {<br>                            Text(result.label)<br>                                .font(.callout)<br>                            Text(formatAsPercentage(result.confidence))<br>                                .font(.caption2)<br>                        }<br>                    }<br>                }<br>            }<br>            Divider()<br>            HStack(spacing: 20) {<br>                actionButton()<br>                if viewModel.requestStatus == .loading {<br>                    ProgressView()<br>                }<br>            }<br>        }<br>        .sheet(isPresented: $isImagePickerPresented) {<br>            ImagePicker(sourceType: $sourceType) { image in<br>                self.selectedImage = image<br>            }<br>        }<br>    }</pre><p>And to address those compiler errors, add two new functions:</p><pre>// ContentView.swift<br>@ViewBuilder<br>private func actionButton() -&gt; some View {<br>    if let image = selectedImage {<br>        Button(&quot;Upload Image&quot;) {<br>            viewModel.upload(image)<br>        }.buttonStyle(.borderedProminent)<br>    } else {<br>        HStack(spacing: 20) {<br>            Button(&quot;Camera&quot;) {<br>                sourceType = .camera<br>                isImagePickerPresented = true<br>            }.buttonStyle(.bordered)<br>            Button(&quot;Photo Library&quot;) {<br>                sourceType = .photoLibrary<br>                isImagePickerPresented = true<br>            }.buttonStyle(.bordered)<br>        }.padding(.bottom, 20)<br>    }<br>}<br><br>// and<br><br>private func formatAsPercentage(_ value: Float) -&gt; String {<br>    String(format: &quot;%.2f%%&quot;, value * 100)<br>}</pre><p>Heck yeah, you guys. If everything has gone according to plan, you should now be able to create/select a picture, upload it to the server, classify the dominant object in the picture, and then display the classification results in the UI!</p><p>If you run into issues, please feel free to reference the source code, or leave a comment below.</p><p>I hope this project inspires you and gets the gears turning for your next ML project.</p><p>Cheers!</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=48809a853fae\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/deploy-coreml-models-on-the-server-with-vapor-48809a853fae\">Deploy CoreML Models on the Server with Vapor</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/37afb7a6b26c","link":"https://medium.com/better-programming/full-stack-engineers-dont-exist-37afb7a6b26c?source=rss----d0b105d10f0a---4","title":"Full Stack Engineers Don’t Exist!","author":"Stephen Walsh","updated":"2023-11-10T17:28:33.460Z","published":"Fri, 10 Nov 2023 17:28:33 GMT","categories":["software-development","software-engineering","programming","careers","technology"]}]},"meta":{"timestamp":"2026-06-02T16:53:17.711Z","request_id":"48c08a68-e916-431c-88b0-5e8b335edfac"},"status":"ok","message":"Publication posts","success":true}}}},"401":{"description":"Missing or invalid x-oanor-key header"},"402":{"description":"Active subscription required"},"429":{"description":"Rate-limit or monthly quota reached"},"502":{"description":"Upstream did not respond"}}}},"/v1/tag":{"get":{"operationId":"get_v1_tag","tags":["Medium"],"summary":"Tag posts","description":"","parameters":[{"name":"tag","in":"query","required":true,"description":"Tag/topic slug","schema":{"type":"string"},"example":"programming"}],"security":[{"oanorKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"feed":{"link":"https://medium.com/tag/programming/latest?source=rss------programming-5","image":"https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png","title":"Programming on Medium","description":"Latest stories tagged with Programming on Medium"},"count":9,"items":[{"guid":"https://medium.com/p/36024bdb36c6","link":"https://medium.com/techtofreedom/9-levels-of-profiling-python-apps-in-2026-from-cprofile-to-tachyon-36024bdb36c6?source=rss------programming-5","title":"9 Levels of Profiling Python Apps in 2026: From cProfile to Tachyon","author":"Yang Zhou","updated":"2026-06-02T16:38:28.436Z","published":"Tue, 02 Jun 2026 16:38:28 GMT","categories":["technology","python","software-engineering","programming","coding"]},{"guid":"https://medium.com/p/62467e5d41df","link":"https://medium.com/@kp9810113/the-day-our-2-week-sprint-became-a-6-week-death-march-62467e5d41df?source=rss------programming-5","title":"The Day Our 2-Week Sprint Became a 6-Week Death March","author":"The Concurrent Mind","updated":"2026-06-02T16:37:59.027Z","published":"Tue, 02 Jun 2026 16:37:58 GMT","categories":["programming","technology","software-development","tech","agile"]},{"guid":"https://medium.com/p/df5f5dc124b1","link":"https://medium.com/@folajeff/rhapsody-of-realities-2nd-june-2026-righteousness-is-the-nature-of-god-that-reveals-his-df5f5dc124b1?source=rss------programming-5","title":"RHAPSODY OF REALITIES - 2ND JUNE 2026\n\"Righteousness is the nature of God that reveals His…","author":"STARYLX","updated":"2026-06-02T16:37:56.400Z","published":"Tue, 02 Jun 2026 16:37:56 GMT","categories":["programming","design","productivity","self-improvement","creativity"]},{"guid":"https://medium.com/p/f21e6c82937b","link":"https://ottomancoder.medium.com/flutter-3-44-looks-like-a-minor-update-its-quietly-one-of-the-biggest-in-years-f21e6c82937b?source=rss------programming-5","title":"Flutter 3.44 Looks Like a Minor Update. It’s Quietly One of the Biggest in Years.","author":"Muhammad Usman","updated":"2026-06-02T16:20:29.095Z","published":"Tue, 02 Jun 2026 16:20:29 GMT","categories":["mobile-development","software-development","dart","flutter","programming"]},{"guid":"https://medium.com/p/36df887e70ef","link":"https://blog.cubed.run/i-built-an-ai-assistant-that-turns-messy-human-requests-into-real-tasks-36df887e70ef?source=rss------programming-5","title":"I Built an AI Assistant That Turns Messy Human Requests Into Real Tasks","author":"Ahmad Tariq","updated":"2026-06-02T16:14:41.161Z","published":"Tue, 02 Jun 2026 16:14:41 GMT","categories":["programming","technology","artificial-intelligence","machine-learning","coding"]},{"guid":"https://medium.com/p/581834f48f3c","link":"https://medium.com/javarevisited/9-powerful-java-apis-that-save-development-time-581834f48f3c?source=rss------programming-5","title":"9 Powerful Java APIs That Save Development Time","author":"Gopi C K","updated":"2026-06-02T16:00:16.647Z","published":"Tue, 02 Jun 2026 16:00:16 GMT","categories":["software-development","java","coding","programming"]},{"guid":"https://medium.com/p/c7a719846e47","link":"https://medium.com/javarevisited/i-misused-spring-boot-for-2-years-heres-what-finally-made-it-click-c7a719846e47?source=rss------programming-5","title":"I Misused Spring Boot for 2 Years — Here’s What Finally Made It Click","author":"Pushpak Raut","updated":"2026-06-02T16:00:08.676Z","published":"Tue, 02 Jun 2026 16:00:08 GMT","categories":["java","software-engineering","spring-boot","backend-development","programming"]},{"guid":"https://medium.com/p/3a1d3857692d","link":"https://medium.com/javarevisited/stop-writing-code-youll-regret-dry-kiss-yagni-and-the-law-of-demeter-explained-with-java-3a1d3857692d?source=rss------programming-5","title":"Stop Writing Code You’ll Regret: DRY, KISS, YAGNI, and the Law of Demeter Explained with Java","author":"Sumit Kumar Singh","updated":"2026-06-02T16:00:06.690Z","published":"Tue, 02 Jun 2026 16:00:06 GMT","categories":["system-design-interview","software-development","programming","design","software-engineering"]},{"guid":"https://medium.com/p/1a0385cfbb5a","link":"https://medium.com/javarevisited/command-design-pattern-in-java-a-practical-guide-to-undo-queues-retries-and-clean-architecture-1a0385cfbb5a?source=rss------programming-5","title":"Command Design Pattern in Java: A Practical Guide to Undo, Queues, Retries, and Clean Architecture","author":"Sumit Kumar Singh","updated":"2026-06-02T16:00:04.779Z","published":"Tue, 02 Jun 2026 16:00:04 GMT","categories":["software-engineering","software-development","design","system-design-interview","programming"]}]},"meta":{"timestamp":"2026-06-02T16:53:18.010Z","request_id":"8dfd41c7-b5f1-44aa-b966-7efe6e948129"},"status":"ok","message":"Tag posts","success":true}}}},"401":{"description":"Missing or invalid x-oanor-key header"},"402":{"description":"Active subscription required"},"429":{"description":"Rate-limit or monthly quota reached"},"502":{"description":"Upstream did not respond"}}}},"/v1/user":{"get":{"operationId":"get_v1_user","tags":["Medium"],"summary":"User posts","description":"","parameters":[{"name":"user","in":"query","required":true,"description":"Medium @handle","schema":{"type":"string"},"example":"medium"}],"security":[{"oanorKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"feed":{"link":"https://medium.com/@Medium?source=rss-504c7870fdb6------2","image":"https://cdn-images-1.medium.com/fit/c/150/150/1*8E6Laeaz-zMfU_rkpZUyKw.png","title":"Stories by Medium on Medium","description":"Stories by Medium on Medium"},"count":10,"items":[{"guid":"https://medium.com/p/62edea66ffa4","link":"https://medium.com/blog/updating-our-rules-june-2023-62edea66ffa4?source=rss-504c7870fdb6------2","title":"Updating Our Rules — June 2023","author":"Medium","excerpt":"Updating Our Rules — June 2023 This month, we are updating our Rules . As the world evolves, so do the ways in which people use Medium. To accommodate this, we regularly assess our rules, and adjust them accordingly. We believe that through helping people share their experiences and viewpoints, we c","updated":"2023-06-28T18:01:22.039Z","published":"Wed, 28 Jun 2023 18:01:22 GMT","categories":["rules","medium"],"content_html":"<h3>Updating Our Rules — June 2023</h3><p>This month, we are updating our <a href=\"https://policy.medium.com/medium-rules-30e5502c4eb4\">Rules</a>. As the world evolves, so do the ways in which people use Medium. To accommodate this, we regularly assess our rules, and adjust them accordingly.</p><p>We believe that through helping people share their experiences and viewpoints, we can foster greater levels of understanding, education, and acceptance. We realize that, at various moments in the larger cultural conversation, certain underrepresented groups become targeted, and our rules need to quickly evolve in order to maintain our mission of moving conversation forward. You can read more in our in post “<a href=\"https://blog.medium.com/medium-stands-for-lgbtqia-rights-ee4d63e8052e\">Medium stands for LGBTQIA+ rights</a>.”</p><p>New changes to our rules include expanding and improving the language around how we define hateful conflict to include slurs, deadnaming, and intentional misgendering.</p><p>Additionally, please note that accounts who plagiarize or commit copyright infringement will not be warned, and are not eligible for appeal. Please only post content to Medium which you own or have clear permission to use. If you are unsure if you can use someone else’s content, please see the EFF’s article on <a href=\"https://www.eff.org/issues/bloggers/legal/liability/IP\">“Intellectual Property.”</a></p><p>We also did some general editing clean up, and removed redundant sections to make the rules page cleaner and easier to read.</p><p>The Medium Rules are available <a href=\"https://policy.medium.com/medium-rules-30e5502c4eb4\">here</a>. We <a href=\"https://github.com/Medium/medium-policy/blob/master/Rules.md\">track changes</a> to our rules on Github so you can see how they evolve.</p><p>If you find content or accounts you feel violate our rules, please <a href=\"https://help.medium.com/hc/en-us/articles/217047977-Report-posts-users\">report it.</a> You can use <a href=\"https://help.medium.com/hc/en-us/requests/new\">this form</a> to provide more detail or to report other conduct. Additionally, you can always send an email to <a href=\"mailto:trust@medium.com\">trust@medium.com</a>.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=62edea66ffa4\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/blog/updating-our-rules-june-2023-62edea66ffa4\">Updating Our Rules — June 2023</a> was originally published in <a href=\"https://medium.com/blog\">The Medium Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/cccf29534bf4","link":"https://policy.medium.com/email-import-terms-of-use-cccf29534bf4?source=rss-504c7870fdb6------2","title":"Email Import Terms of Use","author":"Medium","excerpt":"Effective as of November 8th, 2021 By using this feature to import any email addresses (the “Email Import”), you represent and agree that: You have collected all email addresses that you will import through the Email Import in accordance with all applicable local, state, provincial, national, intern","updated":"2021-11-08T17:24:26.374Z","published":"Mon, 08 Nov 2021 17:24:26 GMT","categories":[],"content_html":"<p>Effective as of November 8th, 2021</p><p>By using this feature to import any email addresses (the “Email Import”), you represent and agree that:</p><ul><li>You have collected all email addresses that you will import through the Email Import in accordance with all applicable local, state, provincial, national, international, and other applicable laws, rules, and regulations (“Applicable Law”), and your use and disclosure to us of such email addresses complies with Applicable Law.</li><li>You have provided all required notices and obtained all required consents to engage Medium to send emails on your behalf for commercial or marketing purposes, including sending stories or newsletters.</li><li>You have collected the email addresses that you will import directly from the email recipients and have not bought, rented, or otherwise acquired these addresses in any way from any third-party.</li><li>You will not direct Medium to send any emails to any recipient who has unsubscribed from your mailing list either in the Medium settings or “unsubscribe” link in the Medium email itself, or through contacting you off-platform in any other way. For more information, please see our <a href=\"https://help.medium.com/hc/en-us/articles/360059837393\">Help Center article</a>.</li><li>If sending emails using the Medium platform results in significant bounce rates (rate of which email addresses in your list that didn’t receive your email because it was returned by a mail server), recipient complaint rates, or unsubscribe requests in excess of industry standards, or a determination that you have engaged in spam, as defined under our<a href=\"https://policy.medium.com/medium-rules-30e5502c4eb4\"> Rules</a>, then Medium may suspend or terminate your use of this Email Import in Medium’s sole discretion.</li><li>When Medium processes the email addresses you import through the Email Import for the purpose of sending emails on your behalf, Medium is a processor or service provider, as those terms are defined under Applicable Law, and Medium processes those emails in accordance with your instructions and on your behalf.</li><li>Medium will not retain, use, or disclose the email addresses for any purpose, including any commercial purpose, other than for the specific purpose of sending emails on your behalf, or as otherwise permitted by Applicable Law, such as honoring recipient unsubscribe requests.</li></ul><p>Please note that your use of the Email Import is part of Medium services, and you must comply with our legal terms, including the<a href=\"https://policy.medium.com/medium-terms-of-service-9db0094a1e0f\"> Terms of Service</a>,<a href=\"https://policy.medium.com/medium-privacy-policy-f03bf92035c9\"> Privacy Policy</a>, and<a href=\"https://policy.medium.com/medium-rules-30e5502c4eb4\"> Rules</a> at all times. Medium reserves the right to suspend or terminate your access to Medium and its services with or without notice in Medium’s sole discretion</p><p>If you have any questions about this Email Import, please review our <a href=\"https://help.medium.com/hc/en-us/articles/360059837393\">Help Center article</a>.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cccf29534bf4\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://policy.medium.com/email-import-terms-of-use-cccf29534bf4\">Email Import Terms of Use</a> was originally published in <a href=\"https://policy.medium.com\">Medium Policy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/fcfe9cf777b8","link":"https://policy.medium.com/medium-partner-program-terms-fcfe9cf777b8?source=rss-504c7870fdb6------2","title":"Medium Partner Program Terms","author":"Medium","excerpt":"Effective as of June 1, 2026. These terms (the “Partner Program Terms”) govern A Medium Corporation’s (“Medium”) partner program (“Partner Program”). If you apply, meet the eligibility criteria, and are accepted into Medium’s Partner Program, you can (i) put a story from your user account behind Med","updated":"2026-06-01T20:09:10.689Z","published":"Wed, 11 Aug 2021 23:38:00 GMT","categories":["medium","terms-of-service"],"content_html":"<p><strong>Effective as of June 1, 2026.</strong></p><p>These terms (the “Partner Program Terms”) govern A Medium Corporation’s (“Medium”) partner program (“Partner Program”). If you apply, meet the eligibility criteria, and are accepted into Medium’s Partner Program, you can (i) put a story from your user account behind Medium’s paywall (“locking” a story) and receive revenue based on its performance among Medium members, and (ii) subject to your further evaluation and acceptance by Medium, to participate in the Editor Partner Program, as more specifically defined and described below. Medium reserves the right to modify, suspend, or discontinue the Partner Program, or any portion thereof, at any time and without advance notice to you. Medium will not be liable to you or any third party for any such modification, suspension, or termination.</p><p>By participating in the Partner Program, you agree to these Partner Program Terms, as well as Medium’s <a href=\"https://medium.com/policy/medium-terms-of-service-9db0094a1e0f\">Terms of Service</a>, <a href=\"https://medium.com/policy/medium-privacy-policy-f03bf92035c9\">Privacy Policy</a>, <a href=\"https://medium.com/policy/medium-rules-30e5502c4eb4\">Rules</a>, and all other terms and conditions that apply to the Medium website, mobile applications, and any other Medium service or product (collectively, the “Terms”). Please read the Partner Program Terms carefully. If you don’t understand them, or don’t accept any part of them, then you can’t participate in the Partner Program. Your acceptance of Medium’s Partner Program Terms forms a binding agreement between you and Medium and shows your intent to be bound by them.</p><h3>Eligibility &amp; Application</h3><p>To participate in the Partner Program, you must meet the <a href=\"https://help.medium.com/hc/en-us/articles/115011694187\">Eligibility Requirements</a>, apply, and be accepted into the Partner Program. Medium reserves the right to admit writers to the Partner Program at Medium’s sole discretion. Eligibility requirements are subject to change.</p><p><strong><em>Existing Partner Program Participants</em>:</strong> If you are a participant in the Partner Program as of August 1, 2024, you will remain in the program. However, to continue to participate in the Partner Program, you’ll need to meet the <a href=\"https://help.medium.com/hc/en-us/articles/115011694187\">Eligibility Requirements</a> by December 31, 2024. Medium will use reasonable efforts to contact you before removing you from the Partner Program if you no longer meet the Eligibility Requirements. If you are removed from the Partner Program, your locked stories will be removed from behind the paywall.</p><p><strong><em>Ongoing Eligibility for all Partner Program participants</em>:</strong> To remain in the Partner Program, you must continue to meet the <a href=\"https://help.medium.com/hc/en-us/articles/115011694187\">Eligibility Requirements</a>. In the event that we disable your ability to receive any revenue under the Partner Program and we subsequently determine that you meet the requirements to remain in the Partner Program, we may re-enable your ability to receive revenue under the Partner Program without requiring a reapplication from you for the Partner Program.</p><h3>Earning on Locked Stories</h3><p><strong><em>User Stories<br></em></strong>For each locked story associated with your account, you have the opportunity to receive revenue based on various performance factors, including reader engagement. Based on the performance and assessed quality of a story, Medium will pay you a portion of revenues received by Medium from membership fees, as determined by Medium. Medium will send you payment for any revenues above $10 USD accrued as described in the Payment section below. Medium reserves the right to adjust at any time the factors by which story performance and revenue are determined.</p><p><strong><em>User Stories &amp; Medium Publications<br></em></strong>A locked story may be included in a Medium publication. The writer chooses whether to lock a story and will receive the same amount of funds through the Partner Program regardless of whether the story is included in a publication. As with any story, either the writer or the publication owner or editors may choose to remove a locked story from a publication at any time.</p><p>The above arrangement may be modified on a case-by-case basis, such as for publications operating on Medium that commission or own their content. For any such publication planning to receive proceeds from a story’s performance, this arrangement must be made clear to any writer potentially affected and agreed upon in advance with a given story’s writer.</p><h3>Earning on Editing Activities</h3><p>As a participant in the Partner Program, you will be eligible to apply to participate in Medium’s Editor Partner Program (the “EPP”). Medium reserves the right to admit or reject any applicant to the EPP at Medium’s sole discretion. Eligibility requirements for the EPP are described <a href=\"https://help.medium.com/hc/en-us/articles/40661484998679-Editor-Partner-Program-overview\">here</a> and may be changed by Medium at any time without notice.</p><p>If Medium accepts your application to participate in the EPP, you will be permitted to edit eligible stories submitted to Medium publications of which you are an owner or an editor, in accordance with Medium’s editorial guidelines and policies. For each story you edit, you may earn compensation based on that story’s performance, as described in Medium’s EPP Pay Structure <a href=\"https://help.medium.com/hc/en-us/articles/40661484998679-Editor-Partner-Program-overview\">here</a>. Medium retains sole discretion to determine which stories are eligible for editing under the EPP. Medium reserves the right to modify, suspend, or discontinue the EPP, or any portion thereof, at any time and without advance notice to you.</p><p><strong>Note to Writers</strong><br>Stories submitted to Medium may be edited by an editor participating in EPP. EPP editors may receive compensation from Medium based on the performance of stories they edit. Editor compensation is paid by Medium and does not affect the author’s earnings. By submitting a story to Medium, you acknowledge that your story may be edited by an EPP editor who may earn compensation in connection with your story’s performance.</p><h3>Necessary Rights</h3><p>You may only lock a story (i.e. place it behind Medium’s paywall) if you have sufficient rights to make commercial use of the content in it, including text, video, and audio elements. Medium may require you to provide documentation proving you own sufficient rights to lock a story.</p><p>The consequences of locking stories when you lack the rights to do so or failing to provide adequate documentation upon request include unlocking stories, withholding revenue, suspension, or termination from the Partner Program, and suspension or termination of your Medium account.</p><p>You are not entitled to earn or receive any revenue in connection with your content if one or more third parties claim rights to any element(s) of your content.</p><p>These Partner Terms are not legal advice. If you plan to lock and earn revenue on a story containing any content you did not create, you may want to talk to a lawyer first to ensure you own all rights required to do so.</p><h3>Termination</h3><p>Medium reserves the right to unlock a story at any time, which will end that story’s ability to generate revenue. Medium reserves the right in its sole discretion to disable your ability to receive any revenue under the Partner Program, suspend your participation in the Partner Program, or terminate your participation in the Partner Program at any time for any reason, including but not limited to the failure to meet the Eligibility Requirements, sustained inactivity in the Partner Program, or violation of any terms contained herein.</p><p>If your participation in the Partner Program is terminated for any reason, your locked stories will be unlocked and can no longer generate revenue, and you will no longer be eligible to earn from Medium’s deprecated Referral Earnings program.</p><h3>Payment</h3><p>To accrue or receive payment of any revenues under the Partner Program, you must be a Partner Program participant in good standing (meaning for example, your monetization is not currently disabled, you are not currently suspended, you are not violating Terms or have not violated the Terms, or have not been terminated), have at all times an active Medium account, and have at all times an active payout method associated with your Medium account. Medium does not owe you any revenue that may be associated with your content during any period when you are not in good standing in the Partner Program, do not have a valid Medium account, or do not have an active payout method. You will not receive any revenue if you do not have a payout account in a geographic region where the Partner Program is available. The list of available geographic regions is available <a href=\"https://help.medium.com/hc/en-us/articles/115011694187\">here</a>.</p><p>Medium reserves the right to require that your revenue meet a $10 minimum payout threshold before Medium disburses payment. In the event that you quit or are removed from the Partner Program and have not met the minimum payout threshold OR have not completed your payout settings, Medium reserves the right to forfeit the payment.</p><h3>Changes or Discontinuation of Partner Program</h3><p>Medium reserves the right to modify, suspend, or discontinue the Partner Program. Medium will not be liable to you or any third party for any such modification, suspension, or termination. Medium may change these Partner Program Terms from time to time so we encourage you to periodically review this page for the most up-to-date version.</p><h3>Taxes</h3><p>If required by law, you are responsible for reporting and paying any applicable taxes in connection with your participation in the Partner Program. Such taxes may include duties, customs fees, or other taxes (other than income tax), along with any related penalties or interest.</p><h3>Governing Law &amp; Forum</h3><p>The governing law, mandatory forum, and dispute resolution provisions of Medium’s Terms of Service also apply to these Partner Program Terms and any dispute arising under them.</p><h3>Miscellaneous</h3><p>You participate in the Partner Program at your own risk. You agree that you are solely responsible for your actions in connection with the Partner Program, any breach of your obligations under the Partner Program Terms, and for the consequences of any such breach.</p><p>Any capitalized terms used but not defined in these Partner Program Terms have the meanings given to them in Medium’s Terms of Service. Except as modified by these Partner Program Terms, Medium’s Terms of Service remain in effect. Medium’s right to modify or revise the Terms of Service also applies to these Partner Program Terms.</p><h3>Contact Us</h3><p>If you have questions or feedback about the Partner Program, you can submit a request at help.medium.com.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fcfe9cf777b8\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://policy.medium.com/medium-partner-program-terms-fcfe9cf777b8\">Medium Partner Program Terms</a> was originally published in <a href=\"https://policy.medium.com\">Medium Policy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/30e5502c4eb4","link":"https://policy.medium.com/medium-rules-30e5502c4eb4?source=rss-504c7870fdb6------2","title":"Medium Rules","author":"Medium","excerpt":"Medium is an open platform that exists to share ideas and perspectives from the world’s most insightful writers, thinkers, and storytellers. We welcome thoughtful and civil discussion from a broad spectrum of viewpoints. To maintain a safe, respectful, and welcoming environment for a wide range of p","updated":"2024-09-24T17:46:09.785Z","published":"Wed, 26 May 2021 07:00:00 GMT","categories":["terms","medium","rules"],"content_html":"<p>Medium is an open platform that exists to share ideas and perspectives from the world’s most insightful writers, thinkers, and storytellers.</p><p>We welcome thoughtful and civil discussion from a broad spectrum of viewpoints. To maintain a safe, respectful, and welcoming environment for a wide range of people to engage in meaningful conversations, we prohibit certain conduct.</p><p>In deciding whether there has been a violation of the rules, we will take into consideration things like<a href=\"https://help.medium.com/hc/en-us/articles/360018664574-Newsworthiness-considerations\"> newsworthiness</a>, the context and nature of the posted information, the likelihood and severity of actual or potential harms, account history, and applicable laws.</p><p>Violations of our rules may result in consequences such as account restrictions or suspension of your content. Medium has the sole authority and final decision as to whether content or behavior violates our rules.</p><h4><strong>To report a violation:</strong></h4><p>If you find a post or account on Medium that you believe violates these rules, <strong>please </strong><a href=\"https://help.medium.com/hc/en-us/articles/217047977-Report-posts-users\"><strong>report it.</strong></a> You can find the report button in the 3-dot menu on every post, response, and account page.</p><p>You can use <a href=\"https://help.medium.com/hc/en-us/requests/new?ticket_form_id=168427\">this form</a> to provide more detail or to report other conduct you believe violates our rules. Additionally, you can send an email to <a href=\"mailto:trust@medium.com\">trust@medium.com</a>.</p><h4><strong>Block and mute:</strong></h4><p>If you find yourself in conflict with another user, we encourage the following:</p><ul><li>Block them and move on. <a href=\"https://help.medium.com/hc/en-us/articles/217048077-Block-a-user\">You can learn more about blocking here</a>.</li><li>Mute them, and they won’t show up in your feeds. <a href=\"https://help.medium.com/hc/en-us/articles/224544048-Mute-an-author-or-publication\">You can learn more about muting here</a>.</li><li>Remove their responses from your stories. <a href=\"https://help.medium.com/hc/en-us/articles/217048127-Manage-responses\">You can learn more about managing and turning off your responses here</a>.</li></ul><h3><strong>Rules</strong></h3><p>Capitalized terms here have the same meaning as defined in the<a href=\"https://policy.medium.com/medium-terms-of-service-9db0094a1e0f\"> Medium Terms of Service</a>.</p><p><strong>Threats of violence and incitement<br></strong>We do not allow content or actions that threaten, encourage, or incite violence against anyone, directly or indirectly.</p><p><strong>Hateful content<br></strong>We do not allow content that constitutes or promotes violence, harassment, or hatred against people based on characteristics like race, ethnicity, national origin, religion, caste, disability, disease, age, sexual orientation, gender, or gender identity.</p><p>We do not allow posts or accounts that glorify, celebrate, downplay, or trivialize violence, suffering, abuse, or deaths of individuals or groups. This includes the use of scientific or pseudoscientific claims or misleading statistics to pathologize, dehumanize, or disempower others. We do not allow calls for intolerance, exclusion, or segregation based on protected characteristics, nor do we allow the glorification of groups which do any of the above.</p><p>We do not allow posts or accounts that target others with slurs, tropes, or other content that intends to dehumanize, degrade or reinforce negative or harmful stereotypes about a protected category. For example, this may include targeted or intentional misgendering or deadnaming of transgender individuals, or harmfully ableist language.</p><p>We do not allow hateful text, images, symbols, or other content, including in your username, profile, or bio.</p><p><strong>Harassment<br></strong>Medium exists to share and discuss ideas. We don’t tolerate harassment, which includes:</p><ul><li>Bullying, threatening, mocking, or shaming someone, or posting things likely to encourage others to do so</li><li>Engaging in a repetitive or targeted campaign of harassment against someone or a group of people</li><li>Using derogatory language, racial slurs, or obscenities to disparage or attack someone or a group of people</li><li>Using Medium features like responses, private notes, mentions, follows, lists, highlights, or requests in a way that attempts to or does annoy or harass someone, or to draw inorganic attention to your content on Medium</li></ul><p><strong>Privacy and Reputation<br></strong>We do not allow the following:</p><ul><li>Posting images of, transcripts of, copies of, or links to private communications between private individuals without the explicit consent of all parties to the communication. Redacting names or other information does not supplant the need to secure permissions</li><li>Doxing, which includes not only private or obscure personal information but also the aggregation of publicly available information to target, shame, blackmail, harass, intimidate, threaten, or endanger a person or group of people</li><li>Posting intimate or explicit images taken or posted without the subject’s express consent</li><li>Content that violates others’ privacy or personal safety, including sensitive or confidential information such as credit card numbers, social security numbers, non-public phone numbers, physical addresses, email addresses, non-public data, or other similar information</li></ul><p><strong>Restricted categories<br></strong>We do not allow posts or accounts that engage in the following restricted categories of activity:</p><ul><li>Promotion of controversial, suspect, or extreme content. <a href=\"https://help.medium.com/hc/en-us/articles/360018182453\">You can read more about these policies in our Help Center</a>.</li><li>Facilitation of gambling or betting</li><li>Facilitation of buying or selling social media interactions, including off-platform</li><li>Facilitation of sexual services</li><li>Facilitation of copyright or other intellectual property violation</li><li>Facilitation or evidence of violating the terms of service of Medium or third party(s)</li><li>Providing reviews of businesses or products in a gratuitously harmful or abusive manner</li><li>Facilitation of illegal hacking (e.g., stealing credentials, compromising personal data)</li><li>Promotion of pseudoscience, disinformation, or other content that is contrary to public health or safety</li></ul><p><strong>Related conduct<br></strong>We do not allow content or accounts that engage in on-platform, off-platform, or cross-platform campaigns of targeting, harassment, hate speech, violence, or disinformation. We may consider off-platform actions in assessing a Medium account, and restrict access or availability to that account.</p><p><strong>Graphic content<br></strong>We do not allow posting, linking to, or otherwise promoting pornographic images or videos. We do allow erotic writing and non-graphic erotic images.</p><p>Medium is a large network, and posts can travel in front of all different types of readers. We ask that you be mindful of unintentional viewers when selecting your images.</p><p>We do not allow gratuitously graphic or disturbing media, regardless of subject matter.</p><p><strong>Exploitation of minors<br></strong>We do not allow content promoting the sexual, violent, or other exploitation of minors, including the sexualization of fictional minors.</p><p><strong>Promotion and glorification of self-harm<br></strong>We do not allow content or activities that encourage, promote or glorify acts of self-harm, such as cutting, eating disorders like anorexia or bulimia, and suicide. If you encounter users contemplating or threatening self-harm, please report it.</p><p><strong>Duplicate Content<br></strong>We do not allow posting <a href=\"https://help.medium.com/hc/en-us/articles/360039513913-About-the-No-Duplicate-Content-rule\">duplicate copies</a> of the same content to Medium, whether from a single account or across multiple accounts, either publicly or as an unlisted story. (You are allowed to cross-post content from your blog to Medium, provided you own the rights for the content.)</p><p><strong>Spam or Site Misuse<br></strong>We do not allow spam or misuse of Medium. All spam or misuse will be immediately removed from Medium without notification. Examples of spam or misuse include:</p><ul><li>Posting content primarily to drive traffic to, or increase the search rankings of, an external site, product, or service</li><li>Scraping and reposting content from other sources for the primary purpose of generating revenue or other personal gains</li><li>Posting duplicate content, whether from a single account or across multiple accounts</li><li>Stories where the content is clipped with the purpose of linking to the rest of the article on a different website</li><li>Performing a disproportionately large number of interactions, particularly by automated means. This includes bulk or indiscriminate interactions, such as following of other accounts (follow spam), clapping, highlighting, leaving notes, or flagging content</li><li>Repeatedly using responses, mentions, or other interactions as a method of promotion or marketing</li><li>Participating in bounty campaigns or brigades to artificially inflate rankings for posts, accounts, businesses, or products</li><li>Use or re-use content templates with slight modifications across multiple posts and accounts</li></ul><p>For each of these behaviors, when we talk about “content,” we mean not only posts but also any other feature that allows you to add your own text or media. When we talk about “interactions,” we mean any feature that allows one user to interact with another person or post.</p><p><strong>Copyright and trademark infringement<br></strong>Respect the copyrights and trademarks of others. Per our<a href=\"https://medium.com/policy/medium-terms-of-service-9db0094a1e0f\"> Terms of Service</a>, we require users to have permission to post the content they publish on Medium. Additionally, we have <a href=\"https://help.medium.com/hc/en-us/articles/360041640213\">specific policies</a> around plagiarism, to which all Medium accounts are held. Users found in violation of our copyright rules are not eligible for warning, appeal, or restoration. Deletion of copyright violations is not grounds for reinstatement.</p><p>We respond to notices of alleged infringement as described in our<a href=\"https://medium.com/policy/medium-terms-of-service-9db0094a1e0f\"> Terms of Service</a>,<a href=\"https://medium.com/policy/mediums-copyright-and-dmca-policy-d126f73695\"> Copyright and DMCA Policy</a>, and<a href=\"https://medium.com/policy/mediums-trademark-policy-e3bb53df59a7\"> Trademark Policy</a>.</p><p><strong>Deceptive conduct<br></strong>We do not allow deceptive conduct on Medium. This includes:</p><ul><li>Posting content or impersonating a person or organization in a way likely to deceive people. Parody and satire are fine, but make clear that is what you’re doing. Our<a href=\"https://medium.com/policy/medium-username-policy-7054a77fb04f\"> Username Policy</a> has specific requirements for parody accounts</li><li>Using Medium for phishing or fraud. Don’t use tags, links, titles, or other metadata in a misleading way. Don’t link to or embed malicious or harmful code or software in your posts</li><li>Using deception to generate revenue or traffic</li><li>If you have received free goods or services, or anything of value in connection with the topic of a post, you must make this clear.</li></ul><p><strong>Ads, Promotions, and Marketing</strong></p><ul><li>First party promotion is allowed, and you may promote and link to your own business, website, mailing list, or fundraiser.</li><li>Third-party advertising and sponsorships are not allowed. You may not advertise or promote third-party products, services, or brands through Medium posts, publications, or newsletters. This includes images that indicate brand sponsorship in a post or newsletter, or as part of a publication name or logo.</li><li>Affiliate links, such as links out to Amazon with an affiliate code, or any other link out where you will receive a commission or other value, are allowed in posts. However, per Federal Trade Commission law, you must disclose the inclusion of these links in your post. This can be a simple sentence in the footer. (for further guidance, see<a href=\"https://www.ftc.gov/tips-advice/business-center/guidance/ftcs-endorsement-guides-what-people-are-asking\"> FTC Rules and Guides</a>).</li></ul><p><strong>Embedded Content and Collection of Personal Information<br></strong>To ensure the data privacy and security of our users, embedded content must comply with the following requirements. An “embed” includes a link, form, or a request for information or other content.</p><p>Embeds directly collecting data through Medium form fields, comments or other onsite means are not allowed. This includes embeds that facilitate the submission of email addresses, credit card information or other personal information. If you want to collect information from your readers, you will need to follow these requirements:</p><ul><li>If you link out from your post on Medium to a form hosted elsewhere, that form must make it clear to a user that they are no longer in the Medium network and the information they disclose is subject to the third-party’s Terms of Use and Privacy Policy,</li><li>If the link directs users to your own platform to subscribe to your newsletter, blog or other content you create, then in your Medium content immediately next to the link you must disclose that the link will take the user offsite outside of Medium</li><li>If your content includes a form that sends user information to you or a third-party, immediately next to or within the form you must disclose that the form will send the user’s information to an offsite third-party outside of Medium that is subject to that offsite third-party’s Terms of Use and Privacy Policy, or offsite outside of Medium, to you directly.</li></ul><p><strong>Paid, automatic, bulk, or non-genuine interactions<br></strong>Medium depends on various user behaviors — like follows and claps — to determine what content to feature and make the site work well for everyone. We don’t allow artificial behaviors that skew this system and as a result degrade or distort other users’ experiences.</p><p>This includes:</p><ul><li>Buying, selling, or trading in accounts or account interactions — including views, reads, follows, claps, highlights, responses, or other traffic</li><li>Using services, apps, or arrangements that offer you more views, reads, follows, claps, or other interactions on your Medium account or content</li><li>Registering accounts, posting content, or interacting with users or content automatically, systematically, or programmatically</li></ul><p><strong>Cryptocurrency Accounts, Posts, and Publications<br></strong>Posts and accounts that are focused on launching, announcing, or providing information on cryptocurrencies must meet the requirements listed in our <a href=\"https://help.medium.com/hc/en-us/articles/360000646167-Cryptocurrencies-on-Medium#:~:text=Medium%20does%20not%20endorse%20or,maintain%20that%20email%20account%20actively.\">Cryptocurrency Policy</a>. Medium does not endorse or verify any coin, token, financial advice, or similar announcement.</p><h4><strong>Prohibitions on Use of the Services</strong></h4><p>You agree not to do, try to do, or cause a third party to do any of the following, except without the express written consent of Medium:</p><p>(1) access or tamper with non-public areas of the Services, our computer systems, or the systems of our technical providers;</p><p>(2) access or search the Services by any means other than the currently available, published interfaces (e.g., APIs) that we provide;</p><p>(3) forge any TCP/IP packet header or any part of the header information in any email or posting, or in any way use the Services to send altered, deceptive, or false source-identifying information;</p><p>(4) use the Services in any manner that could disable, overburden, damage, or impair the Services, or interfere with any other use of the Services;</p><p>(5) use any software, script, robot, spider or other automatic device, process or means (including crawlers, browser plugins and add-ons or any other technology) to access the Services for any purpose, including without limitation to scrape or otherwise copy any of the data or content on the Services;</p><p>(6) use any manual process to monitor or copy any of the data or content on the Services, or to engage in any other unauthorized purpose;</p><p>(7) otherwise use any device, software or routine that interferes with the proper working of the Services; or</p><p>(8) otherwise attempt to interfere with the proper working of the Services.</p><h4><strong>Use of custom domain features</strong></h4><p>Medium provides the ability to point a domain name that you control to a publication on Medium. If you use this custom domain feature, you understand that your publication will still be hosted on Medium and will still be subject to Medium’s Terms of Service. If you use this custom domain feature, you may not do, try to do, or cause a third party to do the following:</p><p>(1) Alter the look and feel or transform the content of the webpage, including by injecting ads, inserting tracking code, or altering the Medium look or feel;</p><p>(2) Use proxy servers or any other means to short-circuit our rules or circumvent user protections; or</p><p>(3) Mislead users about what site they are on or what their actions will do.</p><p>Your use of the custom domain feature must comply with our<a href=\"https://policy.medium.com/medium-privacy-policy-f03bf92035c9\"> Privacy Policy</a>. Please note that our rules around storing, transferring, and using user data are more user-protective than some other sites’. We do not sell user data or allow tracking of our users across the web. We expect you to uphold these same rules and policies in connection with operating your custom domain.</p><h4><strong>How to report a violation</strong></h4><p>If you find a post or account on Medium that violates these rules, please<a href=\"https://help.medium.com/hc/en-us/articles/214098258-Report-posts-users\"> report it</a> in product. You can use<a href=\"https://yourfriends.medium.com/\"> this form</a> to provide more detail or to report other conduct you believe violates our rules. Additionally, you can send us an email to trust@medium.com.</p><h4><strong>If you break the rules</strong></h4><p>We strive to be fair, but we reserve the right to suspend accounts or remove content, without notice, for any reason, particularly to protect our services, infrastructure, users, or community. If you attempt to evade suspension by creating new accounts or posts, we will suspend your new accounts and posts.</p><h4><strong>Notice</strong></h4><p>Upon investigating or disabling content associated with your account, we will notify you, unless we believe your account is automated or operating in bad faith, or that notifying you is likely to cause, maintain or exacerbate harm to someone.</p><h4><strong>Appeals</strong></h4><p>If you believe your content or account has been restricted or disabled in error, or believe there is relevant context we were not aware of in reaching our determination, you can write to us at <a href=\"mailto:trust@medium.com\">trust@medium.com</a>. We will consider all good faith efforts to appeal.</p><h4><strong>Government Takedown Requests</strong></h4><p>If Medium receives a request from a government actor to restrict access to content associated with your account, we will notify you unless we are prohibited by law or believe doing so may endanger others. Where applicable, we will work to limit legally-ordered content restrictions to jurisdictions where we have a good faith belief that we are legally required to restrict the content. Medium submits to the<a href=\"https://www.lumendatabase.org/\"> Lumen</a> database government requests to restrict access to content (redacted where appropriate to protect privacy or prevent harm to a person).</p><p>We may enforce, or not enforce, these policies at our sole discretion. These policies don’t create a duty or contractual obligation for us to act.</p><p>We also may change these rules at any time. We<a href=\"https://github.com/Medium/Policy/blob/master/Rules.md\"> track changes</a> to our rules on Github so you can see how they evolve.</p><p>Medium is committed to providing a transparent, open platform for expression and therefore supports the goals and spirit of<a href=\"https://santaclaraprinciples.org/\"> The Santa Clara Principles on Transparency and Accountability in Content Moderation</a> as a starting point for further discussion.</p><p><em>Updated June 2023</em></p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=30e5502c4eb4\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://policy.medium.com/medium-rules-30e5502c4eb4\">Medium Rules</a> was originally published in <a href=\"https://policy.medium.com\">Medium Policy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/f03bf92035c9","link":"https://policy.medium.com/medium-privacy-policy-f03bf92035c9?source=rss-504c7870fdb6------2","title":"Medium Privacy Policy","author":"Medium","excerpt":"Effective date: March 24, 2022 You can see our previous Privacy Policy here . This Privacy Policy explains how A Medium Corporation ( “ Medium ,” “ we ,” or “ us ” ) collects, uses, and discloses information about you. This Privacy Policy applies when you use our websites, mobile applications, and o","updated":"2026-05-07T20:03:52.753Z","published":"Tue, 01 Sep 2020 22:57:00 GMT","categories":["medium","privacy"],"content_html":"<h4>Effective date: March 24, 2022</h4><p><strong><em>You can see our previous Privacy Policy </em></strong><a href=\"https://medium.zendesk.com/hc/en-us/articles/360052305234\"><strong><em>here</em></strong></a><strong><em>.</em></strong></p><p>This Privacy Policy explains how A Medium Corporation (<em>“</em><strong><em>Medium</em></strong><em>,”</em> <em>“</em><strong><em>we</em></strong><em>,”</em> or <em>“</em><strong><em>us</em></strong><em>”</em>) collects, uses, and discloses information about you. This Privacy Policy applies when you use our websites, mobile applications, and other online products and services that link to this Privacy Policy (collectively, our <em>“</em><strong><em>Services</em></strong>”), contact our customer service team, engage with us on social media, or otherwise interact with us.</p><p>We may change this Privacy Policy from time to time. If we make changes, we will notify you by revising the date at the top of this policy and, in some cases, we may provide you with additional notice (such as adding a statement to our website or providing you with a notification). We encourage you to review this Privacy Policy regularly to stay informed about our information practices and the choices available to you.</p><h3>CONTENTS</h3><ul><li>Collection of Information</li><li>Use of Information</li><li>Sharing of Information</li><li>Third-Party Embeds</li><li>Transfer of Information to the United States and Other Countries</li><li>Your Choices</li><li>Your California Privacy Rights</li><li>Additional Disclosures for Individuals in Europe</li><li>Contact Us</li></ul><h4>COLLECTION OF INFORMATION</h4><h4>Information You Provide to Us</h4><p>We collect information you provide directly to us. For example, you share information directly with us when you create an account, fill out a form, submit or post content through our Services, purchase a membership, communicate with us via third-party platforms, request customer support, or otherwise communicate with us. The types of personal information we may collect include your name, display name, username, bio, email address, business information, your content, including your avatar image, photos, posts, responses, and series published by you, and any other information you choose to provide.</p><p>In some cases, we may also collect information you provide about others, such as when you purchase a Medium membership as a gift for someone. We will use this information to fulfill your request and will not send communications to your contacts unrelated to your request, unless they separately consent to receive communications from us or otherwise engage with us.</p><p>We do not collect payment information through our Services. We rely on third parties to process payments in connection with our Services. Any information you provide to facilitate such a payment is subject to the third-party payment processor’s privacy policy, and we encourage you to review this policy before you provide any information to the payment processor.</p><h4>Information We Collect Automatically When You Interact with Us</h4><p>In some instances, we automatically collect certain information, including:</p><ul><li><strong>Activity Information:</strong> We collect information about your activity on our Services, such as your reading history and when you share links, follow users, highlight posts, and clap for posts.</li><li><strong>Transactional Information:</strong> When you purchase a membership, we collect information about the transaction, such as subscription details, purchase price, and the date of the transaction.</li><li><strong>Device and Usage Information:</strong> We collect information about how you access our Services, including data about the device and network you use, such as your hardware model, operating system version, mobile network, IP address, unique device identifiers, browser type, and app version. We also collect information about your activity on our Services, such as access times, pages viewed, links clicked, and the page you visited before navigating to our Services.</li><li><strong>Information Collected by Cookies and Similar Tracking Technologies:</strong> We use tracking technologies, such as cookies and web beacons, to collect information about you. Cookies are small data files stored on your hard drive or in device memory that help us improve our Services and your experience, see which areas and features of our Services are popular, and count visits. Web beacons (also known as “pixel tags” or “clear GIFs”) are electronic images that we use on our Services and in our emails to help deliver cookies, count visits, and understand usage. We also work with third party analytics providers who use cookies, web beacons, device identifiers, and other technologies to collect information about your use of our Services and other websites and applications, including your IP address, web browser, mobile network information, pages viewed, time spent on pages or in mobile apps, and links clicked. This information may be used by Medium and others to, among other things, analyze and track data, determine the popularity of certain content, deliver content targeted to your interests on our Services, and better understand your online activity. For more information about cookies and how to disable them, see Your Choices below.</li></ul><h4>Information We Collect from Other Sources</h4><p>We obtain information from third-party sources. For example, we may collect information about you from social networks, accounting services providers and data analytics providers. Additionally, if you create or log into your Medium account through a third-party platform (such as Apple, Facebook, Google, or Twitter), we will have access to certain information from that platform, such as your name, lists of friends or followers, birthday, and profile picture, in accordance with the authorization procedures determined by such platform.</p><h4>Information We Derive</h4><p>We may derive information or draw inferences about you based on the information we collect. For example, we may make inferences about your location based on your IP address or infer reading preferences based on your reading history.</p><h4>USE OF INFORMATION</h4><p>We use the information we collect to provide, maintain, and improve our Services, which includes publishing and distributing user-generated content, personalizing the posts you see and operating our metered paywall. We also use the information we collect to:</p><ul><li>Create and maintain your Medium account;</li><li>Process transactions and send related information, such as confirmations, receipts, and user experience surveys;</li><li>Send you technical notices, security alerts, and support and administrative messages;</li><li>Respond to your comments and questions and provide customer service;</li><li>Communicate with you about new content, products, services, and features offered by Medium and provide other news and information we think will interest you (see Your Choices below for information about how to opt out of these communications at any time);</li><li>Monitor and analyze trends, usage, and activities in connection with our Services;</li><li>Detect, investigate, and prevent security incidents and other malicious, deceptive, fraudulent, or illegal activity and protect the rights and property of Medium and others;</li><li>Debug to identify and repair errors in our Services;</li><li>Comply with our legal and financial obligations; and</li><li>Carry out any other purpose described to you at the time the information was collected.</li></ul><h4>SHARING OF INFORMATION</h4><p>We share personal information in the following circumstances or as otherwise described in this policy:</p><ul><li>We share personal information with other users of the Services. For example, if you use our Services to publish content, post comments or send private notes, certain information about you will be visible to others, such as your name, photo, bio, other account information you may provide, and information about your activities on our Services (e.g., your followers and who you follow, recent posts, claps, highlights, and responses).</li><li>We share personal information with vendors, service providers, and consultants that need access to personal information in order to perform services for us, such as companies that assist us with web hosting, storage, and other infrastructure, analytics, payment processing, fraud prevention and security, customer service, communications, and marketing.</li><li>We may disclose personal information if we believe that disclosure is in accordance with, or required by, any applicable law or legal process, including lawful requests by public authorities to meet national security or law enforcement requirements. If we are going to disclose your personal information in response to legal process, we will give you notice so you can challenge it (for example by seeking court intervention), unless we are prohibited by law or believe doing so may endanger others or cause illegal conduct. We will object to legal requests for information about users of our Services that we believe are improper.</li><li>We may share personal information if we believe that your actions are inconsistent with our <a href=\"https://policy.medium.com/\">user agreements or policies</a>, if we believe that you have violated the law, or if we believe it is necessary to protect the rights, property, and safety of Medium, our users, the public, or others.</li><li>We share personal information with our lawyers and other professional advisors where necessary to obtain advice or otherwise protect and manage our business interests.</li><li>We may share personal information in connection with, or during negotiations concerning, any merger, sale of company assets, financing, or acquisition of all or a portion of our business by another company.</li><li>Personal information is shared between and among Medium and our current and future parents, affiliates, and subsidiaries and other companies under common control and ownership.</li><li>We share personal information with your consent or at your direction.</li><li>We also share aggregated or de-identified information that cannot reasonably be used to identify you.</li></ul><h4>HELPING YOU CONNECT WITH PEOPLE YOU KNOW</h4><p>When you use our address book feature, we process contact information from your device to help you discover people you know who are on Medium.</p><p>Why we do this: We rely on legitimate interests to offer this feature — specifically, our interest in helping you connect with people you know, and our mission to deepen collective understanding through writing, which supports your fundamental right to freedom of expression and information.</p><p>How it works: When you opt in, we convert contact names and email addresses into encrypted, non-reversible identifiers and match them against our member database. We don’t store names or emails in plain text. For contacts who aren’t Medium members, we delete their encrypted identifiers immediately after checking. We delete all encrypted identifiers within 30 days.</p><h4>THIRD-PARTY EMBEDS</h4><p>Medium does not host some of the content displayed on our Services. Users have the ability to post content that is actually hosted by a third party, but is embedded in our pages (an <em>“</em><strong><em>Embed</em></strong><em>”</em>). When you interact with an Embed, it can send information about your interaction to the hosting third party just as if you were visiting the third party’s site directly. For example, when you load a Medium post page with a YouTube video Embed and watch the video, YouTube receives information about your activity, such as your IP address and how much of the video you watch. Medium does not control what information third parties collect through Embeds or what they do with the information. This Privacy Policy does not apply to information collected through Embeds. The privacy policy belonging to the third party hosting the Embed applies to any information the Embed collects, and we recommend you review that policy before interacting with the Embed.</p><h4>TRANSFER OF INFORMATION TO THE UNITED STATES AND OTHER COUNTRIES</h4><p>Medium is headquartered in the United States, and we have operations and service providers in the United States and other countries. Therefore, we and our service providers may transfer your personal information to, or store or access it in, jurisdictions that may not provide levels of data protection that are equivalent to those of your home jurisdiction. For example, we transfer personal data to Amazon Web Services, one of our service providers that processes personal information for us in various data center locations across the globe, including those listed <a href=\"https://aws.amazon.com/about-aws/global-infrastructure/\">here</a>. We will take steps to ensure that your personal information receives an adequate level of protection in the jurisdictions in which we process it.</p><h4>YOUR CHOICES</h4><h4>Account Information</h4><p>You may access, correct, delete and export your account information at any time by logging into the Services and navigating to the<a href=\"https://medium.com/me/settings\"> Settings page</a>. Please note that if you choose to delete your account, we may continue to retain certain information about you as required by law or for our legitimate business purposes.</p><h4>Cookies</h4><p>Most web browsers are set to accept cookies by default. If you prefer, you can usually adjust your browser settings to remove or reject browser cookies. Please note that removing or rejecting cookies could affect the availability and functionality of our Services.</p><h4>Communications Preferences</h4><p>You may opt out of receiving certain communications from us, such as digests, newsletters, and activity notifications, by following the instructions in those communications or through your account’s <a href=\"https://medium.com/me/settings\">Settings page</a>. If you opt out, we may still send you administrative emails, such as those about your account or our ongoing business relations.</p><h4>Mobile Push Notifications</h4><p>With your consent, we may send push notifications to your mobile device. You can deactivate these messages at any time by changing the notification settings on your mobile device.</p><h4>YOUR CALIFORNIA PRIVACY RIGHTS</h4><p>The California Consumer Privacy Act or <em>“</em><strong><em>CCPA</em></strong><em>”</em> (Cal. Civ. Code § 1798.100 et seq.) affords consumers residing in California certain rights with respect to their personal information. If you are a California resident, this section applies to you.</p><p>In the preceding 12 months, we have collected the following categories of personal information: identifiers, commercial information, internet or other electronic network activity information, and inferences. For details about the precise data points we collect and the categories of sources of such collection, please see the Collection of Information section above. We collect personal information for the business and commercial purposes described in the Use of Information section above. In the preceding 12 months, we have disclosed the following categories of personal information for business purposes to the following categories of recipients:</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/646/0*b9fTh2C5b0m9HrZm\" /></figure><p><a href=\"https://medium.com/@Medium/categories-of-personal-information-917cda14ca49\">Link to a text version of this table</a></p><p>Medium does not sell your personal information.</p><p>Subject to certain limitations, you have the right to (1) request to know more about the categories and specific pieces of personal information we collect, use, and disclose about you, (2) request deletion of your personal information, (3) opt out of any sales of your personal information, if we engage in that activity in the future, and (4) not be discriminated against for exercising these rights. You may make these requests by emailing us at <a href=\"mailto:privacy@medium.com\">privacy@medium.com</a> or by completing <a href=\"https://help.medium.com/hc/en-us/requests/new#/360002298134/360051136954/\">this webform</a>. We will verify a webform request by asking you to provide identifying information. We will not discriminate against you if you exercise your rights under the CCPA.</p><p>If we receive your request from an authorized agent, we may ask for evidence that you have provided such agent with a power of attorney or that the agent otherwise has valid written authority to submit requests to exercise rights on your behalf. This may include requiring you to verify your identity. If you are an authorized agent seeking to make a request, please <a href=\"mailto:privacy@medium.com\">contact us</a>.</p><h4>ADDITIONAL DISCLOSURES FOR INDIVIDUALS IN EUROPE</h4><p>If you are located in the European Economic Area (<em>“</em><strong><em>EEA</em></strong><em>”</em>), the United Kingdom, or Switzerland, you have certain rights and protections under applicable law regarding the processing of your personal data, and this section applies to you.</p><h4>Legal Basis for Processing</h4><p>When we process your personal data, we will do so in reliance on the following lawful bases:</p><ul><li>To perform our responsibilities under our contract with you (e.g., providing the products and services you requested).</li><li>When we have a legitimate interest in processing your personal data to operate our business or protect our interests (e.g., to provide, maintain, and improve our products and services, conduct data analytics, and communicate with you).</li><li>To comply with our legal obligations (e.g., to maintain a record of your consents and track those who have opted out of non-administrative communications).</li><li>When we have your consent to do so (e.g., when you opt in to receive non-administrative communications from us). When consent is the legal basis for our processing your personal data, you may withdraw such consent at any time.</li></ul><h4>Data Retention</h4><p>We store personal data associated with your account for as long as your account remains active. If you close your account, we will delete your account data within 14 days. We store other personal data for as long as necessary to carry out the purposes for which we originally collected it and for other legitimate business purposes, including to meet our legal, regulatory, or other compliance obligations.</p><h4>Data Subject Requests</h4><p>Subject to certain limitations, you have the right to request access to the personal data we hold about you and to receive your data in a portable format, the right to ask that your personal data be corrected or erased, and the right to object to, or request that we restrict, certain processing. To exercise your rights:</p><ul><li>If you sign up for a Medium account, you may at any time request an export of your personal information from the <a href=\"https://medium.com/me/settings\">Settings page</a>, or by going to Settings and then selecting Account within our app.</li><li>You may correct information associated with your account from the <a href=\"https://medium.com/me/settings\">Settings page</a>, or by going to Settings and then selecting Account within our app, and the <a href=\"https://medium.com/me/following/suggestions\">Customize Your Interests page</a> to update your interests.</li><li>You may withdraw consent by deleting your account at any time through the <a href=\"https://medium.com/me/settings\">Settings page</a>, or by going to Settings and then selecting Account within our app (except to the extent Medium is prevented by law from deleting your information).</li><li>You may object at any time to the use of your personal data by contacting <a href=\"mailto:privacy@medium.com\">privacy@medium.com</a>.</li></ul><h4>Questions or Complaints</h4><p>If you have a concern about our processing of personal data that we are not able to resolve, you have the right to lodge a complaint with the Data Protection Authority where you reside. Contact details for your Data Protection Authority can be found using the links below:</p><ul><li>For individuals in the EEA:<br><a href=\"https://edpb.europa.eu/about-edpb/board/members_en\">https://edpb.europa.eu/about-edpb/board/members_en</a></li><li>For individuals in the UK:<br><a href=\"https://ico.org.uk/global/contact-us/\">https://ico.org.uk/global/contact-us/</a></li><li>For individuals in Switzerland: <a href=\"https://www.edoeb.admin.ch/edoeb/en/home/the-fdpic/contact.html\">https://www.edoeb.admin.ch/edoeb/en/home/the-fdpic/contact.html</a></li></ul><h4>CONTACT US</h4><p>If you have any questions about this Privacy Policy, please contact us at <a href=\"mailto:privacy@medium.com\">privacy@medium.com</a>.</p><p>If you are from the EEA or the United Kingdom and have questions about this Privacy Policy, please contact us at <a href=\"mailto:privacy@medium.com\">privacy@medium.com</a> or our privacy representatives as follows:</p><h4>Privacy representative for EEA</h4><p>Unit 3D North Point House<br>North Point Business Park<br>New Mallow Road<br>Cork T23AT2P<br>Ireland</p><p>Or <a href=\"https://verasafe.com/public-resources/contact-data-protection-representative\">here</a>.</p><h4>Privacy representative for the United Kingdom</h4><p>37 Albert Embankment<br>London SE1 7TL<br>United Kingdom</p><p>Or <a href=\"https://verasafe.com/public-resources/contact-data-protection-representative\">here</a>.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f03bf92035c9\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://policy.medium.com/medium-privacy-policy-f03bf92035c9\">Medium Privacy Policy</a> was originally published in <a href=\"https://policy.medium.com\">Medium Policy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/9db0094a1e0f","link":"https://policy.medium.com/medium-terms-of-service-9db0094a1e0f?source=rss-504c7870fdb6------2","title":"Medium Terms of Service","author":"Medium","excerpt":"Effective: September 1, 2020 You can see our previous Terms here . Thanks for using Medium. Our mission is to deepen people’s understanding of the world and spread ideas that matter. These Terms of Service (“ Terms ”) apply to your access to and use of the websites, mobile applications and other onl","updated":"2026-05-12T19:40:11.374Z","published":"Tue, 01 Sep 2020 22:54:00 GMT","categories":["medium","terms","terms-and-conditions"],"content_html":"<h4><strong>Effective: September 1, 2020</strong></h4><p><strong><em>You can see our previous Terms </em></strong><a href=\"https://help.medium.com/hc/en-us/articles/360053078253\"><strong><em>here</em></strong></a><strong><em>.</em></strong></p><p>Thanks for using Medium. Our mission is to deepen people’s understanding of the world and spread ideas that matter.</p><p>These Terms of Service (“<strong><em>Terms</em></strong>”) apply to your access to and use of the websites, mobile applications and other online products and services (collectively, the “<strong><em>Services</em></strong>”) provided by A Medium Corporation (“<strong><em>Medium</em></strong>” or “<strong><em>we</em></strong>”). <strong>By clicking your consent (e.g. “Continue,” “Sign-in,” or “Sign-up,”) or by using our Services, you agree to these Terms, including the mandatory arbitration provision and class action waiver in the Resolving Disputes; Binding Arbitration Section.</strong></p><p>Our <a href=\"https://policy.medium.com/medium-privacy-policy-f03bf92035c9\">Privacy Policy</a> explains how we collect and use your information while our <a href=\"https://policy.medium.com/medium-rules-30e5502c4eb4\">Rules</a> outline your responsibilities when using our Services. By using our Services, you’re agreeing to be bound by these Terms and our Rules. Please see our <a href=\"https://policy.medium.com/medium-privacy-policy-f03bf92035c9\">Privacy Policy</a> for information about how we collect, use, share and otherwise process information about you.</p><p>If you have any questions about these Terms or our Services, please contact us at <a href=\"mailto:legal@medium.com\">legal@medium.com</a>.</p><h3>Your Account and Responsibilities</h3><p>You’re responsible for your use of the Services and any content you provide, including compliance with applicable laws. Content on the Services may be protected by others’ intellectual property rights. Please don’t copy, upload, download, or share content unless you have the right to do so.</p><p>Your use of the Services must comply with our Rules.</p><p>You may need to register for an account to access some or all of our Services. Help us keep your account protected. Safeguard your password to the account, and keep your account information current. We recommend that you do not share your password with others.</p><p>If you’re accepting these Terms and using the Services on behalf of someone else (such as another person or entity), you represent that you’re authorized to do so, and in that case the words “you” or “your” in these Terms include that other person or entity.</p><p>To use our Services, you must be at least 13 years old.</p><p>If you use the Services to access, collect, or use personal information about other Medium users (“Personal Information”), you agree to do so in compliance with applicable laws. You further agree not to sell any Personal Information, where the term “sell” has the meaning given to it under applicable laws.</p><p>For Personal Information you provide to us, you represent and warrant that you have lawfully collected the Personal Information and that you or a third party has provided all required notices and collected all required consents before collecting the Personal Information. You further represent and warrant that Medium’s use of such Personal Information in accordance with the purposes for which you provided us the Personal Information will not violate, misappropriate or infringe any rights of another (including intellectual property rights or privacy rights) and will not cause us to violate any applicable laws.</p><h3>User Content on the Services</h3><p>Medium may review your conduct and content for compliance with these Terms and our Rules, and reserves the right to remove any violating content.</p><p>Medium reserves the right to delete or disable content alleged to be infringing the intellectual property rights of others, and to terminate accounts of repeat infringers. We respond to notices of alleged copyright infringement if they comply with the law; please report such notices using our <a href=\"https://help.medium.com/hc/en-us/articles/214120487-Copyright-DMCA-Policy\">Copyright Policy</a>.</p><h3>Rights and Ownership</h3><p>You retain your rights to any content you submit, post or display on or through the Services. <br><br>Unless otherwise agreed in writing, by submitting, posting, or displaying content on or through the Services, you grant Medium a nonexclusive, royalty-free, worldwide, fully paid, and sublicensable license to use, reproduce, modify, adapt, publish, translate, create derivative works from, distribute, publicly perform and display your content and any name, username or likeness provided in connection with your content in all media formats and distribution methods now known or later developed on the Services.</p><p>Medium needs this license because you own your content and Medium therefore can’t display it across its various surfaces (i.e., mobile, web) without your permission.</p><p>This type of license also is needed to distribute your content across our Services. For example, you post a story on Medium. It is reproduced as versions on both our website and app, and distributed to multiple places within Medium, such as the homepage or reading lists. A modification might be that we show a snippet of your work (and not the full post) in a preview, with attribution to you. A derivative work might be a list of top authors or quotes on Medium that uses portions of your content, again with full attribution. This license applies to our Services only, and does not grant us any permissions outside of our Services.</p><p>So long as you comply with these Terms, Medium gives you a limited, personal, non-exclusive, and non-assignable license to access and use our Services.</p><p>The Services are protected by copyright, trademark, and other US and foreign laws. These Terms don’t grant you any right, title or interest in the Services, other users’ content on the Services, or Medium trademarks, logos or other brand features.</p><p>Separate and apart from the content you submit, post or display on our Services, we welcome feedback, including any comments, ideas and suggestions you have about our Services. We may use this feedback for any purpose, in our sole discretion, without any obligation to you. We may treat feedback as nonconfidential.</p><p>We may stop providing the Services or any of its features within our sole discretion. We also retain the right to create limits on use and storage and may remove or limit content distribution on the Services.</p><h3>Termination</h3><p>You’re free to stop using our Services at any time. We reserve the right to suspend or terminate your access to the Services with or without notice.</p><h3>Transfer and Processing Data</h3><p>In order for us to provide our Services, you agree that we may process, transfer and store information about you in the US and other countries, where you may not have the same rights and protections as you do under local law.</p><h3>Indemnification</h3><p>To the fullest extent permitted by applicable law, you will indemnify, defend and hold harmless Medium, and our officers, directors, agents, partners and employees (individually and collectively, the <em>“Medium Parties”</em>) from and against any losses, liabilities, claims, demands, damages, expenses or costs (<em>“Claims”</em>) arising out of or related to your violation, misappropriation or infringement of any rights of another (including intellectual property rights or privacy rights) or your violation of the law. You agree to promptly notify Medium Parties of any third-party Claims, cooperate with Medium Parties in defending such Claims and pay all fees, costs and expenses associated with defending such Claims (including attorneys’ fees). You also agree that the Medium Parties will have control of the defense or settlement, at Medium’s sole option, of any third-party Claims.</p><h3>Disclaimers — Service is “As Is”</h3><p><strong>Medium aims to give you great Services but there are some things we can’t guarantee. Your use of our Services is at your sole risk. You understand that our Services and any content posted or shared by users on the Services are provided “as is” and “as available” without warranties of any kind, either express or implied, including implied warranties of merchantability, fitness for a particular purpose, title, and non-infringement. In addition, Medium doesn’t represent or warrant that our Services are accurate, complete, reliable, current or error-free. No advice or information obtained from Medium or through the Services will create any warranty or representation not expressly made in this paragraph. Medium may provide information about third-party products, services, activities or events, or we may allow third parties to make their content and information available on or through our Services (collectively, “<em>Third-Party Content</em>”). We do not control or endorse, and we make no representations or warranties regarding, any Third-Party Content. You access and use Third-Party Content at your own risk. Some locations don’t allow the disclaimers in this paragraph and so they might not apply to you.</strong></p><h3>Limitation of Liability</h3><p><strong>We don’t exclude or limit our liability to you where it would be illegal to do so; this includes any liability for the gross negligence, fraud or intentional misconduct of Medium or the other Medium Parties in providing the Services. In countries where the following types of exclusions aren’t allowed, we’re responsible to you only for losses and damages that are a reasonably foreseeable result of our failure to use reasonable care and skill or our breach of our contract with you. This paragraph doesn’t affect consumer rights that can’t be waived or limited by any contract or agreement.</strong></p><p><strong>In countries where exclusions or limitations of liability are allowed, Medium and Medium Parties won’t be liable for:</strong></p><p><strong>(a)</strong> <strong>Any indirect, consequential, exemplary, incidental, punitive, or special damages, or any loss of use, data or profits, under any legal theory, even if Medium or the other Medium Parties have been advised of the possibility of such damages.</strong></p><p><strong>(b)</strong> <strong>Other than for the types of liability we can’t limit by law (as described in this section), we limit the total liability of Medium and the other Medium Parties for any claim arising out of or relating to these Terms or our Services, regardless of the form of the action, to the greater of $50.00 USD or the amount paid by you to use our Services.</strong></p><h3>Resolving Disputes; Binding Arbitration</h3><p>We want to address your concerns without needing a formal legal case. Before filing a claim against Medium, you agree to contact us and attempt to resolve the claim informally by sending a written notice of your claim by email at legal@medium.com or by certified mail addressed to A Medium Corporation, 3500 South DuPont Highway Suite IQ-101 Dover, DE 19901. The notice must (a) include your name, residence address, email address, and telephone number; (b) describe the nature and basis of the claim; and (c) set forth the specific relief sought. Our notice to you will be sent to the email address associated with your online account and will contain the information described above. If we can’t resolve matters within thirty (30) days after any notice is sent, either party may initiate a formal proceeding.</p><p><strong>Please read the following section carefully because it requires you to arbitrate certain disputes and claims with Medium and limits the manner in which you can seek relief from us, unless you opt out of arbitration by following the instructions set forth below. No class or representative actions or arbitrations are allowed under this arbitration provision. In addition, arbitration precludes you from suing in court or having a jury trial.</strong></p><p>(a) <strong>No Representative Actions. You and Medium agree that any dispute arising out of or related to these Terms or our Services is personal to you and Medium and that any dispute will be resolved solely through individual action, and will not be brought as a class arbitration, class action or any other type of representative proceeding.</strong></p><p>(b) <strong>Arbitration of Disputes. </strong>Except for small claims disputes in which you or Medium seeks to bring an individual action in small claims court located in the county where you reside or disputes in which you or Medium seeks injunctive or other equitable relief for the alleged infringement or misappropriation of intellectual property, <strong>you and Medium waive your rights to a jury trial and to have any other dispute arising out of or related to these Terms or our Services, including claims related to privacy and data security, (collectively, “<em>Disputes</em>”) resolved in court</strong>. All Disputes submitted to JAMS will be resolved through confidential, binding arbitration before one arbitrator. Arbitration proceedings will be held in San Francisco, California unless you’re a consumer, in which case you may elect to hold the arbitration in your county of residence. For purposes of this section a “<strong><em>consumer</em></strong>” means a person using the Services for personal, family or household purposes. You and Medium agree that Disputes will be held in accordance with the JAMS Streamlined Arbitration Rules and Procedures (“<strong><em>JAMS Rules</em></strong>”). The most recent version of the JAMS Rules are available on the<a href=\"https://www.jamsadr.com/rules-streamlined-arbitration/\"> JAMS website</a> and are incorporated into these Terms by reference. You either acknowledge and agree that you have read and understand the JAMS Rules or waive your opportunity to read the JAMS Rules and waive any claim that the JAMS Rules are unfair or should not apply for any reason.</p><p>(c) You and Medium agree that these Terms affect interstate commerce and that the enforceability of this section will be substantively and procedurally governed by the Federal Arbitration Act, 9 U.S.C. § 1, <em>et seq</em>. (the “<strong><em>FAA</em></strong>”), to the maximum extent permitted by applicable law. As limited by the FAA, these Terms and the JAMS Rules, the arbitrator will have exclusive authority to make all procedural and substantive decisions regarding any Dispute and to grant any remedy that would otherwise be available in court, including the power to determine the question of arbitrability. The arbitrator may conduct only an individual arbitration and may not consolidate more than one individual’s claims, preside over any type of class or representative proceeding or preside over any proceeding involving more than one individual.</p><p>(d) The arbitration will allow for the discovery or exchange of non-privileged information relevant to the Dispute. The arbitrator, Medium, and you will maintain the confidentiality of any arbitration proceedings, judgments and awards, including information gathered, prepared and presented for purposes of the arbitration or related to the Dispute(s) therein. The arbitrator will have the authority to make appropriate rulings to safeguard confidentiality, unless the law provides to the contrary. The duty of confidentiality doesn’t apply to the extent that disclosure is necessary to prepare for or conduct the arbitration hearing on the merits, in connection with a court application for a preliminary remedy, or in connection with a judicial challenge to an arbitration award or its enforcement, or to the extent that disclosure is otherwise required by law or judicial decision.</p><p>(e) You and Medium agree that for any arbitration you initiate, you will pay the filing fee (up to a maximum of $250 if you are a consumer), and Medium will pay the remaining JAMS fees and costs. For any arbitration initiated by Medium, Medium will pay all JAMS fees and costs. You and Medium agree that the state or federal courts of the State of California and the United States sitting in San Francisco, California have exclusive jurisdiction over any appeals and the enforcement of an arbitration award.</p><p>(f) <strong>Any Dispute must be filed within one year after the relevant claim arose; otherwise, the Dispute is permanently barred, which means that you and Medium will not have the right to assert the claim.</strong></p><p>(g) <strong>You have the right to opt out of binding arbitration within 30 days of the date you first accepted the terms of this section by sending an email of your request to trust@medium.com</strong>. In order to be effective, the opt-out notice must include your full name and address and clearly indicate your intent to opt out of binding arbitration. By opting out of binding arbitration, you are agreeing to resolve Disputes in accordance with the next section regarding “Governing Law and Venue.”</p><p>(h) If any portion of this section is found to be unenforceable or unlawful for any reason, (1) the unenforceable or unlawful provision shall be severed from these Terms; (2) severance of the unenforceable or unlawful provision shall have no impact whatsoever on the remainder of this section or the parties’ ability to compel arbitration of any remaining claims on an individual basis pursuant to this section; and (3) to the extent that any claims must therefore proceed on a class, collective, consolidated, or representative basis, such claims must be litigated in a civil court of competent jurisdiction and not in arbitration, and the parties agree that litigation of those claims shall be stayed pending the outcome of any individual claims in arbitration. Further, if any part of this section is found to prohibit an individual claim seeking public injunctive relief, that provision will have no effect to the extent such relief is allowed to be sought out of arbitration, and the remainder of this section will be enforceable.</p><h3><strong>Governing Law and Venue</strong></h3><p>These Terms and any dispute that arises between you and Medium will be governed by California law except for its conflict of law principles. Any dispute between the parties that’s not subject to arbitration or can’t be heard in small claims court will be resolved in the state or federal courts of California and the United States, respectively, sitting in San Francisco, California.</p><p>Some countries have laws that require agreements to be governed by the local laws of the consumer’s country. This paragraph doesn’t override those laws.</p><h3>Amendments</h3><p>We may make changes to these Terms from time to time. If we make changes, we’ll provide you with notice of them by sending an email to the email address associated with your account, offering an in-product notification, or updating the date at the top of these Terms. Unless we say otherwise in our notice, the amended Terms will be effective immediately, and your continued use of our Services after we provide such notice will confirm your acceptance of the changes. If you don’t agree to the amended Terms, you must stop using our Services.</p><h3>Severability</h3><p>If any provision or part of a provision of these Terms is unlawful, void or unenforceable, that provision or part of the provision is deemed severable from these Terms and does not affect the validity and enforceability of any remaining provisions.</p><h3>Miscellaneous</h3><p>Medium’s failure to exercise or enforce any right or provision of these Terms will not operate as a waiver of such right or provision. These Terms, and the terms and policies listed in the Other Terms and Policies that May Apply to You Section, reflect the entire agreement between the parties relating to the subject matter hereof and supersede all prior agreements, statements and understandings of the parties. The section titles in these Terms are for convenience only and have no legal or contractual effect. Use of the word “including” will be interpreted to mean “including without limitation.” Except as otherwise provided herein, these Terms are intended solely for the benefit of the parties and are not intended to confer third-party beneficiary rights upon any other person or entity. You agree that communications and transactions between us may be conducted electronically.</p><h3><strong>Other Terms and Policies that May Apply to You</strong></h3><p>- <a href=\"https://policy.medium.com/medium-rules-30e5502c4eb4\">Medium Rules</a><br>- <a href=\"https://policy.medium.com/medium-partner-program-terms-fcfe9cf777b8\">Partner Program Terms</a><br>- <a href=\"https://policy.medium.com/paid-terms-of-service-cc7f8e165178\">Membership Terms of Service</a><br>- <a href=\"https://policy.medium.com/medium-username-policy-7054a77fb04f\">Username Policy</a><br>- Amendment to Medium Terms of Service Applicable to U.S. Government Users</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9db0094a1e0f\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://policy.medium.com/medium-terms-of-service-9db0094a1e0f\">Medium Terms of Service</a> was originally published in <a href=\"https://policy.medium.com\">Medium Policy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/bad566e3f7da","link":"https://medium.com/blog/clarifying-mediums-new-terms-of-service-bad566e3f7da?source=rss-504c7870fdb6------2","title":"Clarifying Medium’s new Terms of Service","author":"Medium","excerpt":"We appreciate the feedback about the language in our updated Terms of Service that focuses on your content rights. We first responded by preparing a short blog post explaining the updates. We’ve now edited the Terms of Service to more clearly reflect what we said in that post. Here’s the passage we’","updated":"2020-08-20T01:43:17.837Z","published":"Thu, 20 Aug 2020 01:21:16 GMT","categories":["ｍedium","terms-of-service"],"content_html":"<p>We appreciate the feedback about the language in our updated Terms of Service that focuses on your content rights. We first responded by preparing a <a href=\"https://blog.medium.com/our-updated-terms-and-privacy-policy-d03bed9978d8\">short blog post</a> explaining the updates.</p><p>We’ve now edited the <a href=\"https://policy.medium.com/medium-terms-of-service-9db0094a1e0f\">Terms of Service</a> to more clearly reflect what we said in that post.</p><p>Here’s the passage we’ve updated:</p><blockquote><strong>Rights and Ownership</strong></blockquote><blockquote>You retain your rights to any content you submit, post or display on or through the Services.</blockquote><blockquote>Unless otherwise agreed in writing, by submitting, posting, or displaying content on or through the Services, you grant Medium a nonexclusive, royalty-free, worldwide, fully paid, and sublicensable license to use, reproduce, modify, adapt, publish, translate, create derivative works from, distribute, publicly perform and display your content and any name, username or likeness provided in connection with your content in all media formats and distribution methods now known or later developed on the Services.</blockquote><blockquote>Medium needs this license because you own your content and Medium therefore can’t display it across its various surfaces (i.e., mobile, web) without your permission.</blockquote><blockquote>This type of license also is needed to distribute your content across our Services. For example, you post a story on Medium. It is reproduced as versions on both our website and app, and distributed to multiple places within Medium, such as the homepage or reading lists. A modification might be that we show a snippet of your work (and not the full post) in a preview, with attribution to you. A derivative work might be a list of top authors or quotes on Medium that uses portions of your content, again with full attribution. This license applies to our Services only, and does not grant us any permissions outside of our Services.</blockquote><p>This license doesn’t give Medium permission to sell your content to a third party, and we’ll never do that. You’re not granting us permission to use your content outside of Medium. You’re also not granting us copyright to, or ownership of, your content. So, for example, we’ll never claim the right to develop your content into materials such as books, films, or television shows without your knowledge and express consent.</p><p>You’ll notice this license is royalty-free. This means posting to our services doesn’t involve any compensation to you (unless you’re in our Partner Program), just as you wouldn’t get compensated for posting to Twitter, Facebook, or LinkedIn.</p><p>And speaking of our Partner Program, these Terms of Service (just like the previous version of Medium’s Terms of Service) do <strong>not</strong> alter, modify, or supersede that program. All content you decide to put behind our paywall for monetization still earns you money based on subscribed Medium Member engagement. We’re not de-monetizing that content or changing the Partner Program terms.</p><p>To restate:</p><ul><li>You own all the content you post on Medium, and we make no claims to it, nor will we ever in the future.</li><li>We do not, and will not, sell your content or information. Ever.</li></ul><p>If you have any further questions, the best way to contact us is by emailing <a href=\"mailto:yourfriends@medium.com\">yourfriends@medium.com</a>.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bad566e3f7da\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/blog/clarifying-mediums-new-terms-of-service-bad566e3f7da\">Clarifying Medium’s new Terms of Service</a> was originally published in <a href=\"https://medium.com/blog\">The Medium Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/d03bed9978d8","link":"https://medium.com/blog/our-updated-terms-and-privacy-policy-d03bed9978d8?source=rss-504c7870fdb6------2","title":"Our Updated Terms and Privacy Policy","author":"Medium","excerpt":"**Edit 8/19/20: We have updated the language in the terms for more clarification. You can read more here: Clarifying Medium’s new Terms of Service You may have noticed a banner across all of Medium notifying you of our upcoming new Terms of Service and Privacy Policy. We’ve received some questions a","updated":"2020-08-20T01:34:01.849Z","published":"Tue, 18 Aug 2020 20:21:17 GMT","categories":["privacy","ｍedium","medium","terms"],"content_html":"<p>**Edit 8/19/20: We have updated the language in the terms for more clarification. You can read more here:</p><p><a href=\"https://blog.medium.com/clarifying-mediums-new-terms-of-service-bad566e3f7da\">Clarifying Medium’s new Terms of Service</a></p><p>You may have noticed a banner across all of Medium notifying you of our upcoming new Terms of Service and Privacy Policy. We’ve received some questions about the updates, so we’d like to give you more context and information about them.</p><p>While some of the language in these policies has changed, Medium’s fundamental beliefs (and behaviors) have not. Here’s the deal:</p><ul><li><strong>You own all the content you post on Medium, and we make no claims to it, nor will we ever in the future.</strong></li><li><strong>We do not, and will not, sell your information. Ever.</strong></li></ul><p>Here are links to our latest <a href=\"https://policy.medium.com/medium-terms-of-service-9db0094a1e0f\">Terms of Service</a> and <a href=\"https://policy.medium.com/medium-privacy-policy-f03bf92035c9\">Privacy Policy</a>. These go into effect on September 1st, 2020. Until that date, our existing Terms and Privacy Policy remain in effect. The purpose of the banner you’ve seen is to give you a heads-up about upcoming changes plus an opportunity to think about them and ask questions.</p><p>The following section in the new Terms of Service does have updated, but not materially new, language:</p><blockquote><strong>Rights and Ownership</strong></blockquote><blockquote>You retain your rights to any content you submit, post or display on or through the Services.</blockquote><blockquote>Unless otherwise agreed in writing, by submitting, posting, or displaying content on or through the Services, you grant Medium a nonexclusive, royalty-free, worldwide, fully paid, and sublicensable license to use, reproduce, modify, adapt, publish, translate, create derivative works from, distribute, publicly perform and display your content and any name, username or likeness provided in connection with your content in all media formats and distribution methods now known or later developed without compensation to you.</blockquote><p>This is the same as the permissions in our Terms’ previous version. It’s just written in different language that’s more precise.</p><p>What this term means is that you’re <strong>granting us a license</strong> to reproduce the content you post on Medium within the surfaces and products <strong>served by Medium only</strong>. These are the referenced “Medium Services.” These services include our website, our official apps, and any other future product or service that is powered by the Medium network.</p><p>You are NOT granting us permission to use your content outside of the Medium network. You also are NOT granting us copyright to or ownership of your content. For example, we would make no claim of ownership over your copyright content for development into any other materials such as books, films, or television shows without your permission and participation.</p><p>Additionally, nothing about our updated Terms changes or overrides your ability to monetize any content you choose to publish into our Partner Program. Nothing is changing with the Partner Program.</p><blockquote>In short, you still 100% own your content, and you remain in control of it.</blockquote><p>You’re just granting us permission to display it on Medium. If you don’t agree to that, you’re free to export your content at any time from your <a href=\"http://medium.com/me/settings\">Settings Page</a>, and you retain complete ownership over it. You can also delete your account on that same page.</p><p>Additionally, we have moved the language regarding sale of your information from the Terms to our <a href=\"https://policy.medium.com/medium-privacy-policy-f03bf92035c9\">Privacy Policy</a>. But it remains the same:</p><blockquote><strong>Medium does not sell your personal information.</strong></blockquote><p>Ever. Full stop. We have made a commitment to be an ad-free platform and, as such, we will not sell your information.</p><p>Additionally, we have clarified CCPA compliance and rights for California residents to our Privacy Policy, and added the option for all users to opt-out of binding arbitration in our Terms of Service.</p><p>Medium continues to respect and promote the rights of creators and users, and we stand firm in our commitment that you alone own and control your work and your data.</p><p>If you have any further questions, please send us an email to <a href=\"mailto:yourfriends@medium.com\">yourfriends@medium.com</a>.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d03bed9978d8\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/blog/our-updated-terms-and-privacy-policy-d03bed9978d8\">Our Updated Terms and Privacy Policy</a> was originally published in <a href=\"https://medium.com/blog\">The Medium Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/503dd91c2edd","link":"https://medium.com/blog/medium-the-diversity-report-503dd91c2edd?source=rss-504c7870fdb6------2","title":"Medium: The Diversity Report","author":"Medium","excerpt":"2018 Edition We believe that having diverse perspectives and voices within our workforce is vital for us to offer diverse perspectives and voices to our readers, and the only way to have meaningful diversity at work is to foster an inclusive environment. Diversity for us includes, but is not limited","updated":"2019-01-25T20:36:51.935Z","published":"Fri, 25 Jan 2019 18:14:07 GMT","categories":["diversity","company-culture","inclusion"],"content_html":"<h4>2018 Edition</h4><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*N-u0fshAxKzms88RzWfGaw.png\" /></figure><p>We believe that having diverse perspectives and voices within our workforce is vital for us to offer diverse perspectives and voices to our readers, and the only way to have meaningful diversity at work is to foster an inclusive environment. Diversity for us includes, but is not limited to, gender, ethnicity, race, culture, socio-economic background, religion, age, physical ability, veteran status, country of origin, primary language, sexuality, political preference, education, and family makeup.</p><h3>The Data</h3><p>Every year, we send an optional survey to our staff to gather demographic information and give employees a chance to voice whether they feel our workplace is diverse and inclusive. We sent the survey to staff hired up until November 13, 2018 and the responses are valid as of November 30, 2018. A few notes on the data:</p><ul><li>The survey is anonymous and optional for all employees. Contractors were not included in this survey. In order to capture some of the subgroups of the survey, we cross-referenced survey data with our internal HR data.</li><li>We separate our statistics into tech — which includes engineering, product, and data science — and non-tech, which includes (but is not limited to) marketing, content, operations, human resources, and design. We do not separate the demographics for other functional groups because their small size precludes anonymity.</li><li>Senior employees are defined as individuals who are in one of the top three levels of our six-tier internal leveling system.</li></ul><h3>Who We Are</h3><p>As of November 13, 2018, we are comprised of 98 employees that identify mostly as introverts and ambiverts. We enjoy both outdoor and indoor activities, with a slight preference for staying indoors. Our current employees are based all over the US, with 75% of the company in San Francisco, 15% in New York and the rest working remotely. The largest team is engineering (46% of Medium) followed by Editorial (13%) and Product (11%).</p><h3>Gender</h3><p>The overall gender split for Medium is 39.6% (-0.6 percentage points from 2017) female and 60.4% male. For the tech roles, the split is 31.5% (-6.5pp from 2017) female and 68.5% male, while for non-tech roles, the ratio of female and male employees is even at 50% (+14pp for female held positions from 2017). In senior positions, we have 27.5% (+4.7pp from 2017) of the positions filled by female employees and the remaining 72.5% filled by male employees.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*IfWNVVOnUcitMyHO\" /></figure><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*mRTGeQSDBjwNpeWD\" /></figure><h3>Race &amp; Ethnicity</h3><p>The current makeup of Medium is 63% of employees that identify as White. That’s an increase of +5pp from the previous year. There are also 24% of employees identifying as Asian, which represents an increase of +2pp from 2017. We have 9% of employees that identify as Black or African American and that’s a decrease of -2pp from last year. Other groups saw a marginal increase of +1–2pp from 2017.</p><p>We do not have any Black, African American, Hispanic, or Latinx employees at the senior level.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*AgFQ6X8nwHSOSWFK\" /></figure><h3>Age, LGBTQ+ &amp; Origin</h3><p>There are other areas of diversity that we measure:</p><ul><li>Most of our employees are between 26–30 years old, but there was a decrease of -8pp from the previous year. The company is trending older and now more than 50% of the employees are 31 years and older.</li><li>14% of our employees identify as LGBTQ+, a jump of +4.3pp from 2017.</li><li>Most Medians were born in America but there’s a significant number (45%) of employees that are either immigrants or born of parents that are immigrants.</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*MltblTnZpD6E4tOQ\" /></figure><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*pi6trHyBDkhxCFqb\" /></figure><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*wK5HLnzn_WqBlg59\" /></figure><h3>Inclusion</h3><p>We asked employees who they felt should be better represented at Medium. Combining direct answers to this question with free-form comments, 61% of respondents expressed a desire for a more diverse leadership team. Other areas where people felt Medium lacks diverse representation include: race &amp; ethnicity, age, and political views.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*SytbY7TWSIWk59Uk\" /></figure><p>The free form comments also capture that as we continue to grow and become increasingly distributed as a workforce, we have an opportunity to ensure better parity around benefits and social events between remote worker and workers in our San Francisco office.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=503dd91c2edd\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/blog/medium-the-diversity-report-503dd91c2edd\">Medium: The Diversity Report</a> was originally published in <a href=\"https://medium.com/blog\">The Medium Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/aeee8ef5dacf","link":"https://medium.com/blog/ad-free-medium-aeee8ef5dacf?source=rss-504c7870fdb6------2","title":"Ad-Free Medium","author":"Medium","excerpt":"We’ve been putting our energy at Medium into improving quality — hosting more thoughtful and carefully written content, and improving the reading experience. We want to eliminate distractions that get in the way of reading flow and reduce distortions that could undermine your trust in what you read ","updated":"2018-07-17T18:37:47.385Z","published":"Tue, 17 Jul 2018 18:37:47 GMT","categories":["medium","policy"],"content_html":"<p>We’ve been putting our <a href=\"https://blog.medium.com/the-medium-model-3ec28c6f603a\">energy at Medium</a> into improving quality — hosting more thoughtful and carefully written content, and improving the reading experience. We want to eliminate distractions that get in the way of reading flow and reduce distortions that could undermine your trust in what you read on Medium.</p><p>To that end, we‘re adjusting our policies related to commercial content.</p><h3>Ads, Promotions, and Marketing</h3><p>First, we’ll be restricting some kinds of commercial activity that have been allowed in the past, such as sponsorships and links that are mainly promotional. Medium’s business has been ad-free for more than a year now. But some “ad-like” remnants that had value in the past are now less aligned with Medium’s model of using subscriptions and a metered paywall to provide the best quality user experience. This means eliminating certain features that are superficially free, but for which you pay with attention.</p><p>So, as of September 1, 2018, our policies will include:</p><ul><li><strong>First-party promotion is allowed.</strong> You can promote your own work or goods and services you provide, like a link to your website or your book. For posts or publications run by a company (like company blogs), you can promote goods or services provided by your company.</li><li><strong>Third-party advertising and sponsorships are not allowed. </strong>You may not advertise or promote third-party products, services, or brands through Medium posts, publications, or letters. This includes images that indicate brand sponsorship in a post or letter, or as part of a publication name or logo.</li><li><strong>Images functioning as third-party ads are not allowed.</strong> Inline images or embeds that link out and function as banner ads for third-party brands will no longer be allowed.</li><li><strong>You must disclose affiliate links or payment for a post. </strong>Affiliate links, such as link out to Amazon with your code, or any other link out where you will receive a commission or other value, are allowed in posts. But, you must disclose somewhere in the post that it includes affiliate links. If you have received payment, goods or services, or something else of value in exchange for writing a post, you must still disclose this fact in writing within your post (as <a href=\"https://www.ftc.gov/tips-advice/business-center/guidance/ftcs-endorsement-guides-what-people-are-asking\">FTC Rules and Guides</a>, and <a href=\"https://medium.com/policy/medium-rules-30e5502c4eb4\">Medium Rules</a> require).</li></ul><h3>Embedded Content</h3><p>Second, in preparing to comply with GDPR (the European Union’s new General Data Protection Regulation), it led us to step back, take a fresh look at our privacy practices, and renew our efforts to make Medium trustworthy. It raised questions about what types of data collection we should and should not allow through Medium. And in other cases, it led us to think about how to channel third-party data collection on Medium to make sure our users understand as clearly as possible what information about them is captured, by whom, where it winds up, and how it might be used.</p><p>As of September 1, 2018:</p><ul><li>Embeds that directly collect data through form fields will no longer be allowed.</li></ul><p>This includes embeds that facilitate the submission of email addresses and other personally identifying information through forms (such as Upscribe or Rabbut) and the submission of credit card information (such as Gumroad). We understand that this might make life harder for writers and publications who want to collect information directly from users. But, we believe this change is necessary to ensure that Medium readers know where their data is going and how it will be used.</p><p>Going forward, if you want to collect information from your users, you will need to link out from your post on Medium to a form hosted elsewhere that makes it clear to a user that they are no longer in the Medium network. We are working with Upscribe, Rabbut, and Gumroad so that their services function in line with these Medium policies.</p><p>We will continue to allow embeds that display content, such as YouTube, Vimeo, Soundcloud, GitHub, and others, as explained in our <a href=\"https://help.medium.com/hc/en-us/articles/214981378-Embeds\">Help Center</a>. Our <a href=\"https://medium.com/policy/medium-privacy-policy-f03bf92035c9\">Privacy Policy</a> discusses in more detail how we treat this type of embed.</p><p>Thank you. We’re grateful for your continued use of Medium and appreciate your working with us to make Medium a place for thoughtful, high-quality stories.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aeee8ef5dacf\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/blog/ad-free-medium-aeee8ef5dacf\">Ad-Free Medium</a> was originally published in <a href=\"https://medium.com/blog\">The Medium Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"}]},"meta":{"timestamp":"2026-06-02T16:53:18.677Z","request_id":"4a4bca42-587d-4120-837e-7ab6f87a6765"},"status":"ok","message":"User posts","success":true}}}},"401":{"description":"Missing or invalid x-oanor-key header"},"402":{"description":"Active subscription required"},"429":{"description":"Rate-limit or monthly quota reached"},"502":{"description":"Upstream did not respond"}}}},"/v1/meta":{"get":{"operationId":"get_v1_meta","tags":["Meta"],"summary":"Spec","description":"","parameters":[],"security":[{"oanorKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"auth":"none upstream; gateway requires x-api-key","name":"Medium API","note":"Live, no cache. Returns recent posts (RSS-limited, typically up to 10) with title, author, link, categories and full content HTML. user = @handle, tag = topic slug, publication = publication slug.","source":"Medium public RSS feeds (medium.com/feed/...)","endpoints":4},"meta":{"timestamp":"2026-06-02T16:53:18.818Z","request_id":"0730090c-9cd6-48bf-9e55-c174374c75db"},"status":"ok","message":"Meta","success":true}}}},"401":{"description":"Missing or invalid x-oanor-key header"},"402":{"description":"Active subscription required"},"429":{"description":"Rate-limit or monthly quota reached"},"502":{"description":"Upstream did not respond"}}}},"/v1/publication-tag":{"get":{"operationId":"get_v1_publication_tag","tags":["Feeds"],"summary":"A publication's posts filtered to one tag","description":"","parameters":[{"name":"publication","in":"query","required":true,"description":"Publication slug","schema":{"type":"string"},"example":"better-programming"},{"name":"tag","in":"query","required":true,"description":"Tag slug","schema":{"type":"string"},"example":"programming"}],"security":[{"oanorKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"feed":{"link":"https://betterprogramming.pub/tagged/programming?source=rss----d0b105d10f0a--programming","image":"https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png","title":"Programming in Better Programming on Medium","description":"Latest stories tagged with Programming in Better Programming on Medium"},"count":10,"items":[{"guid":"https://medium.com/p/3ee5d41f81a4","link":"https://medium.com/better-programming/3-fundamental-concepts-to-fully-understand-how-the-fetch-api-works-3ee5d41f81a4?source=rss----d0b105d10f0a--programming","title":"3 Fundamental Concepts to Fully Understand how the Fetch API Works","author":"Jay Cruz","updated":"2023-11-10T17:30:24.902Z","published":"Fri, 10 Nov 2023 17:30:24 GMT","categories":["fetch-api","software-development","javascript","web-development","programming"]},{"guid":"https://medium.com/p/48809a853fae","link":"https://medium.com/better-programming/deploy-coreml-models-on-the-server-with-vapor-48809a853fae?source=rss----d0b105d10f0a--programming","title":"Deploy CoreML Models on the Server with Vapor","author":"Drew Althage","excerpt":"Get the benefits of Apple’s ML tools server-side. SwiftUI client showing image classification results Recently, at Sovrn , we had an AI Hackathon where we were encouraged to experiment with anything related to machine learning. The Hackathon yielded some fantastic projects from across the company. E","updated":"2023-11-10T17:30:15.819Z","published":"Fri, 10 Nov 2023 17:30:15 GMT","categories":["swift","programming","ios","machine-learning","apple"],"content_html":"<p>Get the benefits of Apple’s ML tools server-side.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*XMjKfWYZYPikQ0pJ391YaA.png\" /><figcaption>SwiftUI client showing image classification results</figcaption></figure><p>Recently, at <a href=\"https://www.sovrn.com/\">Sovrn</a>, we had an AI Hackathon where we were encouraged to experiment with anything related to machine learning. The Hackathon yielded some fantastic projects from across the company. Everything from SQL query generators to chatbots that can answer questions about our products and other incredible work. I thought this would be a great opportunity to learn more about Apple’s ML tools and maybe even build something with real business value.</p><p>A few of my colleagues and I teamed up to play with CreateML and CoreML to see if we could integrate some ML functionality into our iOS app. We got a model trained and integrated into our app in several hours, which was pretty amazing. But we quickly realized that we had a few problems to solve before we could actually ship this thing.</p><ul><li>The model was hefty. It was about 50MB. That’s a lot of space to take up in our app bundle.</li><li>We wanted to update the model without releasing a new app version.</li><li>We wanted to use the model in the web browser as well.</li></ul><p>We didn’t have time to solve all of these problems. But the other day I was exploring the <a href=\"https://vapor.codes/\">Vapor</a> web framework and the thought hit me, “Why not deploy CoreML models on the server?”</p><p>Apple provides a few pre-trained models, so today we’ll deploy an image classification model on the server behind a REST API with Vapor and create a SwiftUI client to consume it.</p><h3>Foreword</h3><p>This prototype is just that, a prototype. It’s not meant to be a production-ready solution. It’s meant to be a proof of concept. There will be warnings in the console, and the code won’t be very clean, but it will work and hopefully get your wheels turning.</p><p>If you want to skip all this, or if you do want to follow along, you can find the source code for this project on <a href=\"https://github.com/drewalth/coreml-web-api\">GitHub</a>.</p><p>Okay, disclaimers over. Let’s get started!</p><h3>Requirements</h3><ul><li>Xcode 15</li><li>macOS 14</li><li>Homebrew</li><li>Apple Developer Account + Physical Device for testing</li></ul><h3>Getting Started</h3><p>First start by creating a new directory that will house our Xcode workspace. We’ll call it coreml-web-api .</p><pre>cd ~/Desktop &amp;&amp; mkdir coreml-web-api &amp;&amp; cd coreml-web-api</pre><p>Now let&#39;s install Vapor and bootstrap a brand new server. See <a href=\"https://docs.vapor.codes/\">the docs</a> for more details.</p><pre>brew install vapor<br>vapor new server -n<br>open Package.swift</pre><p>We want our users to be able to upload images for classification so add a new route called classify that supports this. In server/Sources/App/routes.swift , clear out all that generated boilerplate, and add in the following:</p><pre>import CoreImage<br>import Vapor<br><br>func routes(_ app: Application) throws {<br>    app.post(&quot;classify&quot;) { req -&gt; [ClassifierResult] in<br>        let classificationReq = try req.content.decode(ClassificationRequest.self)<br>        let imageBuffer = classificationReq.file.data<br>        guard let fileData = imageBuffer.getData(at: imageBuffer.readerIndex, length: imageBuffer.readableBytes),<br>              let ciImage = CIImage(data: fileData)<br>        else {<br>            throw Errors.badImageData<br>        }<br><br>        let classifier = Classifier() // we&#39;ll add this in a sec<br><br>        return try classifier.classify(image: ciImage)<br>    }<br>}<br><br>enum Errors: Error {<br>    case badImageData // or whatever<br>}<br><br>struct ClassificationRequest: Content {<br>    var file: File<br>}</pre><p>Also, bump up the max file size allowed for uploads in configure.swift :</p><pre>import Vapor<br><br>// configures your application<br>public func configure(_ app: Application) async throws {<br>    app.routes.defaultMaxBodySize = &quot;10mb&quot;<br><br>    // register routes<br>    try routes(app)<br>}</pre><p>Alright, now let&#39;s write up a Classifier API. First, head over to <a href=\"https://developer.apple.com/machine-learning/models/\">Apple’s ML page</a> to download a pre-trained model of your choosing. In this demo, I’m using the Resnet50 model. We’ll add this to the package in just a moment.</p><p>Add a new file called Classifier and drop in the following:</p><pre>import CoreImage<br>import Vapor<br>import Vision<br><br>struct Classifier {<br>    func classify(image: CIImage) throws -&gt; [ClassifierResult] {<br>        let url = Bundle.module.url(forResource: &quot;Resnet50&quot;, withExtension: &quot;mlmodelc&quot;)!<br>        guard let model = try? VNCoreMLModel(for: Resnet50(contentsOf: url, configuration: MLModelConfiguration()).model) else {<br>            throw Errors.unableToLoadMLModel<br>        }<br><br>        let request = VNCoreMLRequest(model: model)<br><br>        let handler = VNImageRequestHandler(ciImage: image)<br><br>        try? handler.perform([request])<br><br>        guard let results = request.results as? [VNClassificationObservation] else {<br>            throw Errors.noResults<br>        }<br><br>        return results.map { ClassifierResult(label: $0.identifier, confidence: $0.confidence) }<br>    }<br><br>    enum Errors: Error {<br>        case unableToLoadMLModel<br>        case noResults<br>    }<br>}<br><br>struct ClassifierResult: Encodable, Content {<br>    var label: String<br>    var confidence: Float<br>}</pre><p>Let’s break this down.</p><p>First, we load the model. Adding a CoreML model to a package is not super straightforward. We need to compile the .mlmodelourselves and add some files to Sources/. We’ll go over that in a few but this wonkiness explains why loading the model might look slightly different from adding one to a standard Xcode project.</p><p>Once the model is loaded, we prepare the request and the request handler; then we do the classification. To send the results as JSON to the client, we need to remap the results to a structure that conforms to Encodable and Content .</p><h4>Adding the Model to the Package</h4><p>This part definitely took me the longest to figure out. Unfortunately, this step is pretty manual; we can’t just drag and drop the model into the project. So, at the root of the server package, add a new folder called MLModelSource and add the Resnet50.mlmodel file here. Create another folder called Resourcesat server/Sources/App/Resources/ .</p><p>Now, we need to compile the model, add the Swift class to sources, and include the .mlmodelc in the package bundle. The compilation steps are repetitive so we’ll place them in a Makefile target. In the project root, create a Makefile:</p><pre># ~/Desktop/coreml-web-api/<br>touch Makefile</pre><p>And add a compile_ml_modeltarget:</p><pre>compile_ml_model:<br>   cd server/MLModelSource &amp;&amp; \\<br>   xcrun coremlcompiler compile Resnet50.mlmodel ../Sources/App/Resources &amp;&amp; \\<br>   xcrun coremlcompiler generate Resnet50.mlmodel ../Sources/App/Resources --language Swift</pre><p>Next, add this to the executable target inPackage.swift file:</p><pre>resources: [<br>    .copy(&quot;Resources/Resnet50.mlmodelc&quot;),<br>]</pre><p>The target should look like this:</p><pre>.executableTarget(<br>    name: &quot;App&quot;,<br>    dependencies: [<br>        .product(name: &quot;Vapor&quot;, package: &quot;vapor&quot;),<br>    ],<br>    resources: [<br>        .copy(&quot;Resources/Resnet50.mlmodelc&quot;),<br>    ]<br>),</pre><p>Okay, now from the project root, run the compile_ml_model target:</p><pre>make compile_ml_model</pre><p>Awesome!!! Now, we have an amazing server that supports classifying uploaded images using the Resnet50 model. Before we move on to the creating the client, we need to adjust the App scheme to make the server available to a physical device on your network.</p><p>Open up the scheme editor, and add serve --hostname 0.0.0.0 to the run arguments.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*9D7VFpFoUNxS6uZPqCVESw.png\" /></figure><p>Sweet. Now, we’ll create a client to do the uploading.</p><h3>iOS Client</h3><p>OK, in Xcode go to File -&gt; New -&gt; Project and add an iOS app to the workspace. We only need SwiftUI, no tests or SwiftData. I’m giving mine a really clever name of CoreMLWebClient … poetic.</p><p>Great. Now, let&#39;s do a little config work. Since we’re going to be using the camera, we need to update the Info.plist with the Privacy — Camera Usage Description key.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*PVfyDXPGfo9-oeQIercJjg.png\" /></figure><p>Nice! In our client, we want to give users the option of using the camera or selecting from the photo library. Create a new file called ImagePicker.swift and paste in the following:</p><pre>import SwiftUI<br><br>struct ImagePicker: UIViewControllerRepresentable {<br>    @Binding var sourceType: UIImagePickerController.SourceType<br>    @Environment(\\.presentationMode) private var presentationMode<br>    var completion: (UIImage) -&gt; Void<br><br>    func makeUIViewController(context: Context) -&gt; some UIViewController {<br>        let picker = UIImagePickerController()<br>        picker.sourceType = sourceType<br>        picker.delegate = context.coordinator<br>        return picker<br>    }<br><br>    func updateUIViewController(_: UIViewControllerType, context _: Context) {}<br><br>    func makeCoordinator() -&gt; Coordinator {<br>        Coordinator(self)<br>    }<br><br>    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {<br>        var parent: ImagePicker<br><br>        init(_ parent: ImagePicker) {<br>            self.parent = parent<br>        }<br><br>        func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {<br>            if let image = info[.originalImage] as? UIImage {<br>                parent.completion(image)<br>            }<br>            parent.presentationMode.wrappedValue.dismiss()<br>        }<br>    }<br>}</pre><p>We’ll use the sourceType binding to switch between the camera and the library.</p><p>Now, we’ll add a Classifierto handle the image uploading and return the classification results. I’m jumping around a little, but all this will come together in a few. Create a new file called Classifier.swift and add this in:</p><pre>import Foundation<br>import UIKit<br><br>struct Classifier {<br>    /// replace this with your dev machine IP address<br>    /// for testing with a physical device.<br>    private let host = &quot;localhost&quot;<br><br>    func classify(image: UIImage) async throws -&gt; [ClassifierResult] {<br>        // Ensure the URL is valid<br>        guard let uploadURL = URL(string: &quot;http://\\(host):8080/classify&quot;) else {<br>            throw URLError(.badURL)<br>        }<br><br>        // Convert the image to JPEG data<br>        guard let imageData = image.jpegData(compressionQuality: 1.0) else {<br>            throw URLError(.unknown)<br>        }<br><br>        // Generate boundary string using a unique per-app string<br>        let boundary = &quot;Boundary-\\(UUID().uuidString)&quot;<br><br>        // Create a URLRequest object<br>        var request = URLRequest(url: uploadURL)<br>        request.httpMethod = &quot;POST&quot;<br>        request.setValue(&quot;multipart/form-data; boundary=\\(boundary)&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)<br><br>        // Create multipart form body<br>        let body = createMultipartFormData(boundary: boundary, data: imageData, fileName: &quot;photo.jpg&quot;)<br>        request.httpBody = body<br><br>        // Perform the upload task<br>        let (data, response) = try await URLSession.shared.upload(for: request, from: body)<br><br>        // Check the response and throw an error if it&#39;s not a HTTPURLResponse or the status code is not 200<br>        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {<br>            throw URLError(.badServerResponse)<br>        }<br><br>        // Decode the data into an array of ClassifierResult<br>        return try JSONDecoder().decode([ClassifierResult].self, from: data)<br>    }<br><br>    /// Creates a multipart/form-data body with the image data.<br>    /// - Parameters:<br>    ///   - boundary: The boundary string separating parts of the data.<br>    ///   - data: The image data to be included in the request.<br>    ///   - fileName: The filename for the image data in the form-data.<br>    /// - Returns: A `Data` object representing the multipart/form-data body.<br>    private func createMultipartFormData(boundary: String, data: Data, fileName: String) -&gt; Data {<br>        var body = Data()<br><br>        // Add the image data to the raw http request data<br>        body.append(&quot;--\\(boundary)\\r\\n&quot;)<br>        body.append(&quot;Content-Disposition: form-data; name=\\&quot;file\\&quot;; filename=\\&quot;\\(fileName)\\&quot;\\r\\n&quot;)<br>        body.append(&quot;Content-Type: image/jpeg\\r\\n\\r\\n&quot;)<br>        body.append(data)<br>        body.append(&quot;\\r\\n&quot;)<br><br>        // Add the closing boundary<br>        body.append(&quot;--\\(boundary)--\\r\\n&quot;)<br>        return body<br>    }<br><br>    struct ClassifierResult: Decodable, Identifiable {<br>        let id = UUID()<br>        var label: String<br>        var confidence: Float<br>    }<br>}<br><br>// Helper function to append string data to Data object<br>private extension Data {<br>    mutating func append(_ string: String) {<br>        if let data = string.data(using: .utf8) {<br>            append(data)<br>        }<br>    }<br>}</pre><p>Great! Now on to the UI. Back in ContentView , let&#39;s add an enum called RequestStatus to communicate to the user what is going on — this is an easy UX win.</p><pre>enum RequestStatus {<br>    case loading, success, idle, error<br>}</pre><p>Now, we’ll create a view model for ContentView that uses the newly created classifier to upload a photo to the server and share the results with the UI. This is also going to use the new <a href=\"https://developer.apple.com/documentation/observation\">Observation framework</a> ⭐.</p><pre>extension ContentView {<br>    @Observable<br>    class ViewModel {<br>        var requestStatus: RequestStatus = .idle<br>        var results: [Classifier.ClassifierResult] = []<br><br>        private var classifier = Classifier()<br><br>        func upload(_ image: UIImage) {<br>            Task { @MainActor in<br>                do {<br>                    requestStatus = .loading<br>                    results.removeAll()<br>                    results = try await classifier.classify(image: image)<br>                    requestStatus = .success<br>                } catch {<br>                    print(error.localizedDescription)<br>                    requestStatus = .error<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>Now we need to add some state. This stuff should probably go in the view model, but for now, I’m going to add these as member vars to ContentView …</p><pre>// ContentView.swift<br>@State private var selectedImage: UIImage?<br>@State private var isImagePickerPresented = false<br>@State private var viewModel = ViewModel()<br>@State private var sourceType: UIImagePickerController.SourceType = .camera</pre><p>Alright, now we’ll do some more UI building. Replace the body variable with this:</p><pre>    var body: some View {<br>        VStack(spacing: 20) {<br>            HStack(spacing: 20) {<br>                if let image = selectedImage {<br>                    VStack {<br>                        Image(uiImage: image)<br>                            .resizable()<br>                            .scaledToFit()<br>                    }.padding()<br>                        .frame(maxHeight: 350)<br>                }<br>                List {<br>                    ForEach(viewModel.results, id: \\.id) { result in<br>                        VStack(alignment: .leading) {<br>                            Text(result.label)<br>                                .font(.callout)<br>                            Text(formatAsPercentage(result.confidence))<br>                                .font(.caption2)<br>                        }<br>                    }<br>                }<br>            }<br>            Divider()<br>            HStack(spacing: 20) {<br>                actionButton()<br>                if viewModel.requestStatus == .loading {<br>                    ProgressView()<br>                }<br>            }<br>        }<br>        .sheet(isPresented: $isImagePickerPresented) {<br>            ImagePicker(sourceType: $sourceType) { image in<br>                self.selectedImage = image<br>            }<br>        }<br>    }</pre><p>And to address those compiler errors, add two new functions:</p><pre>// ContentView.swift<br>@ViewBuilder<br>private func actionButton() -&gt; some View {<br>    if let image = selectedImage {<br>        Button(&quot;Upload Image&quot;) {<br>            viewModel.upload(image)<br>        }.buttonStyle(.borderedProminent)<br>    } else {<br>        HStack(spacing: 20) {<br>            Button(&quot;Camera&quot;) {<br>                sourceType = .camera<br>                isImagePickerPresented = true<br>            }.buttonStyle(.bordered)<br>            Button(&quot;Photo Library&quot;) {<br>                sourceType = .photoLibrary<br>                isImagePickerPresented = true<br>            }.buttonStyle(.bordered)<br>        }.padding(.bottom, 20)<br>    }<br>}<br><br>// and<br><br>private func formatAsPercentage(_ value: Float) -&gt; String {<br>    String(format: &quot;%.2f%%&quot;, value * 100)<br>}</pre><p>Heck yeah, you guys. If everything has gone according to plan, you should now be able to create/select a picture, upload it to the server, classify the dominant object in the picture, and then display the classification results in the UI!</p><p>If you run into issues, please feel free to reference the source code, or leave a comment below.</p><p>I hope this project inspires you and gets the gears turning for your next ML project.</p><p>Cheers!</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=48809a853fae\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/deploy-coreml-models-on-the-server-with-vapor-48809a853fae\">Deploy CoreML Models on the Server with Vapor</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/37afb7a6b26c","link":"https://medium.com/better-programming/full-stack-engineers-dont-exist-37afb7a6b26c?source=rss----d0b105d10f0a--programming","title":"Full Stack Engineers Don’t Exist!","author":"Stephen Walsh","updated":"2023-11-10T17:28:33.460Z","published":"Fri, 10 Nov 2023 17:28:33 GMT","categories":["software-development","software-engineering","programming","careers","technology"]},{"guid":"https://medium.com/p/d36df47f051d","link":"https://medium.com/better-programming/what-isomorphic-types-are-and-why-you-might-need-them-developing-with-swift-d36df47f051d?source=rss----d0b105d10f0a--programming","title":"What Isomorphic types are and why you might need them developing with Swift.","author":"Maksym Teslia","excerpt":"Image by author. A bit of general math * Note: we are not going to dive deep into theoretical definitions, instead will just discuss the conception using common terms. An isomorphism is a correspondence (relation) between objects or systems of objects expressing the equality of their structures in s","updated":"2023-11-10T17:27:51.075Z","published":"Fri, 10 Nov 2023 17:27:51 GMT","categories":["programming","math","swift","ios","debugging"],"content_html":"<figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*YJcmoAVcBtUeuKAQm3BQhA.png\" /><figcaption>Image by author.</figcaption></figure><h3>A bit of general math</h3><p><em>* Note: we are not going to dive deep into theoretical definitions, instead will just discuss the conception using common terms.</em></p><blockquote>An isomorphism is a correspondence (relation) between objects or systems of objects expressing the equality of their structures in some sense. — <a href=\"https://encyclopediaofmath.org/wiki/Isomorphism\">Encyclopedia of Mathematics</a>.</blockquote><p>Isomorphism is a core mathematical concept which is used for comparing various entities, such as groups, graphs, or vector spaces. It involves examination of their fundamental structure on order to determine if there is a correlation between their essential systemic properties. The presence of such correlation implies the isomorphism existence, confirming that these objects share a deep, intrinsic similarity.</p><p>In simple words, types are isomorphic when their properties are fundamentally related to each other. If we consider isomorphism from a practical perspective, we can describe it as a state when objects of different types can be converted to each other with no data loss. Actually, mathematical proof of this concept describes it very well. Let’s see how it looks like (very simplified and non-math-human readable):</p><p>Types <em>A</em> and <em>B</em> are isomorphic if there are two functions <em>F: A -&gt; B</em> and <em>G: B -&gt; A</em>, and for all objects (<em>x</em>) of type <em>A</em> calling <em>G(F(x))</em> is equal to <em>x</em> as well as for all objects (<em>y</em>) of type <em>B</em> the result of calling <em>F(G(y))</em> is equal to <em>y</em>.</p><p>“Very simplified and non-math-human readable”, but still does not look understandable enough? Well, let’s try to implement it in Swift, that should help to figure the above statement out, so you are able to fully grasp that knowledge.</p><h3>Swift representation of this logic</h3><p>In order to observe an example of Isomorphic behaviour in Swift, we would need to create a custom BoolImitation enumeration, which will have just two cases, pseudoTrue and pseudoFalse:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/f9920edeedc07da438ecd5ed3cbc3983/href\">https://medium.com/media/f9920edeedc07da438ecd5ed3cbc3983/href</a></iframe><p>Now, you can construct two functions, one will accept BoolImitation instance and will return real Bool and second one will vice versa accept Bool and return BoolImitation:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/8384d1653c037a20edc6c4b7421b0784/href\">https://medium.com/media/8384d1653c037a20edc6c4b7421b0784/href</a></iframe><p>For those who want to throw a rock in me due to “f” and “g” naming, I know it is not to be done this way, just want to be closer to math declarations, provided earlier.</p><p>Now, if you want to prove Bool and BoolImitation are isomorphic, you can call those functions in the manner, which has been described above:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/a0189047e08c0d514102717a1f96362e/href\">https://medium.com/media/a0189047e08c0d514102717a1f96362e/href</a></iframe><p>As you can see, in both cases the resulting value of a call is gonna be the same as the value, which has been initially passed into a function chain. Let me explain how that works to enlighten it just in case. Will consider the first example.</p><p>When true is being passed inside of a g function, its inner logic transforms it into pseudoTrue along with returning it into an input parameter of function f. After that, f’s internal logic converts this pseudoTrue into true and returns it, so that it can be written into an fResult property. Logic of a second assignment is going to be operating in the same way.</p><p>If we shift closer to math and try to describe it using its language, we can state that “There are functions f of type (BoolImitation) -&gt; Bool and g of type (Bool) -&gt; BoolImitation, and for any object x of type BoolImitation calling g(f(x)) will result in returning x as well as for any object y if type Bool calling f(g(y)) will result in returning y”.</p><p>Therefore, remembering what we’ve studied in the first part, we can state that types can be transformed from one to another without losing any details, so, they are isomorphic!</p><h3>How you can apply that knowledge</h3><h4>Issue</h4><p>I bet you’ve seen such an example during your career path. You have two enumerations in your application, and both of them have identical cases naming. More than likely, they were represented as submodels of bigger view or data models.</p><p>That usually happens in applications which include ‘Buy-Use’ pattern of product handling, when you can order or purchase some product inside an application and then utilise in there. Typically, you receive the list of “available product” models from the server and then, once user purchases one of them, you construct “bought product” model, including “the type” of the product being secured. Let’s look closer into this pattern in order to check an example out.</p><p>So, we have two enums inside of our application, one is responsible for available product type and second one is responsible for purchased product type:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/cd4d2d6c3a8311b46f58c820e59f32fb/href\">https://medium.com/media/cd4d2d6c3a8311b46f58c820e59f32fb/href</a></iframe><p>In the real world you’d have them inside of other bigger “AvailableProduct” and “PurchasedProduct” models, but let’s at that point ignore this for the sake of simplicity.</p><p>At some point, more than likely when the product is finally bought, you will have to transform “AvailableProduct” into a “PurchasedProduct”, including the type of it. Particularly for this, you’d need to construct this kind of function (other conditional checking approach can be used of course, but the conceptual idea would remain the same):</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/0c10499e4d3061036cd56b11f9c73f77/href\">https://medium.com/media/0c10499e4d3061036cd56b11f9c73f77/href</a></iframe><p><em>Generally</em>, there is nothing wrong about it, but adding more products will make you to produce more cases, which will result in cyclomatic complexity increase, so your code becomes less readable and analysable.</p><h4>Solution</h4><p>If we inspect what happens inside getPurchasedProductType function, one specific behaviour can be spotted: in all the scenarios AvailableProductType instance will be transformed into a same name case of PurchasedProductType, therefore, in other words, <em>they will be</em> <em>converted with no data loss</em>.</p><p>Based on the above, we can state that those types are isomorphic, hence, there is a mathematical statement under the hood of their transformation. If there is one, we can surely describe it somehow within our application. For that, we would need to find some <em>fundamental relation </em>between their properties, which can be used by us in code. But what would that be?</p><p>I won’t muddy the waters and will tell you right away. For two enums with identically named cases that fundamental relation is a hashValue — if you have two enum cases with same names, it will be the same for both of them. If by now it is not clear why we would need it, hang tight, we will get to the implementation shortly.</p><p>Let’s start implementing our transforming functionality by establishing a protocol, which will describe any enum, which can be transformed through our custom isomorphic-related mechanism:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/de3acc6a7b248fd5254b60361d376bb3/href\">https://medium.com/media/de3acc6a7b248fd5254b60361d376bb3/href</a></iframe><p>Protocol conformances are required to:</p><ul><li>Hashable — to be sure that instance is going to be populated with a hashValue property.</li><li>CaseIterable — to be sure we are using an enum and to have the ability to iterate over the cases of this enum.</li></ul><p>You will see why both of those protocols are required a bit further.</p><p>In order to conduct a transformation, we would need to create an “isomorphic context”, which is going to handle converting of one Isomorphicable instance into another one. In order to make this context reusable for any protocol conformant, we need to stick around generics and create a structure with two abstract types:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/ec0c8bb0d49dd54e6b9e068ef135ff27/href\">https://medium.com/media/ec0c8bb0d49dd54e6b9e068ef135ff27/href</a></iframe><p>Instance of a structure will accept only one value into initialiser, that will be actual enum case of F type, which is then to be transformed into identical case of S type.</p><p>After we have the structure, we need the API which will return <strong>transformed</strong> case of type S to us. Not thinking of the name for a long time, we create it this way:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/65e2a303a08440feccc2f857d7aca283/href\">https://medium.com/media/65e2a303a08440feccc2f857d7aca283/href</a></iframe><p>I promised you will see where Hashable and CaseIterable protocols come into play, so here they are.</p><p>First, we need to check if both enums have equal cases. If they don’t, we produce an error. Conceptually, ‘fatalError’ pattern for handling errors isn’t really suitable for production applications, but if the functionality is covered by tests and you are sure that potential crash isn’t going to leak into production, it is acceptable. This approach has been chosen here for the sake of simplicity, however, if you implement this mechanism in your app, you can either throw an error or use Result type, depending on your preferences.</p><p>If during the check we did not receive an error, we then iterate over all of the cases of “Second” enum to see which of them has the same hashValue as the one which was passed as an initialValue. Once such case is found, we return it to a final consumer. Force-unwrap is fully safe here, cause we know for sure that such a case exists after conducting an error check.</p><p>So, as we have everything settled, we can now use our Isomorphic structure to get rid of a switch inside getPurchasedProductType function and substitute it by the following call:</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/dc44acbf719cdd9efc619cdb167466fa/href\">https://medium.com/media/dc44acbf719cdd9efc619cdb167466fa/href</a></iframe><p>Voilà! Now if product count increases, you won’t have to care about that, cause this transformation is gonna be handled automatically, with help of isomorphic context we’ve created.</p><p>There are a few important points to keep in mind though. Firstly, you have to always be sure that cases of enums, placed into Isomorphic instance, are the same, otherwise, the error will be produced. Secondly, you can not use it on enums with different raw and associated values, cause it will influence hash values for their cases.</p><p>In this article we went through a mathematical concept of isomorphism and investigated in which cases it might be helpful in your daily development routine. There is a potential improvements wiggle room in that pattern of generic types transformation, you can be establishing this kind of approach for other cases of similar types conversion.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d36df47f051d\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/what-isomorphic-types-are-and-why-you-might-need-them-developing-with-swift-d36df47f051d\">What Isomorphic types are and why you might need them developing with Swift.</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/3049cf50a992","link":"https://medium.com/better-programming/exploring-rusts-option-type-a-guide-to-optional-value-handling-3049cf50a992?source=rss----d0b105d10f0a--programming","title":"Exploring Rust’s Option Type: A Guide to Optional Value Handling","author":"John Philip","updated":"2023-11-10T17:25:05.789Z","published":"Fri, 10 Nov 2023 17:25:05 GMT","categories":["programming","technology","rust-programming-language","rustlang","rust"]},{"guid":"https://medium.com/p/88a7c3b4f04d","link":"https://medium.com/better-programming/modern-data-is-a-painting-88a7c3b4f04d?source=rss----d0b105d10f0a--programming","title":"Modern Data is a painting","author":"Ievgenii Spitsyn (E. Fogwalker)","updated":"2023-11-10T17:24:53.269Z","published":"Fri, 10 Nov 2023 17:24:53 GMT","categories":["data-visualization","programming","virtual-reality","augmented-reality","web-development"]},{"guid":"https://medium.com/p/5be52e97fff0","link":"https://medium.com/better-programming/localisation-in-xcode-15-5be52e97fff0?source=rss----d0b105d10f0a--programming","title":"Localisation in Xcode 15","author":"Jacob Bartlett","updated":"2024-03-11T09:52:34.127Z","published":"Wed, 08 Nov 2023 11:31:57 GMT","categories":["programming","ios","apple","software-engineering","technology"]},{"guid":"https://medium.com/p/1ebd2d5005ec","link":"https://medium.com/better-programming/geometryreader-blessing-or-curse-1ebd2d5005ec?source=rss----d0b105d10f0a--programming","title":"GeometryReader: Blessing or Curse?","author":"fatbobman ( 东坡肘子)","updated":"2024-11-28T03:39:58.002Z","published":"Wed, 08 Nov 2023 02:06:15 GMT","categories":["programming","swiftui","mobile-app-development","swift","ios-development"]},{"guid":"https://medium.com/p/40bdaadbc22f","link":"https://medium.com/better-programming/everything-you-can-do-with-pythons-bisect-module-40bdaadbc22f?source=rss----d0b105d10f0a--programming","title":"Everything You Can Do With Python’s Bisect Module","author":"Martin Heinz","excerpt":"Learn how to optimize search and keep your data sorted in Python with the &quot;bisect&quot; module Photo by Jason Leung on Unsplash While Python’s bisect module is very simple - containing really just 2 functions - there&#39;s a lot one can do with it, including searching data efficiently, keeping ","updated":"2023-11-08T02:04:23.042Z","published":"Wed, 08 Nov 2023 02:04:23 GMT","categories":["data-science","technology","programming","python","software-engineering"],"content_html":"<h4>Learn how to optimize search and keep your data sorted in Python with the &quot;bisect&quot; module</h4><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*hlocGOJQ3nl4mJtuQFaL9w.jpeg\" /><figcaption>Photo by <a href=\"https://unsplash.com/@ninjason?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Jason Leung</a> on <a href=\"https://unsplash.com/photos/BUa1LDeT8PI?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Unsplash</a></figcaption></figure><p>While Python’s bisect module is very simple - containing really just 2 functions - there&#39;s a lot one can do with it, including searching data efficiently, keeping any data sorted, and much more - and in this article we will explore all of it!</p><h3>What is Bisect(ion)?</h3><p>Before we start playing with the module, let’s first explain what <em>bisect(ion)</em> actually is. An official definition:</p><blockquote><em>Bisection is the division of a given curve, figure, or interval into two equal parts (halves).</em></blockquote><p>Which in plain English means that it implements a binary search. In practice that means that we can use it to — for example — insert elements into a list while maintaining the list in sorted order:</p><pre>import bisect<br><br>some_list = [0, 6, 1, 5, 8, 2]<br>some_list.sort()<br>print(some_list) # [0, 1, 2, 5, 6, 8]<br><br>i = bisect.bisect_left(some_list, 4)<br>print(i)  # 3<br><br>some_list.insert(i, 4)<br>print(some_list)  # [0, 1, 2, 4, 5, 6, 8]<br># OR<br>bisect.insort_left(some_list, 4)<br>print(some_list)  # [0, 1, 2, 4, 5, 6, 8]</pre><p>In this basic example, we first sort a list because we can only use functions from bisect on sorted iterable. We then use bisect_left on the list to find an index where the second argument ( 4) should be inserted to maintain sorted order. We then proceed to do just that - insert the number 4 at index 3. Alternatively, we can directly use insort_left function which first uses bisect_left internally and then does the insert too.</p><p>Well, that’s cool, but why should you care about this module, though? Well, let me show you all the useful things you can do with it…</p><h3>Binary Search</h3><p>As already mentioned, bisect implements binary search so the most obvious use for it is just that:</p><pre>from bisect import bisect_left<br><br>def binary_search(a, x, lo=0, hi=None):<br>    if hi is None:<br>        hi = len(a)<br>    pos = bisect_left(a, x, lo, hi)  # find insertion position<br>    return pos if pos != hi and a[pos] == x else -1  # don&#39;t walk off the end<br><br>print(binary_search([0, 1, 2, 5, 6, 8], 5))  # 3<br>print(binary_search([0, 1, 2, 5, 6, 8], 4))  # -1</pre><p>The parameters of binary_search function above follow the same pattern as the functions in bisect module. That is - we look for value x in the list a between index lo and hi.</p><p>The only interesting line is the return statement, where we test whether the value x is actually in the list, if yes we return its position, otherwise we return -1.</p><h3>Successive Equal Values</h3><p>There are however more interesting things we can do with bisect module, for example finding successive equal values in a list:</p><pre>from bisect import bisect_left, bisect_right<br><br>some_list = [5, 10, 15, 15, 15, 20, 25, 40]<br># Find all the 15&#39;s<br>value = 15<br>start = bisect_left(some_list, value)<br>end = bisect_right(some_list, value)<br>print(f&#39;Successive values of {value} from index {start} to {end}: {some_list[start:end]}&#39;)<br># Successive values of 15 from index 2 to 5: [15, 15, 15]</pre><p>We’ve already seen bisect_left, so here we also introduce bisect_right which does the same thing, but from the other end. This way we can locate both start and end of the span of successive values we&#39;re looking for.</p><h3>Mapping from Intervals to Values</h3><p>Now, let’s imagine that we have a series of intervals/ranges, and we want to return corresponding ID/value. A naive, ugly solution could look something like:</p><pre>def interval_to_value(val):<br>    if val &lt;= 100:<br>        return 0<br>    elif 100 &lt; val &lt;= 300:<br>        return 1<br>    elif 300 &lt; val &lt;= 500:<br>        return 2<br>    elif 500 &lt; val &lt;= 800:<br>        return 3<br>    elif 800 &lt; val &lt;= 1000:<br>        return 4<br>    elif val &gt; 1000:<br>        return 5</pre><p>But, there’s a much nicer solution using bisect_left:</p><pre>def interval_to_value(val):<br>    return bisect_left([100, 300, 500, 800, 1000], val)</pre><p>This isn’t just very clean solution but also super fast. It can be also extended in case you’d need a non-natural ordering or, for example, if you wanted to return something different, like a string:</p><pre>i = interval_to_value(325)<br>a = [&#39;absent&#39;, &#39;low&#39;, &#39;average&#39;, &#39;high&#39;, &#39;very high&#39;, &#39;extreme&#39;]<br>print(a[i])  # average</pre><h3>Closest Key in Dictionary</h3><p>Now, let’s say we have a mapping in form of a dictionary, and we want to lookup values for a specified key. If the key exist then cool, but if it doesn’t, then we want to return the value of the closest key:</p><pre>import collections<br>some_dict = collections.OrderedDict(<br>    [(0, 0), (2, 1), (4, 4), (6, 9), (8, 16), (10, 25), (12, 36), (14, 49), (16, 64), (18, 81)]<br>)<br><br>target = 10.5<br>index = bisect_left(list(some_dict.keys()), target)  # 6<br><br>items = list(some_dict.items())<br><br># Check which one is closer:<br>print(f&#39;Distance for to index {index}: {distance1}&#39;)    # Distance for to index 6: 1.5<br>print(f&#39;Distance for to index {index-1}: {distance2}&#39;)  # Distance for to index 5: 0.5<br><br>print(&#39;Closest value:&#39;)<br>if distance1 &lt; distance2:<br>    print(items[index])<br>else:<br>    print(items[index-1])<br><br># Closest value: (10, 25)</pre><p>Here we use OrderedDict to make sure that we have the keys in correct order. We then use bisect_left on them to find insertion point. Finally, we need to check whether the next or the previous index is closer to the target.</p><h3>Prefix Search</h3><p>Another thing you can use bisect for is prefix search - let&#39;s assume we have a very large word list and want to lookup words based on a given prefix:</p><pre>def prefix_search(wordlist, prefix):<br>    try:<br>        index = bisect_left(wordlist, prefix)<br>        return wordlist[index].startswith(prefix)<br>    except IndexError:<br>        return False<br><br><br>words = [&#39;another&#39;, &#39;data&#39;, &#39;date&#39;, &#39;hello&#39;, &#39;text&#39;, &#39;word&#39;]<br><br>print(prefix_search(words, &#39;dat&#39;))  # True<br>print(prefix_search(words, &#39;xy&#39;))  # False</pre><p>The function above only check whether a word with specified prefix exists in the list, but this could be easily modified to loop from the index and return all the words starting with prefix.</p><p>If you have large enough word list, using bisect becomes <em>much</em> faster in comparison to just iterating the list from the start.</p><h3>Sorted Custom Objects</h3><p>So far we’ve only used built-in types, but functions from bisect module can be also applied to custom types. Let&#39;s say that we have a list of custom objects, and we want to maintain their order in the list based on some attribute:</p><pre>from bisect import insort_left<br><br>class CustomObject:<br>    def __init__(self, val):<br>        self.prop = val  # The value to compare<br><br>    def __lt__(self, other):<br>        return self.prop &lt; other.prop<br><br>    def __repr__(self):<br>        return &#39;CustomObject({})&#39;.format(self.prop)<br><br>some_objects = sorted([CustomObject(7), CustomObject(1), CustomObject(3), CustomObject(9)])<br><br>insort_left(some_objects, CustomObject(2))<br>print(some_objects)  # [CustomObject(1), CustomObject(2), CustomObject(3), CustomObject(7), CustomObject(9)]</pre><p>This code snippet uses the fact that bisect uses __lt__ magic method to compare objects. However, having to use bisect functions all the time might be a bit inconvenient, to avoid that you could implement a SortedCollection described in <a href=\"https://code.activestate.com/recipes/577197-sortedcollection/\">this more complete example/recipe</a>.</p><h3>Key Function</h3><p>Functions in bisect module also support more complicated comparison/search using the the key function parameter:</p><pre>some_list = [1, 3, 7, 16, 25]<br>some_list.reverse()<br>insort_left(some_list, 10, key=lambda x: -1 * x)<br>print(some_list)  # [25, 16, 10, 7, 3, 1]</pre><p>Here we use the key function to implement a reverse order binary search, just remember that the list also has to be sorted in reverse order to begin with.</p><p>Similarly to reverse ordering, one might be also inclined to use key function for searching list of tuples, it’s however possible to do just:</p><pre>list_of_tuples = [(1, 3), (3, 8), (5, 4), (10, 12)]<br><br>index = bisect_left(list_of_tuples, (5, ))  # 2<br>print(list_of_tuples[2])  # (5, 4)</pre><p>Omitting the second value in the tuple forces bisect_left to compare only based on the first value. If you wanted to be explicit and use the key function anyway, then key=lambda i: i[0] would work too.</p><p>With that said, the key function is somewhat unintuitive — one would expect it to work like e.g. sorted function:</p><pre>some_tuples = [<br>    (0, 10),<br>    (2, 12),<br>    (3, 15),<br>    (5, 20),<br>]<br><br>print(sorted(some_tuples, key=lambda t: t[0] + t[1]))  # Works</pre><p>But it doesn’t:</p><pre>index = bisect_left(some_tuples, (4, 17), key=lambda t: t[0] + t[1])  # Doesn&#39;t work<br># Expectation: index = 3<br># Reality: &quot;TypeError: &#39;&lt;&#39; not supported between instances of &#39;int&#39; and &#39;tuple&#39;&quot;<br><br>def key_func(t):<br>    return t[0] + t[1]<br><br>index = bisect_left(some_tuples, key_func((4, 17)), key=key_func)<br>print(index)  # 3</pre><p>Instead, we have to define a key function, pass it as a key argument and invoke it on the second argument, too.</p><p>That’s because (from docs):</p><blockquote><em>key specifies a key function of one argument that is used to extract a comparison key from each element in the array. To support searching complex records, the key function is not applied to the x value.</em></blockquote><p>Also see this <a href=\"https://github.com/python/cpython/issues/91966#issuecomment-1110180631\">GitHub issue</a> in cpython repository for reasoning behind this design decision.</p><p>In my opinion, for such a tiny module, that’s a lot of things you can use it for.</p><h3>Conclusion</h3><p>Also, besides all the above useful things you can do with bisect, I want to highlight how fast this is - especially if you have a list that&#39;s already sorted. For example, consider <a href=\"https://stackoverflow.com/questions/7571635/fastest-way-to-check-if-a-value-exists-in-a-list/40963434#40963434\">this example</a> on Stack Overflow showing that bisect_left is much faster than in operator.</p><p>It being <em>binary search</em> naturally means that it runs in O(log(n)), but it&#39;s further helped by being precompiled in C, so it&#39;s always going to be faster than anything you write yourself.</p><p><a href=\"https://betterprogramming.pub/weird-python-features-that-might-catch-you-by-surprise-5b2ca521b0bf\">Python “Features” That Might Catch You By Surprise</a></p><pre><strong>Want to Connect?</strong><br><br>This article was originally posted at <a href=\"https://martinheinz.dev/blog/106\">martinheinz.dev</a>.</pre><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=40bdaadbc22f\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/everything-you-can-do-with-pythons-bisect-module-40bdaadbc22f\">Everything You Can Do With Python’s Bisect Module</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"},{"guid":"https://medium.com/p/bf99876e985f","link":"https://medium.com/better-programming/ram-and-vram-profiling-in-python-bf99876e985f?source=rss----d0b105d10f0a--programming","title":"RAM and VRAM Profiling in Python","author":"Thomas Rouch","excerpt":"Efficiently monitor RAM and GPU VRAM simultaneously with a single powerful function decorator. Photo by John Cameron on Unsplash 1. Why profile memory usage? Introduction While the specific motivations for memory profiling in your code application may vary, the ultimate goal is always to enhance its","updated":"2023-11-08T02:02:05.695Z","published":"Wed, 08 Nov 2023 02:01:22 GMT","categories":["software-engineering","programming","data-science","machine-learning","artificial-intelligence"],"content_html":"<h4>Efficiently monitor RAM and GPU VRAM simultaneously with a single powerful function decorator.</h4><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*SRlYKkJXgif9WPTQ\" /><figcaption>Photo by <a href=\"https://unsplash.com/@john_cameron?utm_source=medium&amp;utm_medium=referral\">John Cameron</a> on <a href=\"https://unsplash.com?utm_source=medium&amp;utm_medium=referral\">Unsplash</a></figcaption></figure><h3>1. Why profile memory usage?</h3><h4>Introduction</h4><p>While the specific motivations for memory profiling in your code application may vary, the ultimate goal is always to enhance its speed and robustness. Here are some memory patterns that are useful to detect:</p><ul><li><em>Memory Leak</em>: The memory consumption is growing over time, which makes the application slow down and eventually crash. It often comes from an improper memory management that fails to de-allocate memory that was dynamically allocated.</li><li><em>Unwanted Memory Allocation</em>: A given function is allocating a lot of memory even-though it shouldn’t. For instance, this can happen if you inadvertently use the copy constructor instead of the move constructor without realizing it.</li><li><em>Memory Peak</em>: This is the maximum memory consumption that was required during the runtime. In resource-constrained environments, the available memory ceiling is often restricted and might fall short of meeting your application’s requirements.</li></ul><h4>Don’t forget the GPU</h4><p>When discussing memory constraints in a runtime application, it’s essential to distinguish between two specific types of memory:</p><ul><li><em>RAM (Random Access Memory)</em>:<br>This is the general-purpose system memory of a computer, used to temporarily store data and program instructions for fast access by the CPU during computing tasks.</li><li><em>VRAM (Video Random Access Memory)</em>:<br>This is the memory used by Graphics Processing Units (GPUs) to efficiently manage and process graphical data, playing a pivotal role in accelerating tasks such as deep learning, gaming, and multimedia applications.</li></ul><h4>Resource-constrained environments</h4><p>In Computer Vision projects, the risk of running out of memory is prevalent due to the memory-intensive nature of image and video processing. The complex algorithms and deep learning models, along with large datasets, can overload the memory. Effective memory management and hardware configurations are crucial to prevent such issues.</p><p>The screenshot below shows the memory consumption (RAM) when an application ran out of of memory and got killed. This is precisely the scenario we aim to prevent when conducting memory profiling.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*2syde2-02DuFA-ri_DJ7oA.png\" /><figcaption>Application got killed because it ran out of memory — Screenshot by the author</figcaption></figure><p>Here are possible causes of an Out-Of-Memory error:</p><ul><li>Too many memory-intensive threads launched simultaneously.<br>Have you ever tried compiling with “make -j,” using all the available threads? You’ll often witness your PC freeze due to memory depletion.</li><li>Heavy Deep Learning Model that doesn’t fit in the VRAM.</li><li>The entire dataset is loaded in memory, instead of processing it in batches.</li><li>Memory is never de-allocated.</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*8VHj69JFxAiQaR6A\" /><figcaption>Photo by <a href=\"https://unsplash.com/@bermixstudio?utm_source=medium&amp;utm_medium=referral\">Bermix Studio</a> on <a href=\"https://unsplash.com?utm_source=medium&amp;utm_medium=referral\">Unsplash</a></figcaption></figure><h3>2. Choose the tool’s purpose</h3><h4>Goal</h4><p>There isn’t a one-size-fits-all memory profiling tool. The choice depends on what you want to observe, as explained in the previous section.</p><p>In my case, I have a pipeline made of algorithmic blocks and I’d like to know the memory peak of each block in terms of RAM and VRAM.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*CNFCvee12W9qFxiRsriFiQ.png\" /><figcaption>Example of RAM and VRAM Profiling on a pipeline of Photogrammetry (The displayed memory curves are for illustration and may not reflect an actual pipeline.) — Diagram by the author</figcaption></figure><p>For instance, taking into consideration the peak RAM and VRAM consumption of your pipeline is essential when choosing an Amazon EC2 instance. It ensures that you can align your application’s memory needs with the available resources of the selected EC2 instance, resulting in a well-balanced and cost-effective computing environment.</p><h4>Existing memory profiling tools</h4><p>There are amazing memory profiling tools available for free, like the two below:</p><ul><li><a href=\"https://github.com/KDE/heaptrack\"><em>KDE heaptrack</em></a>: An open-source memory profiler designed for tracking heap memory allocations and de-allocations in Linux-based software development.</li><li><a href=\"https://github.com/pythonprofilers/memory_profiler\"><em>memory-profiler</em></a>: A Python module for monitoring memory consumption of a process as well as line-by-line analysis.</li></ul><p>Data collected by <em>heaptrack</em> might be too low-level in my case, since I’ve already identified in my pipeline the scopes that I want to profile.</p><p>The <em>memory-profiler</em> library provides an easy-to-use @profile decorator. Even though it profiles only RAM, we will draw inspiration from its logic to develop our own version that also monitors VRAM.</p><p>Let’s see how to implement our custom profiling decorator that tracks the peak RAM and VRAM consumption of a given function. (This will be a nice addition to the time profiling introduced in a previous Medium article <a href=\"https://medium.com/better-programming/profiling-and-multiprocessing-in-python-e2e90f044e1e\">Profiling and Multiprocessing in Python</a> )</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*H2NplR5r-jquOUxQ\" /><figcaption>Photo by <a href=\"https://unsplash.com/@nickkarvounis?utm_source=medium&amp;utm_medium=referral\">Nick Karvounis</a> on <a href=\"https://unsplash.com?utm_source=medium&amp;utm_medium=referral\">Unsplash</a></figcaption></figure><h3>3. Basic Memory utilities</h3><h4>Psutil</h4><p>The Python library <a href=\"https://github.com/giampaolo/psutil\">psutil</a> (process and system utilities) is used to retrieve information on running processes and system utilization.</p><p>The code below illustrates how to use it to retrieve (in bytes):</p><ul><li>The total RAM available, i.e. the memory ceiling</li><li>The RAM used by the entire platform</li><li>The RAM is used by the current process only. The resident set size (RSS) is the portion of memory occupied by a process that is held in RAM</li></ul><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/cfe6861607588137132451b96e55f3b8/href\">https://medium.com/media/cfe6861607588137132451b96e55f3b8/href</a></iframe><h4>Nvidia-smi</h4><p>If you’ve had prior experience with a GPU, you’ve certainly encountered the challenge of installing the right Nvidia drivers. Once everything is correctly set up, the nvidia-smi command-line tool provides real-time monitoring and management of Nvidia GPU devices.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/809/1*A-DiF__jHxpt7LTDqjUBdQ.png\" /><figcaption>nvidia-smi command — Screenshot by the author</figcaption></figure><p>Python bindings for the Nvidia Management Library can be installed via pip3 install nvidia-ml-py3 .</p><p>Just like our approach with psutil, we can also obtain information about the used and total VRAM.</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/c974458c968d45e9d7f18c7ef8791c3b/href\">https://medium.com/media/c974458c968d45e9d7f18c7ef8791c3b/href</a></iframe><p>It makes sense to check beforehand whether or not the GPU is available so that the profiling decorator doesn’t raise an exception and just skips the VRAM profiling. Caching with the @lru_cache decorator prevents unnecessary calls to ‘nvidia-smi’.</p><blockquote>I omitted it here for the sake of readability, but feel free to modify the upcoming functions in the following sections to incorporate a mechanism for bypassing VRAM-related tasks in case a GPU is unavailable.</blockquote><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/96a89092eac464dbbedf15192889408c/href\">https://medium.com/media/96a89092eac464dbbedf15192889408c/href</a></iframe><h4>Kilobytes or Kibibytes?</h4><p>Now that we’ve figured out how to check the RAM and VRAM memory status, let’s talk about the units they use. They’re usually in bytes, which are quite small. We’re more familiar with kilobytes (kB), megabytes (MB), or gigabytes (GB).</p><p>But sometimes, in tools like the Ubuntu System Monitor or nvidia-smi, you’ll see ‘i’ added to the units, like GiB or MiB. These stand for Mebibytes and Gibibytes, not the same as Megabytes and Gigabytes. So, it’s important not to mix them up.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/518/1*y4Pg1cw_70ZP_FXiuQ9zXQ.png\" /><figcaption>Memory and Swap History part of the Resources section of the Ubuntu System Monitor (Left) and GPU Memory part displayed by the nvidia-smi command (Right) — Screenshot by the author</figcaption></figure><p>As explained in the tables below, Kilobytes use a decimal unit system, whereas kibibytes operate on a binary unit system.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*MEPOVxZp94ldto07tvOzww.png\" /><figcaption>Decimal and Binary unit systems to count bytes — Tables by the author</figcaption></figure><p>The psutil library already provides a <a href=\"https://psutil.readthedocs.io/en/latest/#bytes-conversion\"><em>bytes2human</em></a> method that converts a number of bytes into a human-readable string. However, I don’t like the fact that you can’t choose between the decimal or binary systems, so here’s my version. We compute the number of bytes corresponding to each symbol, e.g. K, M, or G, and then generate a string with the more appropriate unit to express the input memory quantity.</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/7c19034419a63769543d772bd64500cd/href\">https://medium.com/media/7c19034419a63769543d772bd64500cd/href</a></iframe><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*khT1RH2byuI2yPG-\" /><figcaption>Photo by <a href=\"https://unsplash.com/@pluyar?utm_source=medium&amp;utm_medium=referral\">Shane Aldendorff</a> on <a href=\"https://unsplash.com?utm_source=medium&amp;utm_medium=referral\">Unsplash</a></figcaption></figure><h3>4. Implement a RAM+VRAM profiling decorator</h3><h4>Context Manager</h4><p>Using the utility tools defined in the previous section, we can now implement a single-function decorator for memory profiling.</p><p>As said earlier, we will draw inspiration from the logic of the @profile decorator of the <em>memory-profiler</em> library. The idea is quite simple: we launch a parallel process alongside the target function, periodically monitoring memory status until the function completes.</p><p>The class defined below is a context manager that implements the mechanism we’ve just described. When entering the context, the __enter__ method is called to launch a memory monitoring process (The definition of the _memory_monitor function comes right after), while the __exit__ method sends an event to stop the monitoring process. Finally, the memory peak is displayed in the console.</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/15b59e0737480daf77f27fa6996df9ce/href\">https://medium.com/media/15b59e0737480daf77f27fa6996df9ce/href</a></iframe><h4>Core memory profiling function</h4><p>As described earlier, the _memory_monitor function is pretty straight-forward and merely calls nvidia-smi and psutil at regular time intervals to keep track of the RAM/VRAM memory peak.</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/2f4c3a1becf056d92ffcfd597bde07f0/href\">https://medium.com/media/2f4c3a1becf056d92ffcfd597bde07f0/href</a></iframe><h4>Function Decorator</h4><p>Once we have the context manager, we can implement a function decorator at no cost. We just need to infer a pretty function name.</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/69c6c9ac66e527da6aa4e851e96b4dfa/href\">https://medium.com/media/69c6c9ac66e527da6aa4e851e96b4dfa/href</a></iframe><h3>Conclusion</h3><p>I hope you enjoyed reading this article and that it gave you more insights on how to monitor your code!</p><pre>See more of my code on <a href=\"https://github.com/ThomasParistech\">GitHub</a>.</pre><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf99876e985f\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/better-programming/ram-and-vram-profiling-in-python-bf99876e985f\">RAM and VRAM Profiling in Python</a> was originally published in <a href=\"https://betterprogramming.pub\">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"}]},"meta":{"timestamp":"2026-06-13T14:37:06.871Z","request_id":"7ffcd7eb-96ad-45d7-af02-1e247e13ad4e"},"status":"ok","message":"Publication posts by tag","success":true}}}},"401":{"description":"Missing or invalid x-oanor-key header"},"402":{"description":"Active subscription required"},"429":{"description":"Rate-limit or monthly quota reached"},"502":{"description":"Upstream did not respond"}}}},"/v1/recommended":{"get":{"operationId":"get_v1_recommended","tags":["Feeds"],"summary":"Stories a user has applauded for (their curation)","description":"","parameters":[{"name":"user","in":"query","required":true,"description":"Medium @handle","schema":{"type":"string"},"example":"quincylarson"}],"security":[{"oanorKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"feed":{"link":"https://medium.com/@quincylarson?source=rss-17756313f41a------3","image":"https://cdn-images-1.medium.com/fit/c/150/150/1*R0u5NcfQM5JfNjFNUMdVcw.jpeg","title":"Stories applauded for by Quincy Larson on Medium","description":"Latest stories applauded for by Quincy Larson on Medium"},"count":9,"items":[{"guid":"https://medium.com/p/0fb7bf339400","link":"https://medium.com/@takafumi.endo/prompt-requirements-document-prd-a-new-concept-for-the-vibe-coding-era-0fb7bf339400?source=rss-17756313f41a------3","title":"Prompt Requirements Document (PRD): A New Concept for the Vibe Coding Era","author":"Takafumi Endo","excerpt":"In conventional product development, the established approach has been to create a Product Requirements Document (PRD) to clearly outline product requirements for the development team. As agile development has gained popularity, the significance of PRDs has only grown. Historically, the PRD served a","updated":"2025-04-08T04:43:17.793Z","published":"Mon, 07 Apr 2025 15:45:51 GMT","categories":["startup","vibe-coding","model-context-protocol","cursor","ai"],"content_html":"<p>In conventional product development, the established approach has been to create a Product Requirements Document (PRD) to clearly outline product requirements for the development team. As agile development has gained popularity, the significance of PRDs has only grown. Historically, the PRD served as a blueprint to align stakeholders, developers, and designers under a unified vision. This alignment helped teams address issues like shifting specifications and misaligned expectations.</p><p>However, in what I call the Vibe Coding era — where AI and humans work side by side — I believe a new concept is necessary: the <strong>Prompt Requirements Document (PRD)</strong>. Whereas traditional PRDs deal mostly with unstructured information for human-to-human alignment, this new PRD focuses on generating, editing, and managing structured prompts (including text, images, videos, and more) that both AI and humans can understand and use effectively.</p><p>This shift reflects our transition from an era where documentation preceded development to one where we first implement prototypes and then use them to define specifications or as input for AI to guide subsequent feature development.</p><h3>Current AI-Assisted Development Environment</h3><p>If you’ve used AI development tools like Cursor or Cline, you might be familiar with the .rules directory and mdc files used to prevent AI from generating unintended code. You can think of that as an early-stage precursor to what I’m calling the Prompt Requirements Document.</p><p>Anyone who’s embraced Vibe Coding or works on a team that leverages AI tools has probably gone through the trial-and-error process of figuring out good prompts. That’s because, without proper instructions and guidelines, AI can easily produce results that are off the mark — something developers experience on a daily basis.</p><p>Indeed, for teams using Claude or other AI tools, managing prompts has become a key challenge for effective communication. Today, it’s not just about code generation; AI is also used for defining requirements, testing, and documentation across the entire development lifecycle. To handle this scope successfully, a systematic approach is crucial.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*s7rlkG9jHnX8ZL4oHvF47g.png\" /><figcaption>A sample of my .rules</figcaption></figure><h3>Observed Limitations in AI Rule-Setting</h3><p>I’ve been experimenting with various approaches to rule-setting. Lately, I’ve settled on a cycle of feature implementation → refactoring → rule updates. I let the AI implement a feature, then look for improvements and rework the design, and finally update the rules to capture our new insights. I use tools like Cursor, Cline, and Claude to keep this cycle moving swiftly.</p><p>Of course, there are still challenges. One main issue is that no matter how refined your rules are, you can’t fully control AI output. It’s heavily dependent on the constraints of the context window as well as the quality of the information you provide.</p><p>Over longer sessions, many developers have experienced how the AI’s focus can drift away from the original intent. This echoes the traditional problem of shifting requirements in software development, but in an AI-driven setting, it tends to appear even more pronounced.</p><h3>Developing Optimal Starting Prompts for AI Collaboration</h3><p>To address these challenges, many teams now leverage modes like “Plan” in Cline or “Architect” in Roo Code. They conduct a thorough review of the entire project, collaborate with AI to devise requirements and implementation plans upfront, and then execute development. This type of planning helps maintain clear alignment between humans and AI throughout the process.</p><p>In addition to rules, <strong>the quality of your initial prompt is incredibly important</strong>. Just like people, AI struggles to produce quality results when instructions are vague. That’s why the Prompt Requirements Document is so vital.</p><p>Even when utilizing Cursor with comprehensive .rules files and properly structured code, many developers have experienced how significantly initial instructions can influence outcomes. No matter how intelligent the AI, if the initial direction misses the mark, you can fall into unproductive response loops, accumulating implementations bloated with unnecessary context. It’s as if the team development atmosphere gradually deteriorates, making effective work increasingly challenging.</p><h3>The Nature of Vibe Coders</h3><p>Vibe Coders are typically brimming with enthusiasm and energy. They crave rapid development cycles and immediate results, preferring a “learn-as-you-go” style. They believe that by using AI, they can shortcut portions of the traditional process while still producing solid outcomes.</p><p>However, for this pace to be sustainable and truly efficient, it’s essential to raise the quality of AI interactions and ensure consistent output — and that’s precisely where the Prompt Requirements Document comes in.</p><p>By introducing a Prompt Requirements Document, even junior developers or non-technical contributors can leverage AI support to actively participate in complex projects. This, in turn, promotes diversity and creativity within the team.</p><p>The Prompt Requirements Document I envision holds this transformative potential. Ideally, such documentation shouldn’t create additional burden for developers or slow down the rapid cycle that Vibe Coders thrive in. Rather than requiring explicit documentation efforts, I believe PRDs will evolve to be automatically generated from the implementation traces left by Vibe Coders as they work.</p><p>These automatically generated documents would capture valuable patterns and insights that benefit both humans and AI, creating a virtuous learning cycle. This way, the documentation becomes an organic byproduct of the development process itself — enhancing collaboration without sacrificing the speed and spontaneity that defines the Vibe Coding approach.</p><h3>G3 Framework of a Prompt Requirements Document</h3><p>A well-crafted Prompt Requirements Document typically includes:</p><ol><li><strong>Guideline: Shared AI-Human Understanding</strong><br>A comprehensive knowledge base that establishes the project context, technical rationale, and architectural decisions. This foundation ensures both AI assistants and human team members operate from the same understanding, creating consistency in approach and direction — similar to how a traditional PRD aligns stakeholders around product requirements.</li><li><strong>Guidance: Methodology for Evolving Prompt<br></strong>A structured approach designed to help developers evolve abstract ideas into precise instructions that AI systems can accurately interpret and execute. This system includes annotated prompt examples, pattern libraries, contextual best practices, and common pitfall warnings — enabling even those new to AI collaboration to create effective prompts that produce consistent, high-quality results.</li><li><strong>Guardrails: AI-Assisted Code Reviews</strong><br>A defined set of automated evaluation standards and quality checkpoints specifically tailored to address known project risks and recurring pain points. These guardrails enable AI systems to perform preliminary code reviews on pull requests, identifying basic issues before human review begins. This systematic approach reduces the cognitive load on human reviewers, allowing them to focus on more complex architectural concerns while ensuring consistent enforcement of code quality standards.</li></ol><p>With these elements in place, Vibe Coders can take part in more advanced development processes and collaborate effectively with seasoned engineers.</p><h3>Comparing Conventional PRDs and AI-Driven PRDs</h3><p>Traditional PRDs and AI-driven PRDs — Prompt Requirements Documents — differ in several significant ways.</p><ul><li><strong>Purpose<br></strong> Conventional PRDs focus on aligning human stakeholders and setting a unified direction. <br>AI-driven PRDs facilitate effective collaboration between people and AI, serving as a bridge.</li><li><strong>Content</strong><br>Conventional PRDs are often unstructured text, images.<br>AI-driven PRDs, however, manage prompts in various formats (text, images, videos) in a structured way, allowing AI to parse and respond more accurately.</li><li><strong>Focus</strong><br>Conventional PRDs center on human comprehension. <br>AI-driven PRDs prioritize optimizing the human–AI collaboration process, paying constant attention to how AI interprets the instructions.</li><li><strong>Tools</strong><br>While Conventional PRDs often live in tools like Notion, Jira, or online whiteboards, AI-driven PRDs leverage systems like GitHub, ChatGPT, Cursor, or Cline for more interactive and real-time interactions with AI.</li><li><strong>Challenges</strong><br>Conventional PRDs often wrestle with differences in human understanding. <br>AI-driven PRDs must tackle issues like the AI’s context window limits and the need for high-quality prompts. Because AI’s ability to interpret context can be limited, your instructions must not only be clear to humans, but also be formatted in a way that machines can process.</li></ul><p>Rather than replacing traditional PRDs entirely, AI-driven PRDs are complementary. A hybrid approach that combines the strengths of both can be especially effective in modern development environments.</p><h3>Where Prompt Requirements Documents Are Headed</h3><p>While still in an experimental phase, Prompt Requirements Documents have the potential to become a foundational element of AI-driven development. My team’s products, <a href=\"https://giselles.ai/\">Giselle (AI App Builder)</a> and <a href=\"https://liambx.com/\">Liam (Database Builder)</a>, are already exploring ways to integrate these concepts into more tangible services and features. Ultimately, they’re all working toward the same goal: establishing a common language that makes it easier for humans and AI to cooperate.</p><p>Here are a few potential directions for the future:</p><ul><li><strong>Standardization</strong><br>Developing universal standards for PRDs in AI-driven environments could simplify adoption and boost effectiveness.</li><li><strong>Advanced AI Capabilities</strong><br> As AI tools evolve, they’ll take on an even larger role in refining and validating PRDs, reducing the human workload while improving overall quality.</li><li><strong>Broader Applications</strong><br> The core principles of PRDs can extend to areas beyond software development — like marketing or customer service — where AI is also playing an increasingly important role.</li></ul><h3>An Ideal PRD Workflow</h3><p>The ideal workflow for utilizing Prompt Requirements Documents might look like this:</p><ol><li><strong>Initial Setup</strong><br> Define the basic rules and project overview.</li><li><strong>Requirements Design</strong><br> Clarify features in collaboration with AI.</li><li><strong>Refining Instructions</strong><br> Translate those features into machine-friendly prompts (like special “magic prompts”).</li><li><strong>Implementation</strong><br> Co-create code with AI and perform verification.</li><li><strong>Refactoring</strong><br> Improve quality and maintain coherence.</li><li><strong>PRD Updates</strong><br> Integrate lessons learned and new information back into the PRD.</li></ol><p>In AI-assisted development case studies, teams have managed to drastically shorten development time by integrating requirements, implementation, and testing in a single streamlined process. In each of these success stories, a well-defined PRD and systematized AI communication were critical factors.</p><p>AI-driven PRD is a concept well-suited for the Vibe Coding era, but I believe it’s essential that the document itself can be updated with the same tempo and “vibe”. Teams and environments that can facilitate this kind of flexible updating will become stronger in the AI era.</p><p>The recent boom in Model Context Protocol (MCP) is further accelerating this trend. AI systems capable of performing a wide range of tasks from document analysis to read/write operations are making information increasingly accessible. As these technologies advance, the occasions where humans need to manually create documentation will likely diminish significantly.</p><p><a href=\"https://medium.com/@takafumi.endo/why-model-context-protocol-mcp-is-essential-for-next-generation-vibe-coding-e2a55a64c287\">Why Model Context Protocol (MCP) is Essential for Next-Generation Vibe Coding</a></p><h3>PRDs as the Human-AI Bridge</h3><p>Of course, adopting and using Prompt Requirements Documents comes with its own set of challenges:</p><ul><li><strong>Context Limitations</strong><br> AI output is heavily dependent on the quality and scope of the context data provided. Poor or incomplete context can easily lead to deviations from the intended outcome.</li><li><strong>Human–AI Collaboration</strong><br> While AI tools boost productivity, their outputs need careful monitoring to confirm they meet expectations. This raises questions about striking the right balance between automation and human oversight.</li><li><strong>Evolving Standards</strong><br> There is no fully established best-practice standard for AI-driven PRDs yet, which can make it difficult to ensure consistent adoption across teams.</li></ul><p>Addressing these issues requires ongoing experimentation, learning within each team, and sharing best practices with the broader community.</p><p>In the Vibe Coding era, the skill of communicating effectively with AI can be just as crucial as coding prowess itself. Prompt Requirements Documents are poised to be a key resource for enhancing this communication, and I believe they will become central to future AI-driven development processes.</p><p>By focusing on the quality of prompts and leveraging AI tools, PRDs can introduce a more rapid, efficient, and collaborative style of development. Yet they also bring about new challenges — such as the need for better context and a drive toward standardization. As AI continues to evolve, PRDs will form the bridge between human creativity and machine efficiency.</p><p>I’m hopeful that this new methodology will gain broader acceptance, helping pave the way for a future in which humans and AI can work together in truly creative and productive ways.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*H8I-ENbVycr1qQaZ-zTCnQ.png\" /></figure><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0fb7bf339400\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/a443a2c0127c","link":"https://medium.com/@donna.m.ware/the-guardian-angel-of-my-post-pandemic-career-change-is-freecodecamp-org-a443a2c0127c?source=rss-17756313f41a------3","title":"The Guardian Angel of My Post Pandemic Career Change is freeCodeCamp.org","author":"Donna M Ware","excerpt":"Why I Changed My Career Post-Covid I had been fantasizing about how I would escape the toxic relationship that was sucking the air from my lungs and killing me from the inside out. Then one night, my dream finally became a reality. When I got home, I was greeted by the cold eyes of my ex. I could te","updated":"2022-08-08T12:57:53.470Z","published":"Mon, 08 Aug 2022 12:26:57 GMT","categories":["coding","career-change","quincy-larson","freecodecamp","post-pandemic"],"content_html":"<h4>Why I Changed My Career Post-Covid</h4><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*qP9YRPv2_YbqZQ25-MF1tw.jpeg\" /></figure><p>I had been fantasizing about how I would escape the toxic relationship that was sucking the air from my lungs and killing me from the inside out. Then one night, my dream finally became a reality. When I got home, I was greeted by the cold eyes of my ex. I could tell he was drunk and geared up to punish me for some crazy shit he’d concocted in his mind. I had had enough and decided that motherfucker wasn’t going to hit me that night. I packed three bags and bought a one-way train ticket from Detroit to Rhode Island.</p><p>That was about seven years ago, jump start to 2020 and the seeds of my risky jump were finally beginning to sprout. I had miraculously saved enough money to open my own hair salon, my business was starting to grow and I could see the light at the end of the tunnel. Then the Goddamned pandemic hit…</p><h4>Covid, The Catalyst of Change</h4><p>I was mandated to temporarily close my salon. The CDC(Centers of Disease Control and Prevention) and <a href=\"https://www.niaid.nih.gov/about/director\">Dr. Fauci</a> determined hair care was a nonessential convenience. Over the course of those bizaare times, the Coronavirus pandemic changed everything, including me.</p><p><a href=\"https://time.com/6051955/work-after-covid-19/\">Working from home</a> felt safe. I weighed my options and made a career shift. I now have a home-based business doing copy/content writing and website development.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*IcWOgkdtZGryoPaSQWXcYA.jpeg\" /></figure><h4>The Absurd Amount of Free Training I am Devouring at freeCodeCamp.org</h4><p>As a website developer coding is essential for my success. But how was I going to learn coding? Surprisingly, I discovered many free options, the one I chose was <a href=\"https://www.freecodecamp.org/\">freeCodeCamp.org</a>. freeCodeCamp’s specific and concise curriculum is designed for clueless newbies like me.</p><p>I had zilch coding experience and didn’t know where to start. freeCodeCamp.org solved that problem with its building block design. I am forever grateful to <a href=\"https://medium.com/@quincylarson\">Quincy Larson’s </a>well thought out program that’s walking me through coding step-by-step. I am also beyond thankful for the insane amount of tutorials freeCodeCamp.org provides on <a href=\"https://www.youtube.com/c/Freecodecamp\">YouTube</a>. So many people have put their time and expertise into easy to understand videos that help me when I get stuck on a lesson.</p><p>I am feeling especially blessed for this awesome program because academia didn’t offer me anymore options. Like many other Americans, I am maxed out on my student loans.</p><p>I already have a bachelor’s of arts degree in philosophy and tons of credits towards my master’s. But due to my poor choice in a spouse, I quickly exasperated my student loan limits. Attending school while I was in an unstable marriage destroyed any chances of properly using my student loan money.</p><p>I was constantly on academic probation, re-taking classes, dropping classes and basically fucking up. I thank God for <a href=\"https://www.freecodecamp.org/\">freeCodeCamp.org</a> and the many other free training opportunities available for people like me; who have found themselves in tough financial positions. Especially concerning the building of new skill sets, they are essential in this post-pandemic era.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*OiwD8O4MwmmknOjIJWZgJg.jpeg\" /></figure><h3>Don’t Give Up on Your Dreams</h3><p>If you are reading this and are in any way inspired, take the next step! The Covid-19 pandemic has forever reshaped the way we work. People want change and are taking control of how and where they work. <a href=\"https://www.nytimes.com/2022/03/23/business/pandemic-workers-careers.html\">There are so many free training options available and inspirational stories of people beating the odds.</a></p><p>Technology isn’t the only transitional career path that offers free education. Believe in yourself and start Googling the things you find interesting. YouTube is another platform that offers an incredible amount of how to information. Here are some cool things I’ve found that might interest you:</p><ul><li><strong>Free Cooking Classes:</strong> <a href=\"https://ice.edu/newyork/free-online-cooking-classes\">ICE, Intitue of Culinary Education</a> offers free online cooking classes. They are distributed via Zoom Webinar with “plant-based comfort foods, the ultimate cookies, one-pot paella, festive Taco Tuesdays and basic sous vide techniques.”</li><li><a href=\"https://tradersacademy.online/\"><strong>Trader’s Academy</strong></a><strong>:</strong> “a free financial education service provided by Interactive Brokers for those seeking a better understanding of markets, asset classes, currencies and trading tools.” They have a slick website with an incredible amount of knowledge.</li><li><strong>Free Makeup Classes:</strong> <a href=\"https://www.skillshare.com/browse/makeup\">Skillshare.com</a> has all kinds of talented makeup artists that want to teach you this amazing skill.</li><li><strong>How to Become a Rap Artist: </strong>just Google this, all kinds of videos and articles populate, there where about 32,100,000 search results</li><li><strong>Photography:</strong> lifehacker.com teaches the basics of photography, MIT(Massachusetts Institute of Technology has videos and lectures on the subject and many others. Here’s a great article by <a href=\"https://www.adorama.comn/alc/10-best-free-online-photography-courses/\">Adorama</a>.</li></ul><h3>The New Frontier of Careers and Education</h3><p>freeCodeCamp.org has been my educational guardian angel, navigating me through the changing landscape of technology. There are many educators available, in other fields. They are just waiting for your arrival.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a443a2c0127c\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/ab1a14538684","link":"https://darrellsilver.medium.com/ive-started-a-company-company-ab1a14538684?source=rss-17756313f41a------3","title":"I’ve started a company company!","author":"Darrell Silver","excerpt":"Frustration is the better mother of invention so 18 months ago I backed Ben to start Union . Mostly it was to shut him up. Ben is Thinkful’s former Head of People where he scaled us from 30 to 130 FTEs and played a critical role in our acquisition. But he spent 2020 complaining that the companies he","updated":"2022-07-07T15:25:35.897Z","published":"Thu, 07 Jul 2022 15:25:35 GMT","categories":[],"content_html":"<figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*iuQkOE8zuveD_IOjLDRX3g.png\" /></figure><p>Frustration is the better mother of invention so 18 months ago I backed <a href=\"https://www.linkedin.com/in/ben-aronowicz-83680031/\">Ben</a> to start <a href=\"https://www.teamunion.com/\">Union</a>. Mostly it was to shut him up. Ben is Thinkful’s former Head of People where he scaled us from 30 to 130 FTEs and played a critical role in our acquisition. But he spent 2020 complaining that the companies he was excited to work for were headed toward avoidable mistakes without the will to avoid them. For my part I had just left Chegg and was heading for some lived-through-M&amp;A-and-integration-worked-full-time-since-2004-about-to-turn-40 time off.</p><p>My time off really was mostly time off: building a woodshop, traveling, all that good stuff. But backing Ben was so rewarding I found myself repeating it two more times with two other underappreciated founders, supporting two non-profits (Donate to <a href=\"https://www.theyoungcenter.org/\">Child Advocacy</a>! Donate to <a href=\"https://darrellsilver.medium.com/why-im-donating-150k-to-freecodecamp-to-help-fund-their-next-big-curriculum-ffb7ff4ae828\">freeCodeCamp</a>!), advising and/or investing in 20 Seed to D-stage companies, and trying but failing to get excited about crypto.</p><p>Union is a recruiting firm that gets companies to hire better, retain longer, and manage performance without bias. Because of Ben in 10 years no one will hire in-house HR. I’ve backed him with early customer intros, a budget in case it wasn’t quickly profitable, and a promise to collaborate on how to grow.</p><p>The last 18 months taught me something stupidly unexpected: There’s boundless fulfillment in helping someone else succeed. That’s weird, right? Have I mentioned I was the CEO of Thinkful, an entire <em>school</em> based on <em>mentoring</em>? Yes, well, perhaps Ben, Blake, Parniyan, and Arlyss have just shown me how to mix co-founding (which I’ve loved since 2009!) and mentoring (which I fell in love with thanks to Dan in 2012!) and investing (which is nearly criminally not time consuming…).</p><p>Which brings us to starting a company company. Three days a week I’m now working as a co-founder on three companies under the arbitrarily named holding company “503 Corp”. Union is profitable, growing 10% monthly, isn’t saas (yet) and as a result can’t yet be a monopoly (and so isn’t yet raising money). The others are earlier, but like Union, <em>need</em> to exist. Aside from their great co-founders, each share a belief that companies and careers will be better with fewer cost centers. Instead of reluctantly hiring cost center FTEs then slowly integrating software together, companies by 2030 will hire great experts that come bundled with best in class software that can be piloted cheaply, turned on quickly, and scaled endlessly. 503 is testing where that thesis proves most true. Put another way, 503 is creating companies like <a href=\"https://pilot.com/\">Pilot</a>, which already bundles bookkeeping with QuickBooks. <a href=\"https://www.teamunion.com/\">Union</a> is Pilot for HR, the second company is Pilot for FP&amp;A, and the third, <a href=\"https://www.goodgig.work/\">GoodGig</a>, is Pilot for subcontracting.</p><p>My other days are spent researching, maybe another company, we’ll see.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ab1a14538684\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/fe53909aca8b","link":"https://medium.com/@jaybhaskar/collaborative-workflows-under-the-ka%E1%B9%87%C4%81da-open-source-initiative-kosi-fe53909aca8b?source=rss-17756313f41a------3","title":"Collaborative Workflows under The Kaṇāda Open Source Initiative (KOSI)","author":"Bhaskar Jayaraman","excerpt":"Collaborative Workflows Collaborative Workflows — Making document reviews easier With Collaborative Workflows you can prevent accidental sharing and usage of docs by capturing and displaying their workflow state, so the document collaborator knows, whether the author of the doc is requesting for doc","updated":"2022-09-27T15:23:13.467Z","published":"Sun, 30 Aug 2020 23:05:13 GMT","categories":["g-suite","apps-script","civilization","science","history"],"content_html":"<h3>Collaborative Workflows</h3><h3>Collaborative Workflows — Making document reviews easier</h3><p><strong>With Collaborative Workflows you can prevent accidental sharing and usage of docs by capturing and displaying their workflow state, so the document collaborator knows, whether the author of the doc is requesting for document approval, whether they have all necessary approvals, whether they’ve made all changes requested, look up the list of reviewers, and if it’s been published.</strong></p><p><strong>The document state is saved within the document’s key value store so you don’t need any external database to save state. You may extend it’s functionality further with the source code shared.</strong></p><h3>Installation</h3><p>Use this github repo link to get a copy of this project — <a href=\"https://github.com/lotusbaba/Collaborative-Workflows\">https://github.com/lotusbaba/Collaborative-Workflows</a></p><p>The first step in running this project is to open a google apps script project on https://script.google.com and creating your first apps script project.</p><p>Google doesn’t let you publish the project on the <a href=\"https://gsuite.google.com/marketplace/\">G Suite Marketplace</a> so you can make it work you in one of the following ways -</p><ul><li>Right now there doesn’t seem to be a way to download github repos directly to an app script project so after downloading the repo you will need to copy files onto the script editor one by one.</li><li>But there is a useful <a href=\"https://chrome.google.com/webstore/detail/google-apps-script-github/lfjcgcmkmjjlieihflfhjopckgpelofo?hl=en\">chrome extension</a> which lets you interact with github from the apps script editor. Generate your personal access token for your app on Github</li><li>Use the Login SCM button to authorize the chrome extension with your github login and the access token you just created</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/492/1*oa0pf2T_4zTcTBnoEeJeEw.png\" /></figure><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/608/1*9wqOhMg6j3QtNAUW-c9eAA.png\" /></figure><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/990/1*niJJvSXWln8B4Vf08GSVGg.png\" /></figure><ul><li>After that you will be able to push or access github repos from your account within apps script editor</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/888/1*xiUX_MmxIGm0r4FusGCRkg.png\" /></figure><ul><li>The project in itself won’t still be useful unless it runs on a google docs document. So you can create and assign a test document by navigating to run -&gt; test as add on, within the script editor and selecting the doc (document, slide, or sheet). Make sure to choose the ‘Installed and enabled’ mode -</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*a-qKYKtweeIlhGMieBc2aw.png\" /></figure><ul><li>Once the document has been selected and saved it should appear in the list as shown below -</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*1AW26arK7PE1yzDTXKb51g.png\" /></figure><ul><li>You can save multiple docs for testing and when you wish to test it next time you simply use the interface to select the doc you need and run the code on it</li><li>Another way to run your code is to publish it within a doc (document, slide, or sheet) by opening a script editor within the doc -</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/892/1*r4c4lxy0v1XQyUYX_XvbbQ.png\" /></figure><p>The drawback to the above approach is that the app wont be available on other doc files or on any other G-docs suite such as spreadsheets and slides.</p><p>Installing the app on every doc one by one by opening the script editor is not a scalable approach but it helps you test the app</p><ul><li>You could still make the app available to all docs within your account by publishing it to a google project by visiting to your GCP account and creating a GCP cloud project and using the GCP project number in the window below to set the project and associate it with the apps script project</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*XH54xq9uZuDPG2yQSJUiEA.png\" /></figure><ul><li>Once that is done in order to publish the project on GCP you navigate to the APIs &amp; Services Dashboard</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/930/1*pXIML99lEnr_ZRq6UQ15-w.png\" /></figure><ul><li>and then to the G Suite Marketplace</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/960/1*TMcG2fp9uciAxSFhK3B5rA.png\" /></figure><ul><li>Once here go back to your apps script editor and copy the project key -</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/580/1*rNyjer3egUJYjssFfKZ-Ow.png\" /></figure><ul><li>Also save the app version in the apps script editor Manage versions interface -</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*MzhEsC0vuJP-diAqYshMvw.png\" /></figure><ul><li>Now paste the project key and version in the GCP G Suite Marketplace window and list it as a private app</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/840/1*bveahuBnPNVcSj28eelm_g.png\" /></figure><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*ODC1jm9Iyczrzu1hQJUhYg.png\" /></figure><ul><li>With the app you will also need to save store listing details like privacy, app logo, screen, category, detail etc.</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*NGHpazLlJSS4G0NteRgEng.png\" /></figure><ul><li>The app publishing to G Suite Marketplace usually succeeds with corporate accounts but when you are an individual developer it usually doesn’t so if it did for you please let me know what worked.</li><li>Once this is done you can use the app as an add on after you install it</li></ul><h3>Usage</h3><ul><li>Open it within the add on toolbar and hit Get Started</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*GIr7IoR1h6RGK4RtjlFw1Q.png\" /></figure><ul><li>Notice the initial document status is ‘Being Edited’</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/638/1*jkT2nWyJ1_LMSWMlMSbvOw.png\" /></figure><ul><li>You may change the state of the document from the add on window</li><li>Share the doc with reviewers</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*XQfWcuHeN_Je_WyOlJAcIg.png\" /></figure><ul><li>Send notifications to reviewers</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/662/1*Iiy1fSBD6tT0XKa4WjHxoA.png\" /></figure><ul><li>Receive email notifications</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*uMJG6c2EfF1tA6QBasN0fw.png\" /></figure><ul><li>View reviewer status and document workflow status</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/664/1*H_wKtL_5zYibXfcl6ZsU2A.png\" /></figure><ul><li>Either send it back for more change or approve</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/598/1*y37krEz8DXt03Yc-RiCN7A.png\" /></figure><ul><li>Approve the doc</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/602/1*HTgZ3fPId4MkXdXd5iBRvQ.png\" /></figure><ul><li>Publish the doc through the add on tool bar</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/954/1*FziybSzaHNzcsZjiE7liwQ.png\" /></figure><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/638/1*99My1MoYc-oNc10nSl1Y_w.png\" /></figure><ul><li>On the published doc view the approval state of each reviewer</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/622/1*JovFKFBbT8HvEVE3sk4O3A.png\" /></figure><h3>Closing Remarks</h3><p>This project is published under the Kaṇāda Open Source Initiative (KOSI). This is an initiative to spread awareness about unrecognized individuals or peoples whose important contributions in science, technology, philosophy, language, politics, art, architecture, and culture continue to have an impact to this day.</p><p>Kaṇāda’s writings lead us to conclude that he was what we would call a theoretical physicist who validated several thought experiments.<br>His postulations of the laws of motion predate any other modern day scientist.</p><p>Simple rules to follow while contributing with Kaṇāda Open Source Initiative (KOSI)<br>1. Please use primary or secondary sources of reference such as yours or someone else’s research on the topic, via writings, pictures, or videos<br>2. Please avoid tertiary sources of reference (i.e. to wikipedia, quora etc.)<br>3. Please make an effort to publish code which is useful and not just snippets<br>4. You may use the preamble or inline description for your stories and references e.g. to the Incas and their art or architecture<br>5. Please include this preamble to your project and if it’s an HTML file you will notice the preamble is enclosed within the &lt;script&gt; element</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/269/0*MErKDX_8fmXuaTVz.jpeg\" /></figure><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fe53909aca8b\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/ffb7ff4ae828","link":"https://darrellsilver.medium.com/why-im-donating-150k-to-freecodecamp-to-help-fund-their-next-big-curriculum-ffb7ff4ae828?source=rss-17756313f41a------3","title":"Why I’m donating $150k to freeCodeCamp to help fund their next big curriculum","author":"Darrell Silver","excerpt":"Why I’m donating $150k to freeCodeCamp to help fund their advanced math &amp; machine learning curriculum freeCodeCamp is a non-profit unicorn: It delivered 1.3 billion minutes of free coding education last year, grows 60% every year, and is sustainably run by just 12 full time staff and hundreds of","updated":"2021-02-02T17:31:53.788Z","published":"Tue, 02 Feb 2021 17:31:53 GMT","categories":["coding","freecodecamp","education","non-profit-organization"],"content_html":"<h3><strong>Why I’m donating $150k to freeCodeCamp to help fund their advanced math &amp; machine learning curriculum</strong></h3><p><a href=\"https://www.freecodecamp.org/\">freeCodeCamp</a> is a non-profit unicorn: It delivered <a href=\"https://www.freecodecamp.org/news/freecodecamp-2020/\">1.3 <em>billion</em> minutes of free coding education</a> last year, grows 60% every year, and is sustainably run by just 12 full time staff and hundreds of volunteers. That’s a smaller team than when Facebook acquired Instagram for $1b.</p><figure><img alt=\"freeCodeCamp has 60% growth every year since 2015.\" src=\"https://cdn-images-1.medium.com/max/1024/0*6aHFsBOmSc4M7D99\" /></figure><p>A few weeks ago I saw <a href=\"https://twitter.com/ossia/status/1340016441219805195\">a tweet</a> offering a way to support them for just $5 / month. I wondered if there was a way to help accelerate them toward the second.</p><p>I wrote <a href=\"https://twitter.com/ossia\">Quincy</a>, the founder. <em>“If you could raise tens of thousands all at once, what would you do with it?”</em></p><p>His reply:</p><blockquote><strong><em>Math</em></strong><em>. You can get a developer job without a strong background in math. But you’ll need to learn statistics and linear algebra before you can do a lot of software engineering and data science tasks. Traditionally developers have learned these by going back to school to get a graduate degree. But what if developers had a linear curriculum to learn math, data science, and machine learning at their own pace, for free?</em></blockquote><p>The more I learned the more I fell in love.</p><p>First, non-profit giving is super expensive. For every dollar you give most well respected non-profits spend 20 cents on raising more dollars (and <a href=\"https://www.give.org/charity-landing-page/bbb-standards-for-charity-accountability\">best practice allow</a> up to 35 cents!). By comparison, freeCodeCamp spends $0.00 on fundraising. Instead, it maintains a <a href=\"https://www.freecodecamp.org/donate/\">SaaS-like donation system</a>. This means for every dollar donated to freeCodeCamp, one hundred pennies go toward providing more minutes of learning to people around the world.</p><p>Second, large-dollar donors make non-profits’ budgets unpredictable, making attracting top talent very difficult. But freeCodeCamp is funded almost entirely by a broad coalition of $5 / month supporters. So its future cash flows are far more predictable — similar to the most robust B2C businesses. This stability means Quincy attracts the best talent, which creates — you guessed it — even more minutes of (free!) learning.</p><p>Third, after 5 years of running their YouTube ad-free, freeCodeCamp <em>just </em>turned on YouTube ads. But instead of going on a big spending spree they’re saving all that ad revenue for a rainy day, giving them even more long-term stability. Again, while most non-profits <em>spend </em>money<em> </em>on ads for fundraising, freeCodeCamp <em>earns </em>money<em> </em>from ads, instead growing through reputation and SEO.</p><p>Put it all together and freeCodeCamp represents the rarest of rare opportunities: self-sustaining non-profit unicorn. The staff, the tools, the servers, the community … it’s already self-sustaining because monthly donations grow in tandem with usage. So, when we expand curriculum in math and machine learning it’ll increase use, which will increase the donor base. This one-time donation will sustainably increase both baseline usage and monthly donations.</p><p>That’s where you and I come in. I’m 100% matching all donations to freeCodeCamp, up to a total raised of $300k. If you’ve ever considered giving $50 to the the most efficient education non-profit on the web, now’s your chance to make it double: Your $50 automatically becomes $100.</p><p><a href=\"https://www.freecodecamp.org/news/p/4476d664-eb83-47c9-8328-903a78865c8f#the-2021-data-science-curriculum-pledge-drive\"><strong>Donate now</strong></a><strong>.</strong></p><p>And after <a href=\"https://darrellsilver.medium.com/founding-year-eight-a8d60aa3dbf1\">eight years in online coding education</a> I’ve come to greatly respect what Quincy is building and how he’s building it. Education and entrepreneurship take passion and patience. For most of us it takes money, too. Quincy has shown how to build a unicorn.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*Sf0aojZGr92hFQ43AMPYpQ.png\" /></figure><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ffb7ff4ae828\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/9699a2e1132f","link":"https://darrellsilver.medium.com/departing-thinkful-a-new-call-to-code-9699a2e1132f?source=rss-17756313f41a------3","title":"Departing Thinkful & A New Call to Code","author":"Darrell Silver","excerpt":"When we founded Thinkful in 2012 it was labeled “alternative” education second to degrees and 4-year college. Eight years later 62% of Americans prefer non-degree skills training over degrees. In 2019 we sold to Chegg for $100m , lowered prices for students, integrated, hit plan , and hired for the ","updated":"2020-10-28T21:31:46.497Z","published":"Tue, 27 Oct 2020 20:35:59 GMT","categories":[],"content_html":"<p>When we founded Thinkful in 2012 it was labeled “alternative” education second to degrees and 4-year college. Eight years later <a href=\"https://www.insidehighered.com/news/2020/06/24/americans-seeking-change-job-fields-prefer-nondegree-training-make-jump\">62% of Americans prefer non-degree skills training</a> over degrees. In 2019 we <a href=\"https://investor.chegg.com/Press-Releases/press-release-details/2019/Chegg-to-Acquire-Online-Skills-Based-Learning-Platform-Thinkful-to-Help-Students-Accelerate-their-Path-from-Learning-to-Earning/default.aspx\">sold to Chegg for $100m</a>, lowered prices for students, integrated, <a href=\"https://seekingalpha.com/article/4381484-chegg-inc-chgg-ceo-dan-rosensweig-on-q3-2020-results-earnings-call-transcript?part=single\">hit plan</a>, and hired for the experience necessary to scale inside a public company. Chegg also nearly tripled in value. Chegg and Thinkful are shaping the future of education.</p><p>But America’s recovery from COVID will accelerate wealth and opportunity injustices: In 2021 I fear a lucky few will continue getting richer, while debt and uncertainty will suffocate most Americans’ ambition. This delays and destroys millions of families and careers. <a href=\"https://www.brookings.edu/blog/the-avenue/2020/03/19/covid-19-puts-americas-low-wage-workforce-in-an-even-worse-position/\">44% of American workers earn $10 / hour</a>. <a href=\"https://www.experian.com/blogs/ask-experian/research/consumer-debt-study/\">The average American is $90k in debt</a>. <a href=\"https://www.healthsystemtracker.org/chart-collection/u-s-life-expectancy-compare-countries/#item-le_total-life-expectancy-at-birth-in-years-1980-2017_dec-2019-update\">We already live four fewer years than our peers</a>.</p><p>Those of us trained to rethink large systems must see this as a “Call to Code”. Lucky risk-takers (and yes, my success is mostly <a href=\"https://www.cnbc.com/2018/10/16/warren-buffett-says-being-a-white-man-helped-him-succeed.html#:~:text=The%20ovarian%20lottery%20is%20%E2%80%9Cthe,into%20strongly%20affect%20your%20success.\">luck</a>) must figure out how to better distribute luck. Education will be one solution. Others surely include answering how gig and frontline work can engender economic mobility, and what replaces the social safety net. To play on a <a href=\"https://en.wikiquote.org/wiki/William_Gibson#Quotes\">phrase</a>, opportunity is already here — it’s just not very evenly distributed. Let’s fix that.</p><p>Thinkful now has the best leadership, brand, team, educators, and in Chegg, steward, that it’s ever had. So, it’s time for me to move on. My transition out offers me the privilege of asking big questions, again. I’ll be contributing some answers soon enough. But first, I’ll spend a bit of time with my husband, our family, our friends, and our native NYC.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9699a2e1132f\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/a8d60aa3dbf1","link":"https://darrellsilver.medium.com/founding-year-eight-a8d60aa3dbf1?source=rss-17756313f41a------3","title":"Founding Year Eight","author":"Darrell Silver","excerpt":"Today we’re founding the next era in Thinkful history: an era where our mission to build the world’s next workforce truly begins. For seven years we’ve known, deep down in our soul, that all adults should learn the way we teach. We’ve proven it works with our audited results. Now we get the chance t","updated":"2020-12-11T14:37:48.154Z","published":"Wed, 04 Sep 2019 19:34:12 GMT","categories":["bootcamp","education","startup"],"content_html":"<p>Today we’re founding the next era in <a href=\"https://www.thinkful.com/\">Thinkful</a> history: an era where our mission to build the world’s next workforce truly begins. For seven years we’ve known, deep down in our soul, that all adults should learn the way we teach. We’ve proven it works with our audited results. Now we get the chance to deliver not just for the thousands who choose us today, but for the millions who’ll choose us next.</p><p>“Bootcamps” were started as an <em>alternative</em> to expensive and slow higher ed programs, but prices have ticked up every single year since 2012, now crossing $20k. High cost is bad for students (even when you pay later). High cost limits access, and is a slow ooze toward the status quo. This year Dan &amp; I came to realize that to build the world’s next workforce prices need to go way, way down. And this must be done without compromise to quality, support, and extraordinary student outcomes.</p><p>That’s why we’re ecstatic to join Chegg. They’re the adults in the room bootcamps need. Chegg is a massive company in education, and today we’ve announced it will acquire Thinkful for $100m. Together we share a deep belief that the future of adult education must be accountable for student outcomes, low-risk, high impact, and incredibly affordable. Our values are deeply aligned: Student-first, intellectually honest, transparent, and approaching everything we do with an unreasonable passion. Chegg chose Thinkful as a platform to reach adult learners, perfectly complementing its existing learner base of millions of college students. And <a href=\"https://www.thinkful.com/about/#careers\">we’re hiring</a>.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*QI-TWfuFb0xQo7Xm\" /><figcaption>“Learning is not a four-year thing, it’s a forever thing” in Chegg’s NYC office</figcaption></figure><p>We’ve been working on this for months — plenty of time to consider and plan, course correct and discuss. Above all I’ve realized that today we become life partners. We’re creating something so few get to experience, and that this experience will bind everyone at Thinkful in job, career, friendship, mission, and for the rest of our lives no matter what. I think that’s also called family. To the Thinkful community of students, alumni, educators, team: Welcome to the Chegg family. Congratulations on getting us to today and congratulations on making tomorrow possible.</p><p>This morning we’re very, very proud, and we’ll enjoy it. This afternoon we’re back to work.</p><h3>Read more</h3><ul><li>Nathan Schultz, Chegg’s President of Learning Services, on <a href=\"https://medium.com/chegg/taking-students-from-learning-to-earning-students-need-skills-for-the-modern-workforce-at-71d19204b43f\">Taking Students from Learning to Earning: Students need skills for the modern workforce, at affordable prices, and on-demand!</a></li><li>Official Press Release: <a href=\"https://investor.chegg.com/Press-Releases/press-release-details/2019/Chegg-to-Acquire-Online-Skills-Based-Learning-Platform-Thinkful-to-Help-Students-Accelerate-their-Path-from-Learning-to-Earning/default.aspx\">Chegg to Acquire Online Skills-Based Learning Platform Thinkful to Help Students Accelerate their Path from Learning to Earning</a></li></ul><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a8d60aa3dbf1\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/1c27eca49f50","link":"https://paulo-pereira.medium.com/newsletters-to-follow-in-2021-1c27eca49f50?source=rss-17756313f41a------3","title":"Newsletters to follow in 2021","author":"Paulo Pereira","excerpt":"Newsletters can be a great source of information — if you follow the right ones. For the past few years, newsletters have been my go to for news and interesting information. I found out that newsletters will stay in your inbox, so even if you have a busy schedule one week, the next week you can go b","updated":"2021-01-24T22:18:34.773Z","published":"Sat, 23 Jan 2021 22:37:21 GMT","categories":["business","education","newsletter","technology","life"],"content_html":"<figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*YBtdEEZSgSPoPqxjM6RZ-w.jpeg\" /></figure><p>Newsletters can be a great source of information — if you follow the right ones.</p><p>For the past few years, newsletters have been my go to for news and interesting information. I found out that newsletters will stay in your inbox, so even if you have a busy schedule one week, the next week you can go back and see the major events that happened and you won’t miss out on the news cycle.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*yW0L2ViksCn_EO9gBo1iIg.jpeg\" /></figure><h3>#1 - The Hustle</h3><p>The Hustle is a fairly young daily newsletter that touches major news and smaller quirky stories. Their language is mostly directed at millennials and there’s an irreverence to it that makes it quite appealing.</p><p>Check them out: <a href=\"https://thehustle.co/home/\">https://thehustle.co/home/</a></p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*yW0L2ViksCn_EO9gBo1iIg.jpeg\" /></figure><h3>#2 - Term Sheet</h3><p>Mostly financial information digested by Lucinda Shen, Term Sheet has a grown up vibe to it. Very informative and in the right amount, this newsletter is more East coast focused and allows you to get a glimpse into the world of high finance.</p><p>Check it out: <a href=\"https://mynewsletters.fortune.com/subscribe\">https://mynewsletters.fortune.com/subscribe</a> (look for Term Sheet)</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*yW0L2ViksCn_EO9gBo1iIg.jpeg\" /></figure><h3>#3 - Ideas at TED</h3><p>From the creators of TED talk, Ideas at TED newsletter is a round up of articles written on the Ideas.TED platform. Interesting and informative, the articles cover a broad range of topics from work to race.</p><p>Check it out: <a href=\"https://ideas.ted.com/\">https://ideas.ted.com/</a></p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*yW0L2ViksCn_EO9gBo1iIg.jpeg\" /></figure><h3>#4 - Winning the Internet</h3><p>Data driven newsletter that compiles the most shared articles in newsletters during the past week. Really interesting retro visuals!</p><p>Check it out: <a href=\"https://pudding.cool/projects/newsletter/\">https://pudding.cool/projects/newsletter/</a></p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*yW0L2ViksCn_EO9gBo1iIg.jpeg\" /></figure><h3>#5 - Quincy Larson</h3><p>Quincy Larson is the creator of FreeCodeCamp and he puts a (mostly) weekly newsletter to share articles that he finds interesting. The focus is on coding, learning and overall tech and the curation is excellent.</p><p>Check it out:</p><p><a href=\"https://www.freecodecamp.org/signin\">https://www.freecodecamp.org/signin</a></p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*yW0L2ViksCn_EO9gBo1iIg.jpeg\" /></figure><p>Check out any of these newsletters and you’ll get information of great quality!</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1c27eca49f50\" width=\"1\" height=\"1\" alt=\"\">"},{"guid":"https://medium.com/p/5f5c1b577ef5","link":"https://codeburst.io/introducing-customertestsexcel-5f5c1b577ef5?source=rss-17756313f41a------3","title":"Introducing CustomerTestsExcel","author":"cedd burge","excerpt":"A BDD framework to round trip Excel Customer Tests to C# NUnit Tests specialized to numerical/analytical contexts. The problem BDD involves focusing collaborative work around concrete, real-world examples that illustrate how a system should behave. The examples are executable as tests and can be und","updated":"2022-03-14T15:56:09.760Z","published":"Mon, 09 Nov 2020 22:07:04 GMT","categories":["excel","csharp","bdd","living-documentation","specification-by-example"],"content_html":"<p>A <a href=\"https://github.com/resgroup/customer-tests-excel\">BDD framework to round trip Excel Customer Tests to C# NUnit Tests</a> specialized to numerical/analytical contexts.</p><h3>The problem</h3><p><a href=\"https://cucumber.io/docs/bdd/\">BDD</a> involves focusing collaborative work around concrete, real-world examples that illustrate how a system should behave. The examples are executable as tests and can be understood by the whole team (programmers, testers, business analysts, subject matter experts, and so on). This means that the examples are always up to date; serving as the Single Source of Truth and as <a href=\"https://www.goodreads.com/en/book/show/34927405-living-documentation\">living documentation</a>. <a href=\"https://cucumber.io/\">Cucumber</a> is the most widely used tool for creating these examples/tests, using <a href=\"https://cucumber.io/docs/gherkin/reference/\">Gherkin syntax</a>.</p><p>This all works extremely well for specifying business-centric code, such as Given I am logged in as Greg, When I try to post to &#39;Expensive Therapy&#39;, Then I should see &#39;Your article was published&#39;. However, the code I work with is mostly calculation centric, and all the existing tools are much less useful when trying to specify complex physics, statistics, or maths. In these cases, there can be a lot of input and output data for a calculation, and it is also important to see the intermediate steps. For example, Given I use the sample ERA5 data, When I run the roughness calculation, Then the result should be 5 doesn’t communicate very much.</p><p>So for calculation heavy code, we need a different way to communicate. This communication is necessarily calculation-centric, so the whole team needs to have some mathematical skills, but in the context of technical software, this is very likely to be the case.</p><h3>The framework</h3><p>The <a href=\"https://github.com/resgroup/customer-tests-excel\">CustomerTestsExcel framework</a> aims to solve this problem by using Microsoft Excel to define the examples/calculations, which has a number of advantages:</p><ul><li>Excel is ubiquitous and familiar to most technical people</li><li>Large calculation inputs and outputs can be specified easily</li><li>Excel supports any complexity of calculation</li><li>All the intermediate steps of a calculation are visible</li><li>Named ranges can be used to make calculations more expressive, for example:<br> SQRT(POWER(ShearTurbulenceWakeErosionRate,2)+POWER(AmbientTurbulenceWakeErosionRate,2)+POWER(MechanicalWakeErosionRate,2))</li><li>Explanatory comments can be added to Excel</li></ul><p>The framework then generates C# Unit tests from Excel, which can be run just like normal and integrate well with build servers and code coverage and similar. The framework has two other features that serve to strengthen the link between Excel and C#.</p><p><strong>Round tripping</strong>: The C# tests can optionally round trip back to Excel, recreating the Excel files as they are run. This allows you to use automated refactoring tools in C# to say rename a function, and then to recreate the Excel files with the new name.</p><p><strong>Auto Generating Setup Files</strong>: The framework can scan assemblies / DLLs, and match interfaces with entities found in Excel.</p><p>These features mean that only limited mapping code is required and that there is a strong incentive to keep the names consistent, which aids communication between programmers and non-programmers.</p><p>This brings the well-known benefits of BDD to the previously out of reach world of calculation centric code.</p><ul><li>The examples are executable as tests and can be understood by the whole team (programmers, testers, business analysts, subject matter experts, and so on)</li><li>The examples are always up to date, serve as the single source of truth. and as living documentation</li></ul><h3>Getting Started</h3><p>The easiest way to get started is to clone/download the <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding\">scaffolding project</a>. It has numerous examples, and everything supported by the framework is represented at least once (the examples also function as an end to end tests of the framework). The projects in it can be used independently or can be added to your own solutions, as a starting point, and as a reference.</p><h3>Writing Excel test cases</h3><p>Each sheet that has <em>“Specification”</em> in A1 becomes a test. Other sheets can be used for supporting data/calculations. Primitives, objects, and lists/tables are supported to any level of nesting (except that tables can’t be nested within tables, and objects within tables can’t round trip).</p><p>The example Vermuelen wake length spreadsheet is a good example of a physics calculation based on tabular data, with tabular expected values. The excerpt below shows a table of data being set up, where one of the columns (TurbineGeometry) is an object (as opposed to a primitive).</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/870/1*cS6Oy6CdjlSKy9TsY_0cUQ.png\" /></figure><p>The example Anova spreadsheet is a good example of a statistics calculation, using the power of Excel to show all the intermediate steps. The excerpt below shows supporting calculations alongside each row of a table. The framework requires a blank cell after the values you are setting up, but after that you can use the power of Excel as you wish.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/930/1*FhcUxS1iqDjfDy6jvMsQXw.png\" /></figure><h3>Generating the C# NUnit tests</h3><p>The tests themselves depend on the <a href=\"https://www.nuget.org/packages/CustomerTestsExcel/\">CustomerTestsExcel</a> NuGet package, which also contains GenerateCodeFromExcelTest.exe to create the C# from Excel. This is installed to <em>“&lt;nuget folder&gt;\\customertestsexcel\\2.0.4\\tools\\GenerateCodeFromExcelTest”</em>, and takes the following parameters:</p><ul><li>/folder The folder where the tests should be generated. For example, <em>“…\\SampleTests”</em></li><li>/namespace The namespace that you would like the tests to use. For example, <em>“SampleTests”</em></li><li>/usings Any using statements that the test will require to access the code under test. For example, <em>“SampleSystemUnderTest SampleSystemUnderTest.AnovaCalculator …”</em></li><li>/assertionClassPrefix An optional prefix added to Excel assertion names. When used, it is usually <em>“I”</em>, with the expectation that the names map to interfaces</li><li>/assembliesUnderTest Any assemblies to scan to find interfaces that match entities found in Excel. Sadly these must be entered as absolute paths, due to a limitation in C# Reflection. For example, <em>“C:\\code\\SampleSystemUnderTest\\obj\\Debug\\SampleSystemUnderTest.dll”</em></li></ul><p>You can see an <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding/blob/master/SampleTests/GenerateTests.bat\">example of this in the scaffolding repo</a>.</p><p>When run, the tool will generate the following:</p><ul><li>A <strong>Test Class</strong> for each sheet/tab in any of the Excel spreadsheets. For example <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding/blob/master/SampleTests/Vermeulen%20Near%20Wake%20Length/VermeulenNearWakeLength.cs\">VermeulenNearWakeLength.cs</a>.</li><li>A <strong>Setup Class</strong> file for each interface that it finds in /assembliesUnderTest that matches an Entity in Excel. For example, <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding/blob/master/SampleTests/Setup/Group.cs\">Group.cs</a> in the Anova calculation</li><li>An <strong>Example Setup Class</strong> file for each Entity in Excel where no matching interface is found in /assembliesUnderTest. For example <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding/blob/master/SampleTests/Setup/A_Table.cs\">Group.cs</a></li><li>A <strong>Root Setup Class</strong> for each root class that it finds in Excel (The first entity in an Excel sheet, to the right of <em>“Given A”</em>). For example, <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding/blob/master/SampleTests/Setup/VermeulenNearWakeLengthCalculator.cs\">VermeulenNearWakeLengthCalculator.cs</a>.</li></ul><h3>Adding Custom C# Code</h3><p><strong>Test Classes</strong> do not require any custom modifications.</p><p><strong>Setup Classes</strong> may not have matched everything in Excel, in which case you can add a partial class to fill in the gaps. The convention is to name the file <em>“&lt;Filename&gt;Partial.cs”</em>, like <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding/blob/master/SampleTests/Setup/ClassWithCustomPropertyPartial.cs\">ClassWithCustomPropertyPartial.cs</a> in the scaffolding repo. This allows you to perform complex custom setup in code without exposing the complexity in Excel. This is obviously a double-edged sword so use with due awareness.</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/e8dae4c63958f8de2c53f525598ddce3/href\">https://medium.com/media/e8dae4c63958f8de2c53f525598ddce3/href</a></iframe><p><strong>Example Setup Classes</strong> don’t set anything up in your system under test, but they do record all the things that have been set up on them (which might be all you need in some contexts).</p><p><strong>Root Setup Classes</strong> always require custom code in order to exercise the system under test, and to return the results (The <em>“When”</em> and <em>“Then”</em> sections of BDD tests). <a href=\"https://github.com/ceddlyburge/customer-tests-excel-scaffolding/blob/master/SampleTests/Setup/VermeulenNearWakeLengthCalculatorPartial.cs\">VermeulenNearWakeLengthCalculatorPartial.cs</a> in the example repo is a good example of this.</p><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/fd3e27c6eefa82d39cabcc0f3f5160d4/href\">https://medium.com/media/fd3e27c6eefa82d39cabcc0f3f5160d4/href</a></iframe><p>You can override any of the generated code by creating an <em>“Override&lt;Filename&gt;.cs”</em> file in the <em>“setup”</em> folder, like <a href=\"https://github.com/ceddlyburge/customer-tests-excel-scaffolding/blob/master/SampleTests/Setup/OverrideAnything.cs\">OverrideAnything.cs</a> in the scaffolding repo. This allows you to completely replace the generated code (instead of just adding to it). In this case, the framework generates an <a href=\"https://github.com/ceddlyburge/customer-tests-excel-scaffolding/blob/master/SampleTests/Setup/Anything.cs.txt\">OverrideAnything.cs.txt</a> so that you can see what it would have generated, and potentially copy and paste things from it.</p><p>The framework does not generate the .csproj file so that you can easily add your own dependencies and suchlike. You should create your own one, or use the <a href=\"https://github.com/resgroup/customer-tests-excel-scaffolding/blob/master/SampleTests/SampleTests.csproj\">scaffolding project example</a>. Dotnet Core is recommended.</p><h3>Round tripping Excel spreadsheets from the C# tests</h3><p>Round tripping isn’t required to use the framework but is worth doing if you want to use C# refactoring tools. It is especially helpful when renaming functions in Visual Studio and pushing the changes back to Excel.</p><p>To do this, set a couple of environment variables. <em>“CUSTOMER_TESTS_EXCEL_WRITE_TO_EXCEL”</em> should be set to <em>“true”</em>. <em>“CUSTOMER_TESTS_RELATIVE_PATH_TO_EXCELTESTS”</em> should be set to the relative location of the Excel files from the location where the tests run (<em>“…\\SampleTests\\ExcelTests”</em> for a standard setup). Then whenever you run the tests (either from the command line or through the visual studio), they will recreate their associated Excel file/sheet.</p><h3>Running on a build server</h3><p>It’s probably a good idea to regenerate the C# from the Excel on the build server. That way you can be completely sure that they are synchronized with each other. The framework auto generates setup files by scanning the assemblies you specify. This means that the assembly has to exist and, due to a restriction in C# Reflection, you need to provide an absolute path to it.</p><p>The upshot of this is that you need to build your code (to create the assemblies to be scanned), then generate the C#, and then build/test again, in case the C# has changed. You also need to pass in the absolute path of these assemblies.</p><p>You can see this in action in the <a href=\"https://github.com/resgroup/customer-tests-excel/blob/master/appveyor.yml\">appveyor.yml</a> of the scaffolding repo, and below.</p><pre>- dotnet build CustomerTestsExcelScaffolding.sln   <br> - AppveyorGenerateTests.bat<br> - dotnet test</pre><h3>Conclusion</h3><p>Existing BDD frameworks are not well suited to calculation centric code. The <a href=\"https://github.com/resgroup/customer-tests-excel\">CustomerTestsExcel framework</a> fills this niche in C# by using Excel as a ubiquitous technology, which is well known to most technical people. By round tripping and auto generating setup files, it strongly incentivizes the same names to be used in code and Excel; strengthening the ubiquitous language of the calculations.</p><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5f5c1b577ef5\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://codeburst.io/introducing-customertestsexcel-5f5c1b577ef5\">Introducing CustomerTestsExcel</a> was originally published in <a href=\"https://codeburst.io\">codeburst</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"}]},"meta":{"timestamp":"2026-06-13T14:37:07.428Z","request_id":"cb764226-5a96-4ef4-9416-899b0f245b1d"},"status":"ok","message":"Stories a user applauded","success":true}}}},"401":{"description":"Missing or invalid x-oanor-key header"},"402":{"description":"Active subscription required"},"429":{"description":"Rate-limit or monthly quota reached"},"502":{"description":"Upstream did not respond"}}}}},"x-oanor-pricing":[{"slug":"free","name":"Free","price_cents_month":0,"monthly_call_quota":3500,"rps_limit":2,"hard_limit":true},{"slug":"starter","name":"Starter","price_cents_month":680,"monthly_call_quota":67000,"rps_limit":8,"hard_limit":true},{"slug":"pro","name":"Pro","price_cents_month":1950,"monthly_call_quota":335000,"rps_limit":20,"hard_limit":true},{"slug":"mega","name":"Mega","price_cents_month":4650,"monthly_call_quota":1640000,"rps_limit":50,"hard_limit":true}],"x-oanor-marketplace-url":"https://www.oanor.com/api/medium-api"}