<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ong Chin Hwee</title>
    <description>The latest articles on DEV Community by Ong Chin Hwee (@hweecat).</description>
    <link>https://dev.to/hweecat</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F252437%2F3fe96488-c882-4be4-94ed-a1f7730535d2.jpg</url>
      <title>DEV Community: Ong Chin Hwee</title>
      <link>https://dev.to/hweecat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hweecat"/>
    <language>en</language>
    <item>
      <title>Functional "Control Flow" - Writing Programs without Loops</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Sun, 04 Jul 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/hweecat/functional-control-flow-writing-programs-without-loops-3cod</link>
      <guid>https://dev.to/hweecat/functional-control-flow-writing-programs-without-loops-3cod</guid>
      <description>

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In my previous post on &lt;a href="https://hweecat.github.io/learning-scala-functional-programming-principles"&gt;key principles of functional programming&lt;/a&gt;, I explained how the functional programming paradigm differs from imperative programming, and discussed how the the concepts of idempotency and avoidance of side effects are linked to the property of referential transparency that enables equational reasoning in functional programming.&lt;/p&gt;

&lt;p&gt;Before we dive into some of the features of functional programming, let’s start with a personal anecdote during my first 3 months of writing Scala code.&lt;/p&gt;

&lt;h2&gt;
  
  
  There is no “If-Else” in Functional Code
&lt;/h2&gt;

&lt;p&gt;I was writing a pure Scala function for a custom Spark UDF which computes revenue adjustments based on a custom tiered adjustment expressed in JSON string. While attempting to express the business logic in pure functional code (since that is the team’s coding style), I got pretty frustrated with my perceived drop in productivity to the point whereby I introduced “if-else” logic into my code in a bid to “get the job done”.&lt;/p&gt;

&lt;p&gt;Let’s just say that I learnt a pretty tough lesson during code review for that particular merge request.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“No if-else in functional code, this is not imperative programming… &lt;strong&gt;No ifs, no elses.&lt;/strong&gt; ”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Without “if-else”, how do we write “control flow” in functional programming?&lt;/p&gt;

&lt;p&gt;The short answer: &lt;strong&gt;function composition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The long answer: A combination of function composition and functional data structures.&lt;/p&gt;

&lt;p&gt;As a deep-dive on each functional design pattern can be pretty lengthy, the focus of this post is to provide an overview of function composition and how it enables a more intuitive approach to designing data pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Brief Intro to Function Composition
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---HLjfldj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hweecat.github.io/images/function_composition.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---HLjfldj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hweecat.github.io/images/function_composition.jpg" alt="illustration of function composition" title="illustration of function composition"&gt;&lt;/a&gt;Function composition&lt;/p&gt;

&lt;p&gt;In mathematics, &lt;strong&gt;function composition&lt;/strong&gt; is an operation that takes two functions &lt;em&gt;f&lt;/em&gt; and &lt;em&gt;g&lt;/em&gt; in sequence and forms a composite function &lt;em&gt;h&lt;/em&gt; such that &lt;em&gt;h(x) = g(f(x))&lt;/em&gt; - function &lt;em&gt;g&lt;/em&gt; is applied to the result of applying the function &lt;em&gt;f&lt;/em&gt; to a generic input &lt;em&gt;x&lt;/em&gt;. Mathematically, this operation can be expressed as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DOCJCGiS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Ff:X%255Crightarrow%26space%3BY%2C%26space%3Bg:Y%255Crightarrow%26space%3BZ%255CRightarrow%26space%3Bg%26space%3B%255Ccirc%26space%3Bf:X%255Crightarrow%26space%3BZ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DOCJCGiS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Ff:X%255Crightarrow%26space%3BY%2C%26space%3Bg:Y%255Crightarrow%26space%3BZ%255CRightarrow%26space%3Bg%26space%3B%255Ccirc%26space%3Bf:X%255Crightarrow%26space%3BZ" alt="[f:X\rightarrow Y, g:Y\rightarrow Z\Rightarrow g \circ f:X\rightarrow Z]"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;where &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kqj7KNBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fg%26space%3B%255Ccirc%26space%3Bf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kqj7KNBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fg%26space%3B%255Ccirc%26space%3Bf" alt="g \circ f"&gt;&lt;/a&gt; is a composite function.&lt;/p&gt;

&lt;p&gt;Intuitively, the composite function maps &lt;em&gt;x&lt;/em&gt; in &lt;em&gt;X&lt;/em&gt; to &lt;em&gt;g(f(x))&lt;/em&gt; in domain &lt;em&gt;Z&lt;/em&gt; for all values in domain &lt;em&gt;X&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A useful analogy to illustrate the concept of function composition is making butter toast in an oven with a slice of bread and cold butter. There are two possible operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Toasting in the oven (operation f)&lt;/li&gt;
&lt;li&gt;Spreading butter over the widest surface (operation g)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we toast the bread in the oven first and spread cold butter over the widest surface of what comes out of the oven, we get a slice of toasted bread with &lt;em&gt;cold butter spread&lt;/em&gt;. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kqj7KNBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fg%26space%3B%255Ccirc%26space%3Bf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kqj7KNBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fg%26space%3B%255Ccirc%26space%3Bf" alt="g \circ f"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we spread cold butter over the widest surface of the bread first and toast the bread with cold butter spread in the oven, we get a slice of toasted bread with &lt;em&gt;warm butter spread&lt;/em&gt;. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ce9bkcpR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Ff%26space%3B%255Ccirc%26space%3Bg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ce9bkcpR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Ff%26space%3B%255Ccirc%26space%3Bg" alt="f \circ g"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we know that &lt;em&gt;“cold butter spread” != “warm butter spread”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From these examples, we can intuitively infer that the order of function application matters in function composition. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HyEkpVh8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fg%26space%3B%255Ccirc%26space%3Bf%26space%3B%255Cneq%26space%3Bf%26space%3B%255Ccirc%26space%3Bg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HyEkpVh8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fg%26space%3B%255Ccirc%26space%3Bf%26space%3B%255Cneq%26space%3Bf%26space%3B%255Ccirc%26space%3Bg" alt="[g \circ f \neq f \circ g]"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly in designing data pipelines, we often write data transformations by applying functions to results of other functions. The ability to compose functions encourages &lt;strong&gt;refactoring&lt;/strong&gt; of repeated code segments into functions for &lt;strong&gt;maintainability&lt;/strong&gt; and &lt;strong&gt;reusability&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functions as First-Class Objects
&lt;/h2&gt;

&lt;p&gt;The core idea in functional programming is: &lt;strong&gt;functions are values&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This feature implies that a function can be [2,3]:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;assigned to a variable&lt;/li&gt;
&lt;li&gt;passed as a parameter to other functions&lt;/li&gt;
&lt;li&gt;returned as a value from other functions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this to work, functions must be first-class objects (and stored in data structures) in the runtime environment - just like numbers, strings and arrays. First-class functions are supported in all functional languages including Scala, as well as some interpreted languages such as Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Higher-Order Functions
&lt;/h2&gt;

&lt;p&gt;A key implication resulting from the concept of functions as first-class objects is that function composition can be naturally expressed as a &lt;strong&gt;higher-order function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A higher-order function has at least one of the following properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accepts functions as parameters&lt;/li&gt;
&lt;li&gt;Returns a function as a value&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An example of a higher-order function is &lt;code&gt;map&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When we look at the documentation for the Python built-in function &lt;code&gt;map&lt;/code&gt;, it is stated that the &lt;code&gt;map&lt;/code&gt; function takes in another function and an iterable as input parameters and returns an iterator that yields the results [4].&lt;/p&gt;

&lt;p&gt;In Scala, each of the collection classes in package &lt;code&gt;scala.collections&lt;/code&gt; and its subsets contain the &lt;code&gt;map&lt;/code&gt; method that is defined by the following function signatures on ScalaDoc [5]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def map[B](f: (A) =&amp;gt; B): Iterable[B] // for collection classes
def map[B](f: (A) =&amp;gt; B): Iterator[B] // for iterators that access elements of a collection

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

&lt;/div&gt;



&lt;p&gt;What the function signatures mean is that &lt;code&gt;map&lt;/code&gt; takes a function input parameter &lt;code&gt;f&lt;/code&gt;, and &lt;code&gt;f&lt;/code&gt; transforms a generic input of type &lt;code&gt;A&lt;/code&gt; to a resulting value of type &lt;code&gt;B&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To square each value in a collection of integers, the &lt;strong&gt;iterative approach&lt;/strong&gt; is to traverse each element in the collection, square the element and append the result to a collection of results that expands in length with each iteration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; In Python:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  def square(x):
      return x * x

  def main(args):

      collection = [1,2,3,4,5]
      # initialize list to hold results
      squared_collection = []
      # loop till the end of the collection
      for num in collection:
          # square the current number 
          squared = square(num)
          # add the result to list
          squared_collection.append(squared) 

      print(squared_collection)   

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

&lt;/div&gt;



&lt;p&gt;In the iterative approach, two state changes occur at each iteration within the loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;squared&lt;/code&gt; variable holding the result returned from the &lt;code&gt;square&lt;/code&gt; function; and&lt;/li&gt;
&lt;li&gt;The collection holding the results of the square function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To perform the same operation using a &lt;strong&gt;functional approach&lt;/strong&gt; (i.e. without using mutable variables), the &lt;code&gt;map&lt;/code&gt; function can be used to “map” each element in the collection to a new collection with the same number of elements as the input collection - by applying the square operation to each element and collecting the results into the new collection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Python:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  def square(x):
      return x * x

  def main(args):

      collection = [1,2,3,4,5]
      squared = list(map(square, collection))
      print(squared)   

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In Scala:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  object MapSquare {

      def square(x: Int): Int = {
          x * x
      }

      def main(args: Array[String]) {

          val collection = List[1,2,3,4,5]
          val squared = collection.map(square)
          println(squared)
      }
  }    

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

&lt;/div&gt;



&lt;p&gt;In both implementations, the &lt;code&gt;map&lt;/code&gt; function accepts an input function that is applied to each element in a collection of values and returns a new collection containing the results. As &lt;code&gt;map&lt;/code&gt; has the property of accepting another function as a parameter, it is a higher-order function.&lt;/p&gt;

&lt;p&gt;A few quick side-notes on differences between Python and Scala implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python &lt;code&gt;map&lt;/code&gt; vs Scala &lt;code&gt;map&lt;/code&gt;: An iterable function such as &lt;code&gt;list&lt;/code&gt; is needed to convert the iterator returned from the Python &lt;code&gt;map&lt;/code&gt; function into an iterable. In Scala, there is no need for explicit conversion of the result from the &lt;code&gt;map&lt;/code&gt; function to an iterable, as all methods in the &lt;code&gt;Iterable&lt;/code&gt; trait are defined in terms of an abstract method, &lt;code&gt;iterator&lt;/code&gt;, which returns an instance of the &lt;code&gt;Iterator&lt;/code&gt; trait that yields the collection’s elements one by one [6].&lt;/li&gt;
&lt;li&gt;How values are returned from a function: While the &lt;code&gt;return&lt;/code&gt; keyword is used in Python to return a function result, the &lt;code&gt;return&lt;/code&gt; keyword is rarely used in Scala. Instead, the last line within a function declaration is evaluated and the resultant value is returned when defining a function in Scala. In fact, using the &lt;code&gt;return&lt;/code&gt; keyword in Scala is not good practice for functional programming as it abandons the current computation and is not referentially transparent [7-8].&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;When using higher-order functions, it is often convenient to be able to call input function parameters with function literals or &lt;strong&gt;anonymous functions&lt;/strong&gt; without having to define them as named function objects before they can be used within the higher-order function.&lt;/p&gt;

&lt;p&gt;In Python, anonymous functions are also known as &lt;strong&gt;lambda expressions&lt;/strong&gt; due to their roots in lambda calculus. An anonymous function is created with the &lt;code&gt;lambda&lt;/code&gt; keyword and wraps a single expression without using &lt;code&gt;def&lt;/code&gt; or &lt;code&gt;return&lt;/code&gt; keywords. For example, the &lt;code&gt;square&lt;/code&gt; function in the previous example in Python can be expressed as an anonymous function in the &lt;code&gt;map&lt;/code&gt; function, where the lambda expression &lt;code&gt;lambda x: x * x&lt;/code&gt; is used as a function input parameter to &lt;code&gt;map&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def main(args):

    collection = [1,2,3,4,5]
    squared = map(lambda x: x * x, collection)
    print(squared)   

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

&lt;/div&gt;



&lt;p&gt;In Scala, an anonymous function is defined in-line with the &lt;code&gt;=&amp;gt;&lt;/code&gt; notation - where the function arguments are defined to the left of the &lt;code&gt;=&amp;gt;&lt;/code&gt; arrow and the function expression is defined to the right of the &lt;code&gt;=&amp;gt;&lt;/code&gt; arrow. For example, the &lt;code&gt;square&lt;/code&gt; function in the previous example in Scala can be expressed as an anonymous function with the &lt;code&gt;(x: Int) =&amp;gt; x * x&lt;/code&gt; syntax and used as a function input parameter to &lt;code&gt;map&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;object MapSquareAnonymous {

    def main(args: Array[String]) {
        val collection = List[1,2,3,4,5]
        val squared = collection.map((x: Int) =&amp;gt; x * x)
        println(squared) 
    }
}    

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

&lt;/div&gt;



&lt;p&gt;A key benefit of using anonymous functions in higher-order functions is that single-use single-expression functions need not be wrapped explicitly within a named function definition, hence &lt;strong&gt;optimizing lines of code&lt;/strong&gt; and &lt;strong&gt;improving code maintainability&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursion as a form of “functional iteration”
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recursion&lt;/strong&gt; is a form of self-referential &lt;strong&gt;function composition&lt;/strong&gt; - a recursive function takes the results of (smaller instances of) itself and uses them as inputs to another instance of itself. To prevent an infinite loop of recursive calls, a &lt;em&gt;base case&lt;/em&gt; is required as a terminating condition to return a result without using recursion.&lt;/p&gt;

&lt;p&gt;A classic example of recursion is the factorial function, which is defined as the product of all positive integers less than or equal to an integer &lt;em&gt;n&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9m_h0NFC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fn%21%3Dn%255Ccdot%26space%3B%28n-1%29%255Ccdot%26space%3B%28n-2%29%255Ccdots%26space%3B3%255Ccdot%26space%3B2%255Ccdot%26space%3B1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9m_h0NFC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3Fn%21%3Dn%255Ccdot%26space%3B%28n-1%29%255Ccdot%26space%3B%28n-2%29%255Ccdots%26space%3B3%255Ccdot%26space%3B2%255Ccdot%26space%3B1" alt="[n!=n\cdot (n-1)\cdot (n-2)\cdots 3\cdot 2\cdot 1]"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two possible iterative approaches to implementing a factorial function: using &lt;code&gt;for&lt;/code&gt; loop, and using &lt;code&gt;while&lt;/code&gt; loop.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Python:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  def factorial_for(n):
      # initialize variable to hold factorial
      fact = 1
      # loop from n to 1 in decrements of 1
      for num in range(n, 1, -1):
          # multiply current number with the current product
          fact = fact * num
      return fact

  def factorial_while(n):
      # initialize variable to hold factorial
      fact = 1
      # loop till n reaches 1
      while n &amp;gt;= 1:
          # multiply current number with the current product
          fact = fact * n
          # subtract the number by 1
          n = n - 1
      return fact

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

&lt;/div&gt;



&lt;p&gt;In both iterative implementations of the factorial function, two state changes occur at each iteration within the loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The factorial variable storing the current product; and&lt;/li&gt;
&lt;li&gt;The number being multiplied.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To implement the factorial function using a &lt;strong&gt;functional approach&lt;/strong&gt; , recursion is useful in dividing the problem into subproblems of the same type - in this case, the product of &lt;em&gt;n&lt;/em&gt; and &lt;em&gt;(n-1)&lt;/em&gt;!).&lt;/p&gt;

&lt;p&gt;The basic recursive approach for the factorial function looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Python:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  def factorial(n):
      # base case to return value
      if n &amp;lt;= 0: return 1
      # recursive function call with another set of inputs
      return n * factorial(n-1)

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In Scala:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  def factorial(n: Int): Long = {
      if (n &amp;lt;= 0) 1 else n * factorial(n-1)
  }

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

&lt;/div&gt;



&lt;p&gt;For the basic recursive approach, the factorial of 5 is evaluated in the following manner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;factorial(5)
if (5 &amp;lt;= 0) 1 else 5 * factorial(5 - 1)
5 * factorial(4) // factorial(5) is added to call stack
5 * (4 * factorial(3)) // factorial(4) is added to call stack
5 * (4 * (3 * factorial(2))) // factorial(3) is added to call stack
5 * (4 * (3 * (2 * factorial(1)))) // factorial(2) is added to call stack
5 * (4 * (3 * (2 * (1 * factorial(0))))) // factorial(1) is added to call stack
5 * (4 * (3 * (2 * (1 * 1)))) // factorial(0) returns 1 to factorial(1)
5 * (4 * (3 * (2 * 1))) // factorial(1) return 1 * factorial(0) = 1 to factorial(2)
5 * (4 * (3 * 2)) // factorial(2) return 2 * factorial(1) = 2 to factorial(3)
5 * (4 * 6) // factorial(3) return 3 * factorial(2) = 6 to factorial(4)
5 * 24 // factorial(4) returns 4 * factorial(3) = 24 to factorial(5)
120 // factorial(5) returns 5 * factorial(4) = 120 to global execution context

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

&lt;/div&gt;



&lt;p&gt;For &lt;em&gt;n = 5&lt;/em&gt;, the evaluation of the factorial function involves 6 recursive calls to the factorial function including the base case.&lt;/p&gt;

&lt;p&gt;While the basic recursive approach expresses the factorial function more closely with its definition (and more naturally) compared with the iterative approach, it also uses more memory as each function call is pushed to the call stack as a stack frame and popped off the call stack when the function call returns a value.&lt;/p&gt;

&lt;p&gt;For larger values of &lt;em&gt;n&lt;/em&gt;, the recursion gets deeper with more function calls to itself and more space has to be allocated to the call stack. When the space needed to store the function calls exceeds the capacity for the call stack, a &lt;strong&gt;stack overflow&lt;/strong&gt; occurs!&lt;/p&gt;

&lt;h3&gt;
  
  
  Tail Recursion and Tail-call Optimization
&lt;/h3&gt;

&lt;p&gt;To prevent infinite recursion from causing stack overflow and crashing the program, some optimizations have to be made to the recursive function in order to reduce consumption of stack frames in the call stack. A possible approach in optimizing the recursive function is by rewriting it as a &lt;strong&gt;tail recursive&lt;/strong&gt; function.&lt;/p&gt;

&lt;p&gt;A tail recursive function calls itself recursively and does not perform any computation after the recursive call returns. A function call is a &lt;strong&gt;tail call&lt;/strong&gt; when it does nothing other than returning the value of the function call.&lt;/p&gt;

&lt;p&gt;In functional programming languages such as Scala, &lt;strong&gt;tail-call optimization&lt;/strong&gt; is typically included in the compiler to identify tail calls and compile the recursion to iterative loops that do not consume stack frames for each iteration. In fact, the stack frame can be reused for both the recursion function and the function being called within the recursion function [1].&lt;/p&gt;

&lt;p&gt;With this optimization, the space performance for the recursion function can be reduced from &lt;em&gt;O(N)&lt;/em&gt; to &lt;em&gt;O(1)&lt;/em&gt; - from one stack frame per call to one stack frame for all calls [8]. In a way, a tail recursive function is a form of “functional iteration” with comparative performance to a loop.&lt;/p&gt;

&lt;p&gt;For example, the factorial function can be expressed in the form of a tail recursion in Scala:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def factorialTailRec(n: Int): Long = {
    def fact(n: Int, product: Long): Long = {
        if (n &amp;lt;= 0) product
        else fact(n-1, n * product)
    }

    fact(n, 1)
}

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

&lt;/div&gt;



&lt;p&gt;While tail-call optimization is automatically performed during compilation in Scala, it is not the case for Python. Moreover, there is a recursion limit in Python (the default value is 1000) as a prevention measure against an overflow of the C call stack for the CPython implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next: Higher-Order Functions
&lt;/h2&gt;

&lt;p&gt;In this post, we learn about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Function composition&lt;/li&gt;
&lt;li&gt;Higher-Order Functions as a key implication of functional programming&lt;/li&gt;
&lt;li&gt;Recursion as a form of “functional iteration”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Have we found a replacement for “if-else” yet? Not entirely, but we now know how to write “loops” in functional programming using Higher-Order Functions and tail recursion.&lt;/p&gt;

&lt;p&gt;In the next post, I will explore more on Higher-Order Functions and how they can be used in designing functional data pipelines.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want more behind-the-scenes articles on my learning journey as a data professional? Check out my website at &lt;a href="https://ongchinhwee.me"&gt;https://ongchinhwee.me&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Functional-Programming-Scala-Paul-Chiusano/dp/1617290653"&gt;Functional Programming in Scala by Paul Chiusano and Rúnar Bjarnason&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fpsimplified.com/"&gt;Functional Programming Simplified by Alvin Alexander&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Functional-Python-Programming-programming-built-dp-1788627067/dp/1788627067/ref=dp_ob_title_bk"&gt;Functional Python Programming by Steven F. Lott, 2nd Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/functions.html#map"&gt;Built-in Functions - Python 3.9.6 Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scala-lang.org/api/2.13.6/scala/collection/Iterable.html"&gt;Scala Standard Library 2.13.6 - scala.collections.Iterable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.scala-lang.org/overviews/collections-2.13/trait-iterable.html"&gt;Trait Iterable | Collections | Scala Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tpolecat.github.io/2014/05/09/return.html"&gt;tpolecat - The Point of No Return&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://users.scala-lang.org/t/dont-use-return-in-scala/3688/42"&gt;Don’t Use Return in Scala? - Question - Scala Users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cs.cornell.edu/courses/cs3110/2019sp/textbook/data/tail_recursion.html"&gt;Tail Recursion - Functional Programming in OCaml&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>python</category>
      <category>scala</category>
      <category>functional</category>
    </item>
    <item>
      <title>3 Key Principles of Functional Programming for Data Engineering</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Sun, 09 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/hweecat/3-key-principles-of-functional-programming-for-data-engineering-io4</link>
      <guid>https://dev.to/hweecat/3-key-principles-of-functional-programming-for-data-engineering-io4</guid>
      <description>

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In my previous post on &lt;a href="https://hweecat.github.io/learning-scala-motivations"&gt;my motivations for learning Scala&lt;/a&gt;, I stated that one of my key reasons for learning Scala for data engineering is due to the programming language being primarily designed for functional programming.&lt;/p&gt;

&lt;p&gt;Before we dive into the details of writing functional programs, it is important for us to understand the key principles of functional programming and how these programming principles are useful when designing reproducible data pipelines at scale.&lt;/p&gt;

&lt;p&gt;In this post, I introduce:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is Functional Programming&lt;/li&gt;
&lt;li&gt;Key principles of Functional Programming and their implications on data pipeline design&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is Functional Programming
&lt;/h2&gt;

&lt;p&gt;Functional programming is a &lt;strong&gt;declarative&lt;/strong&gt; style of programming that emphasizes writing software using &lt;strong&gt;only&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pure functions; and&lt;/li&gt;
&lt;li&gt;Immutable values.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To put it simply, functional programmers see their code as mathematical functions - and combinations of functions as equations with defined inputs and outputs.&lt;/p&gt;

&lt;p&gt;The concept of pure functions is the &lt;em&gt;core&lt;/em&gt; of Functional Programming, and has important implications on how functional design principles could be used in designing data applications at scale. For now, here’s a simplified definition of “pure function”:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The output of a pure function depends only on its &lt;strong&gt;input parameters&lt;/strong&gt; and its &lt;strong&gt;internal algorithm&lt;/strong&gt; (i.e. the “black box” where the input parameters are fed into).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A pure function has &lt;strong&gt;no side effects&lt;/strong&gt; ; it does not have any read/write interactions with the outside world.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As a consequence of the above two statements, if a pure function is called with an input parameter &lt;em&gt;x&lt;/em&gt; infinite number of times, &lt;strong&gt;it will always return the same result &lt;em&gt;y&lt;/em&gt;&lt;/strong&gt; - regardless of any state change of an internal or external process.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Declarative vs Imperative Programming
&lt;/h3&gt;

&lt;p&gt;In the &lt;strong&gt;imperative programming&lt;/strong&gt; paradigm, code is viewed as statements that changes a program’s state. An imperative program consists of sequences of statements written as explicit instructions to the computer on how the program operates to change its state.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Procedural and object-oriented programming&lt;/em&gt; paradigms are extensions of imperative programming to improve maintainability of imperative programs by separating programs into smaller components. Procedural programming focuses on breaking down a program into procedures (also known as subroutines or functions), while object-oriented programming focuses on breaking down a program into objects with state (data) and behavior (code).&lt;/p&gt;

&lt;p&gt;While procedural and object-oriented programming allow programs to be expressed in procedures that are easier for a programmer to understand without necessarily looking into the details, the complete program is still imperative since the order of execution for the statements (also known as &lt;strong&gt;control flow&lt;/strong&gt; ) affects how the program state is being changed.&lt;/p&gt;

&lt;p&gt;In contrast with imperative programming, the &lt;strong&gt;declarative programming&lt;/strong&gt; paradigm expresses the computation logic of a program without explicitly describing the steps to achieve them in sequence.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Functional programming&lt;/em&gt; is characterised by a declarative programming style, with computations performed through evaluation of expressions as function application and encapsulation of state mutation over control flow. This programming paradigm enables the programmer to write self-contained reusable and testable programs without additional mock objects and interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Principles of Functional Programming
&lt;/h2&gt;

&lt;p&gt;The key principles of functional programming are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pure functions and avoid side effects&lt;/li&gt;
&lt;li&gt;Immutability&lt;/li&gt;
&lt;li&gt;Referential transparency&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pure functions and avoid side effects
&lt;/h3&gt;

&lt;p&gt;When we look at a mathematical function &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P76HbUhK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3F%255Cinline%26space%3By%3Df%28x%29" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P76HbUhK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://latex.codecogs.com/svg.latex%3F%255Cinline%26space%3By%3Df%28x%29" alt="[y=f(x)]"&gt;&lt;/a&gt;, we expect the function &lt;em&gt;f&lt;/em&gt; to do nothing else other than computing the result &lt;em&gt;y&lt;/em&gt; given its input &lt;em&gt;x&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In other words, a &lt;strong&gt;pure&lt;/strong&gt; function has no observable effect on the program execution besides returning a result (which is its main effect).&lt;/p&gt;

&lt;p&gt;A function with &lt;strong&gt;side effects&lt;/strong&gt; changes state outside the local function scope. Examples of side effects include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modifying a variable or data structure in place&lt;/li&gt;
&lt;li&gt;modifying a global state&lt;/li&gt;
&lt;li&gt;performing any I/O operation (reading from or writing to a file/database, printing to console or reading user input etc.)&lt;/li&gt;
&lt;li&gt;throwing an exception with an error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To illustrate the concept of pure function and its key implications, let’s use an oven as an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jRRY4AQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hweecat.github.io/images/pizza_pure_function.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jRRY4AQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hweecat.github.io/images/pizza_pure_function.jpg" alt="illustration of pure function using oven and pizza" title="illustration of pure function using oven and pizza"&gt;&lt;/a&gt;Pure Function - illustrated using oven and pizza&lt;/p&gt;

&lt;p&gt;To bake a thin-crust Hawaiian pizza (sorry pizza purists), we need pizza crust and toppings, with the oven temperature set at 160 degree Celsius for 10 minutes. The inputs to the oven-baking function are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pizza crust type (thin-crust)&lt;/li&gt;
&lt;li&gt;list of toppings (cheese, tomato, ham, pineapple chutney)&lt;/li&gt;
&lt;li&gt;oven temperature (in degree Celsius)&lt;/li&gt;
&lt;li&gt;baking time (in minutes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we assume that the oven-baking operation is a pure function, we assume that the output of the operation &lt;em&gt;only&lt;/em&gt; depends on the inputs and the internal algoithm of the oven-baking operation. We do not expect any side effects, such as the oven-baking operation burning down the kitchen.&lt;/p&gt;

&lt;p&gt;Consequently, we expect the oven to return a perfectly-baked thin-crust Hawaiian pizza &lt;em&gt;every single time regardless of how many times we perform the operation&lt;/em&gt; given the inputs without changing the state outside the oven. We do not expect the oven to return a cream-based pizza or a burnt pizza given the function input.&lt;/p&gt;

&lt;p&gt;In more formal terminology, we expect a &lt;strong&gt;pure function&lt;/strong&gt; (the oven-baking operation) to be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;deterministic&lt;/em&gt; and &lt;em&gt;idempotent&lt;/em&gt; , and&lt;/li&gt;
&lt;li&gt;without &lt;em&gt;side effects&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In reality, we might sometimes open the oven door to check on the oven-baking operation. (I/O operation)&lt;/p&gt;

&lt;p&gt;We might decide to shorten the baking time by turning the timer on the oven, or add more cheese to the pizza toppings. (modifying a variable in place)&lt;/p&gt;

&lt;p&gt;The oven might heat up its surroundings, increasing the temperature of its external environment. (modifying a global state)&lt;/p&gt;

&lt;p&gt;The oven might either get too hot or suffer a short circuit, affecting the successful completion of the oven-baking operation. (throwing an exception with an error)&lt;/p&gt;

&lt;p&gt;These effects resulting from the oven-baking operation cause changes in state outside the oven besides the thin-crust Hawaiian pizza, hence making the oven-baking operation an &lt;strong&gt;impure&lt;/strong&gt; function with side effects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutability
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Immutability&lt;/strong&gt; means that once a value is assigned to a variable, the state of the variable &lt;em&gt;cannot be changed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The concept of immutability is important in Functional Programming, as it ensures that the function has a disciplined state and does not change other variables outside the function scope. Instead of modifying the value of a variable in place, state changes are managed by creating another instance without affecting the state of the original variable.&lt;/p&gt;

&lt;p&gt;The use of immutable variables also ensures that the function is &lt;strong&gt;pure&lt;/strong&gt; , as it prevents the side effect of state change after a value is assigned to an immutable variable.&lt;/p&gt;

&lt;p&gt;A key implication of immutability is the &lt;strong&gt;ease of writing parallel/concurrent programs&lt;/strong&gt; in Functional Programming.&lt;/p&gt;

&lt;p&gt;In imperative programming, mutability of states often complicates reasoning about distributed states and concurrent execution, as it is immensely difficult to keep track of shared state changes across threads, cores and processors without running into race conditions. In concurrent operations, data race could arise when two threads perform conflicting operations (with one of them being a write operation) on the same memory location at the same time.&lt;/p&gt;

&lt;p&gt;As Python is primarily designed as an object-oriented programming language, its imperative design patterns lead to complications in managing concurrent access to shared variables that are mutable by default - hence the need for a Global Interpreter Lock (GIL) to lock threads and prevent data race.&lt;/p&gt;

&lt;p&gt;Immutability in functional programming simplifies the implementation of concurrency and provides powerful ways of building consistent and concurrent programs, as the use of immutable shared states leads to elimination of race conditions - making concurrent programming less problematic compared with the imperative approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referential transparency
&lt;/h3&gt;

&lt;p&gt;An important property resulting from the use of pure functions is &lt;strong&gt;referential transparency&lt;/strong&gt; , which is intricately linked to the ability for &lt;strong&gt;equational reasoning&lt;/strong&gt; of programs.&lt;/p&gt;

&lt;p&gt;In the book &lt;em&gt;Functional Programming in Scala&lt;/em&gt; by Paul Chiusano and Rúnar Bjarnason, referential transparency is formally defined as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An expression &lt;code&gt;e&lt;/code&gt; is &lt;em&gt;referentially transparent&lt;/em&gt; if, for all programs &lt;code&gt;p&lt;/code&gt;, all occurrences of &lt;code&gt;e&lt;/code&gt; in &lt;code&gt;p&lt;/code&gt; can be replaced by the result of evaluating &lt;code&gt;e&lt;/code&gt; without affecting the meaning of &lt;code&gt;p&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, referential transparency is a property of expressions (not just functions) such that an expression can be substituted by its equivalent result without affecting the program logic for all programs.&lt;/p&gt;

&lt;p&gt;The absence of side effects is a necessary, but not sufficient condition for referential transparency. The expression also has to be &lt;strong&gt;deterministic&lt;/strong&gt; and &lt;strong&gt;idempotent&lt;/strong&gt; to ensure the equivalence between the expression and its evaluated result.&lt;/p&gt;

&lt;p&gt;A function is &lt;strong&gt;deterministic&lt;/strong&gt; if it will always return the same output given the same input.&lt;/p&gt;

&lt;p&gt;A function is &lt;strong&gt;idempotent&lt;/strong&gt; if it can be applied multiple times without changing the result beyond its initial application. Examples of idempotent functions are the identity function, absolute value function and constant functions.&lt;/p&gt;

&lt;p&gt;The sufficient conditions for referential transparency can be illustrated by the following analogy:&lt;/p&gt;

&lt;p&gt;What if the oven breaks down over time even without external interference, causing the pizza to not be as well baked as before? There might not be observable side effects, but the output returned from the oven-baking operation is no longer the same as previous outputs given the same input. This makes the oven-baking operation non-deterministic since the result depends on when the operation is evaluated, breaking the property of referential transparency.&lt;/p&gt;

&lt;p&gt;A key consequent of the property of referential transparency is that it enables &lt;strong&gt;equational reasoning&lt;/strong&gt; of programs. The expression can be replaced with its equivalent result, and computation can be performed by substituting &lt;em&gt;“equals for equals”&lt;/em&gt; without worrying about evaluation order or program state - similar to evaluating an algebraic expression in mathematics.&lt;/p&gt;

&lt;p&gt;This mode of reasoning about program evaluation, called the &lt;em&gt;substitution model&lt;/em&gt;, is simpler to reason about since the effects of evaluation are purely local and do not require sequential reasoning of state updates to understand the code. Even if there were any bugs in the function during the development process, the ease of reasoning makes debugging easier in functional programming compared with imperative programming.&lt;/p&gt;

&lt;p&gt;When designing reproducible data pipelines at scale, having referential transparency in the code provides the following benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Idempotency of functions assures the programmer that the data transformation functions in the program are &lt;strong&gt;reproducible&lt;/strong&gt; beyond the initial application.&lt;/li&gt;
&lt;li&gt;It enables the programmer to express code in more concise and readable functions and values, improving &lt;strong&gt;readability&lt;/strong&gt; when coding.&lt;/li&gt;
&lt;li&gt;It allows the programmer to focus on debugging within the function scope without worrying about state changes outside the function scope, improving &lt;strong&gt;maintainability&lt;/strong&gt; of core transformations within a data pipeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What’s next: Functional Programming for Data Pipeline Design
&lt;/h2&gt;

&lt;p&gt;In this post, we learn about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Functional programming and how it differs from imperative programming&lt;/li&gt;
&lt;li&gt;Concept of pure functions&lt;/li&gt;
&lt;li&gt;Key principles of functional programming and their implications on data pipeline design&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my upcoming post, I will dive into some features of functional programming and how to implement them in designing functional data pipelines.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want more behind-the-scenes articles on my learning journey as a data professional? Check out my website at &lt;a href="https://ongchinhwee.me"&gt;https://ongchinhwee.me&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Functional-Programming-Scala-Paul-Chiusano/dp/1617290653"&gt;Functional Programming in Scala by Paul Chiusano and Rúnar Bjarnason&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fpsimplified.com/"&gt;Functional Programming Simplified by Alvin Alexander&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cs.cornell.edu/courses/cs3110/2021sp/textbook/intro/mutability.html"&gt;Mutability - Functional Programming in OCaml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://softwareengineering.stackexchange.com/questions/40297/what-is-a-side-effect"&gt;programming languages - What is a side effect? - Software Engineering Stack Exchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cs.stackexchange.com/questions/19297/is-equational-reasoning-an-application-of-referential-transparency"&gt;functional programming - Is Equational Reasoning an application of Referential Transparency? - Computer Science Stack Exchange&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>python</category>
      <category>scala</category>
      <category>functional</category>
    </item>
    <item>
      <title>I Started Learning Scala as a Python Programmer. Here’s Why.</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Sun, 18 Apr 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/hweecat/learning-scala-as-a-python-programmer-motivations-4hg4</link>
      <guid>https://dev.to/hweecat/learning-scala-as-a-python-programmer-motivations-4hg4</guid>
      <description>

&lt;h2&gt;
  
  
  Motivations for learning Scala
&lt;/h2&gt;

&lt;p&gt;One of my tech goals in 2021 is to learn Scala. My key reason for learning Scala is to learn Functional Programming for data engineering.&lt;/p&gt;

&lt;p&gt;The question is: Why go through the trouble of learning Scala if Functional Programming is supported in Python?&lt;/p&gt;

&lt;h3&gt;
  
  
  How I learn different programming paradigms
&lt;/h3&gt;

&lt;p&gt;As a language purist who believes in using tools for their intended purposes, I believe that the best way to learn a new programming paradigm (whether it is Object-Oriented Programming or Functional Programming) in depth is to learn a suitable programming language that is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Designed primarily for the target programming paradigm (not as an afterthought)&lt;/li&gt;
&lt;li&gt;Similar in syntax and code patterns to a programming language that you are already familiar with&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason why point no. 2 is important is so that the focus would be more on learning the programming paradigm without fretting way too much on the syntax and code patterns.&lt;/p&gt;

&lt;p&gt;For example, I learnt Object-Oriented Programming by re-acquainting with C and gradually transitioning to C++ through learning object-oriented concepts such as classes, inheritance and polymorphism. This language transition helped in speeding up the process of learning Python, which is designed to be primarily an object-oriented programming language although the language supports multiple programming paradigms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is Python not ideal for learning functional data engineering
&lt;/h3&gt;

&lt;p&gt;While exploring parallel programming in Python for data science (and I highly recommend you watch &lt;a href="https://youtu.be/E9sv2B3Bb20"&gt;my PyData Global 2020 talk&lt;/a&gt; for a vivid explanation of parallelism), I learnt about functional programming in Python through &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;itertools&lt;/code&gt;. It surprised me how intuitively I could relate functional programming to mathematical functions that make sense, and how I am already subconsciously using some functional programming concepts in visualizing my data flows.&lt;/p&gt;

&lt;p&gt;Since then, I started diving deeper into different programming paradigms but felt that I wasn’t diving sufficiently deep enough into the functional programming paradigm with Python.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Python is primarily an object-oriented programming language&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While functions are first-class objects in Python, the code patterns of the builtins and the Python Standard Library are built around classes and objects. This makes Python primarily designed to encourage object-oriented and imperative design patterns rather than the functional paradigm, even though it is a multi-paradigm programing language that provides developers with some level of flexibility in using their preferred coding pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Parallel programming is not truly concurrent in Python due to Python internals design&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The object-oriented design of Python and its builtins also leads to complications in parallel programming in Python, whereby the problem of breaking down a sequential program into parallel chunks implies the need to manage concurrency to prevent concurrent access to a shared variable (hence the need for a Global Interpreter Lock in CPython).&lt;/p&gt;

&lt;p&gt;As a data professional who deals with data volumes that at least requires some level of parallelism, having to spend precious developer hours figuring out which parts of a sequential program can be refactored for parallelism and consistency is a major source of frustration and angst. Having the intuition to break programs down into smaller independent functions (I/O and non-I/O) do not make the process of refactoring for optimal parallelism any easier.&lt;/p&gt;

&lt;p&gt;I still love Python for its comprehensive data ecosystem and the friends I made from the community, and I am still actively using Python in my personal and work projects.&lt;/p&gt;

&lt;p&gt;However, the demands for scalability and reproducibility of data processing pipelines at a larger scale requires heavy-lifting by parallel or even distributed processing, as well as a programming paradigm that supports that heavy-lifting while allowing data professionals to be productive in designing data solutions that they are confident of in producing the same result for the same data input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why learn Scala for data engineering
&lt;/h3&gt;

&lt;p&gt;There might be various reasons why developers choose to learn Scala. For some, it could be due to web programming. In recent years, a popular reason for learning Scala is for Big Data.&lt;/p&gt;

&lt;p&gt;Here are my reasons for learning Scala for data engineering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Scala is primarily designed for functional programming and object-oriented programming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scala is designed to seamlessly integrate the object-oriented programming paradigm of Java and the functional programming paradigm, and aimed to address criticisms of Java.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The syntax for Scala shares similarities with C/C++ and Python&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scala uses curly-brace syntax similar to C/C++, and also encourages indentation for nested logical blocks (e.g function within a class). The slight difference is that Scala uses 2 spaces instead of 4 spaces or tabs (which is the case for Python).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Scala is a strong statically-typed language with type inference, which saves trivial keystrokes while maintaining strong typing for function definitions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Static typing allows bugs to be caught more easily during the development process, hence avoiding bugs in complex applications. As a strong statically-typed language, code written in Scala is checked for type safety at compile time rather than at runtime. Similar to Python, Scala also supports type inference - meaning that the Scala compiler can often infer the types based on different elements of the source code.&lt;/p&gt;

&lt;p&gt;What Scala differs from Python in terms of type inference is that type annotations are required in arguments of function definitions (they are optional in Python) to prevent breaking changes to function internals and optimize compile times. While I try to use type annotations as much as possible in Python, they do not affect execution on the Python interpreter. Making type annotations explicit as a default has the potential to save precious developer time on debugging applications that rely on multiple functions while ensuring that the desired data type is returned from a function.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s next: my progress in learning Scala as a Python programmer
&lt;/h3&gt;

&lt;p&gt;It has been close to 3 months since I started learning Scala, and it has been an incredibly steep learning curve. While I’m glad that I could rely on my prior experiences with learning programming languages to start becoming productive with Scala, I do understand that the learning curve could have been even steeper for someone who is starting fresh.&lt;/p&gt;

&lt;p&gt;I do have to admit that functional programming does get a lot of getting used to, especially when one has gotten used to writing code in procedural and object-oriented programming paradigms. I would like to think of functional programming as math with defined inputs and outputs, while procedural programming is a step-by-step recipe and object-oriented programming is objects with properties. They are different ways of writing code that works, but the thinking behind the process of writing code differs.&lt;/p&gt;

&lt;p&gt;It does get very tempting to fall back on comfortable code patterns when the learning gets tough, especially in a multi-paradigm programming language like Python. Hence, I do find myself feeling terribly unproductive at times when getting used to (and being “steered towards”) writing code in the functional paradigm. I do love using type annotations in Scala though - they do make debugging much easier during the development process!&lt;/p&gt;

&lt;p&gt;In my upcoming post, I will introduce the basic principles of functional programming and relate these principles to reproducible data pipeline designs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want more behind-the-scenes articles on my learning journey as a data professional? Check out my website at &lt;a href="https://ongchinhwee.me"&gt;https://ongchinhwee.me&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>scala</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Year 2020 in review - when tech conferences go virtual</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Thu, 31 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/hweecat/year-2020-in-review-when-tech-conferences-go-virtual-398f</link>
      <guid>https://dev.to/hweecat/year-2020-in-review-when-tech-conferences-go-virtual-398f</guid>
      <description>

&lt;p&gt;At the start of 2020, I set a goal to speak at 4 tech conferences including one in Europe. COVID-19 disrupted my plans completely, and I was forced to adapt to the new reality of virtual conferences. Here’s my journey from regional to international speaker in the midst of a pandemic, and lessons learnt along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recap of Year 2020: Speaking
&lt;/h2&gt;

&lt;p&gt;In year 2020, I have given a total of &lt;strong&gt;10 talks&lt;/strong&gt; , including an &lt;strong&gt;exclusive episode&lt;/strong&gt; and &lt;strong&gt;my first invited keynote&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 Invited Keynote&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;12 December 2020: “Is Rainfall Getting Heavier? Building a Weather Forecasting Pipeline with Singapore Weather Station Data” at &lt;a href="https://pycode-conference.org/"&gt;PyCode Conference 2020&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;6 Conferences&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;20 March 2020: &lt;a href="https://hweecat.github.io/talk_fossasia-parallel-async-python"&gt;Speed Up Your Data Processing: Parallel and Asynchronous Programming in Python&lt;/a&gt; at &lt;a href="https://summit.fossasia.org/"&gt;FOSSASIA Summit 2020&lt;/a&gt; — Recording &lt;a href="https://youtu.be/aB6f5KicM2Y"&gt;courtesy of FOSSASIA&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;25 April 2020: &lt;a href="https://hweecat.github.io/talk_pypizza-jit-with-numba"&gt;Just-in-Time with Numba&lt;/a&gt; at &lt;a href="https://remote.python.pizza/"&gt;Remote Python Pizza&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;23 July 2020: &lt;a href="https://hweecat.github.io/talk_europython-parallel-async-ds"&gt;Speed Up Your Data Processing&lt;/a&gt; at &lt;a href="https://ep2020.europython.eu/"&gt;EuroPython 2020&lt;/a&gt; — Recording &lt;a href="https://you.tube/PB7_5BQp1SU"&gt;courtesy of EuroPython&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;6 September 2020: &lt;a href="https://hweecat.github.io/talk_pycontw-parallel-async-ds"&gt;Speed Up Your Data Processing: Parallel and Asynchronous Programming in Data Science&lt;/a&gt; at &lt;a href="https://tw.pycon.org/2020/"&gt;PyCon Taiwan 2020&lt;/a&gt; — Recording &lt;a href="https://youtu.be/w2eUdxPQQ78"&gt;courtesy of PyCon Taiwan&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;15 November 2020: “Speed Up Your Data Processing: Parallel and Asynchronous Programming in Data Science” at &lt;a href="https://global.pydata.org/"&gt;PyData Global&lt;/a&gt; — &lt;a href="https://youtu.be/E9sv2B3Bb20"&gt;Pre-recorded talk, post-processed by PyData Global&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;6 December 2020: “Is Rainfall Getting Heavier? Building a Weather Forecasting Pipeline with Singapore Weather Station Data” at &lt;a href="https://pyjamas.live"&gt;PyJamas 2020&lt;/a&gt; — Livestream recording &lt;a href="https://youtu.be/lrb3f8jtZqg?t=1800"&gt;courtesy of PyJamas 2020&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;6 December 2020: “Seeing Data in Multiple Dimensions: Hierarchial Indexing in Pandas and How to Visualize Them” at &lt;a href="https://pyjamas.live"&gt;PyJamas 2020&lt;/a&gt; - Murphy’s Law struck down this one. ::sadface::&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;2 Meetups&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;14 January 2020: &lt;a href="https://hweecat.github.io/talk_juniordevsg_exploring_seasonal_insights_from_sg_weather_data"&gt;Exploring Seasonal Insights from Singapore Weather Station Data&lt;/a&gt; at &lt;a href="https://www.meetup.com/Junior-Developers-Singapore/events/267507133/"&gt;JuniorDevSG Code and Tell&lt;/a&gt; — Recording &lt;a href="https://youtu.be/gNF8D8AkCgY"&gt;courtesy of Engineers.SG&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;28 March 2020: &lt;a href="https://hweecat.github.io/talk_pyladies-jit-with-numba"&gt;Just-in-Time with Numba&lt;/a&gt; at &lt;a href="https://pyladies.com/"&gt;PyLadies International Women’s Month Lightning Talks&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1 Exclusive Collaboration&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;11 November 2020: &lt;a href="https://www.wanted.jobs/events/spotlight_breaking_data_ep3"&gt;Wanted Spotlight - Breaking Data Episode 3: “How to Build Your Data Career Like A Business”&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Facing Impostor Syndrome as a Tech Speaker
&lt;/h2&gt;

&lt;p&gt;Before I got started with speaking at tech conferences and meetups, the question I asked myself was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What would it take for me to be up on this stage as a speaker?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Throughout this year, the question I repeatedly asked myself was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Am I good enough to become a successful speaker?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I struggle a lot with impostor syndrome as a developer and a tech speaker who has been doing most of my speaking for free.&lt;/p&gt;

&lt;p&gt;Even after speaking at a higher-profile conference such as EuroPython, I still wonder if I am good enough to develop a good reputation as a developer and tech speaker.&lt;/p&gt;

&lt;p&gt;In short, I’m not a natural when it comes to speaking in front of an audience. I still get nervous about what could go wrong, whether giving a bad talk would affect my ROI on speaking at a conference.&lt;/p&gt;

&lt;p&gt;When I reflect upon my reasons for speaking at tech events:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build my personal brand and raise my profile in the local tech community&lt;/li&gt;
&lt;li&gt;Gain experience in public speaking&lt;/li&gt;
&lt;li&gt;Pay it forward to the local tech community&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wonder if these reasons still hold true amidst a global pandemic when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The local tech community is getting a bit more muted due to absence of in-person meetups and networking opportunities&lt;/li&gt;
&lt;li&gt;I end up having to either speak one-way to a screen or pre-record my talk during a virtual conference, without much audience interaction&lt;/li&gt;
&lt;li&gt;I find myself struggling to keep myself relevant in the local tech community while juggling my speaking and career goals, thinking of how to prepare for upcoming Call for Proposals and what to speak about in my next speaking opportunity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For year 2021, I will be focusing more on quality over quantity of conference talks - in order to dedicate more time towards my personal learning and career goals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speaking in-person at a Conference amidst a Pandemic
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://summit.fossasia.org/"&gt;FOSSASIA Summit 2020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When: 20 March 2020&lt;/p&gt;

&lt;p&gt;Talk: &lt;a href="https://hweecat.github.io/talk_fossasia-parallel-async-python"&gt;Speed Up Your Data Processing: Parallel and Asynchronous Programming in Python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was really looking forward to attending FOSSASIA Summit since 2019, as it is one of the largest gatherings of open-source enthusiasts - it could even be considered a headliner event for the open-source community. In fact, I was really looking forward to meet Eriol Fox again and bring them around Singapore for meatless food.&lt;/p&gt;

&lt;p&gt;Alas, travel restrictions due to COVID-19 all but prevented most of the overseas speakers and attendees from attending in-person. Even the FOSSASIA organizers could not make it to the venue in person due to COVID-19 restrictions - they flew in from Europe.&lt;/p&gt;

&lt;p&gt;Despite the challenges posed by COVID-19, the organizers decided to proceed with the event with a mix of offline and online talks with live streaming and chats. I could still proceed with giving my talk, and I ended up speaking to a room of less than 10 people and a livestream audience of I-have-no-idea-how-many.&lt;/p&gt;

&lt;p&gt;It was a strange experience attending a conference with so few people. I originally designed this talk to be interactive and audience-driven, with the pace of the talk driven by casual “coffee shop” banters with the audience. While having less than 10 people in the audience does make for a more “intimate” experience, a part of me wished that I could interact with more people as per normal.&lt;/p&gt;

&lt;p&gt;Then again, is anything normal in a pandemic?&lt;/p&gt;

&lt;p&gt;As it turns out, this was my first and last in-person conference speaking of 2020.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Time Speaking at a Conference outside of Asia
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://ep2020.europython.eu/"&gt;EuroPython 2020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Talk: &lt;a href="https://hweecat.github.io/talk_europython-parallel-async-ds"&gt;Speed Up Your Data Processing: Parallel and Asynchronous Programming in Data Science&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of my speaking goals in 2020 is to speak at a tech conference outside of Asia, and this talk was intended for my European speaking debut.&lt;/p&gt;

&lt;p&gt;COVID-19 rendered me unable to travel to speak. However, COVID-19 also led to conferences being moved online and I was able to speak at a European conference “virtually” for the first time.&lt;/p&gt;

&lt;p&gt;I came in without much expectations on audience interest since my talk wasn’t part of the list of talks selected by popular vote - in fact, I was just happy to be given a chance to speak at EuroPython with an impressive lineup of high-profile Pythonistas! Hence, I was pleasantly surprised that my talk at EuroPython managed to make a pretty good impression - and might have opened a few more doors for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speaking virtually at an in-person Conference amidst a Pandemic
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://tw.pycon.org/2020/"&gt;PyCon Taiwan 2020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When: 6 September 2020&lt;/p&gt;

&lt;p&gt;Talk: &lt;a href="https://hweecat.github.io/talk_pycontw-parallel-async-ds"&gt;Speed Up Your Data Processing: Parallel and Asynchronous Programming in Data Science&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Speaking at PyCon Taiwan has been one of my key priorities in year 2020 even before the pandemic, as Taiwan was where I made my international debut on the conference-speaking circuit.&lt;/p&gt;

&lt;p&gt;Alas, I couldn’t fly to Tainan for the in-person conference due to COVID-19.&lt;/p&gt;

&lt;p&gt;Seeing live audience responses from the other end of the remote call while I presented my talk gives me hope about the future of tech conferences though - that one day we may be able to meet in-person again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-recording talk with “live” elements at my first PyData Conference
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://global.pydata.org/"&gt;PyData Global 2020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When: 15 November 2020&lt;/p&gt;

&lt;p&gt;Talk: Speed Up Your Data Processing: Parallel and Asynchronous Programming in Data Science&lt;/p&gt;

&lt;p&gt;By this time, I am getting a bit “sick” of writing reflection posts for virtual conferences. Moreover, this is a pre-recorded talk - no comments about audience response or speaking performance since the talk was not given live anyway.&lt;/p&gt;

&lt;p&gt;Special mention to the folks who designed the virtual conference space on Gather to replicate the “in-person” conference experience as much as possible - it made PyData Global a slightly more engaging experience even when I was getting a bit “fatigued” by virtual conferences and Zoom calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Murphy’s Law Struck Down The Internet and Mobile Network (and One of My Talks)
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://pyjamas.live/"&gt;PyJamas 2020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When: 6 December 2020&lt;/p&gt;

&lt;p&gt;Talk #1: Is Rainfall Getting Heavier? Building a Weather Forecasting Pipeline with Singapore Weather Station Data&lt;/p&gt;

&lt;p&gt;Talk #2: Seeing Data in Multiple Dimensions - Hierarchial Indexing and How to Visualize Them&lt;/p&gt;

&lt;p&gt;For some reason, Murphy’s Law loves to strike when I unexpectedly get an opportunity to give conference talks on the first week of December.&lt;/p&gt;

&lt;p&gt;The good news: Getting both talk proposals accepted via anonymous voting.&lt;/p&gt;

&lt;p&gt;The bad news: Preparing two new talks, and having the Internet connection playing tantrums on my ambitious attempt to prepare two new talks in less than 24 hours.&lt;/p&gt;

&lt;p&gt;Unfortuntely for me, Murphy’s Law won and struck down &lt;strong&gt;both&lt;/strong&gt; my fibre broadband &lt;strong&gt;and&lt;/strong&gt; mobile network by the time my second talk came around. Hence, Talk #1 lives and Talk #2 dies.&lt;/p&gt;

&lt;p&gt;And, I ended up becoming so grumpy about my Internet woes until I ended up sleeping off my frustrations for the rest of the conference. Thanks goodness for YouTube replay.&lt;/p&gt;

&lt;p&gt;I guess there’s a reason why I think giving two talks for a conference is not a good idea - and I resolve to refrain from doing that from now on. Quality over quantity. Sorry, community folks.&lt;/p&gt;

&lt;h2&gt;
  
  
  My first Keynote
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://pycode-conference.org/"&gt;PyCode Conference 2020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When: 12 December 2020&lt;/p&gt;

&lt;p&gt;Talk: Is Rainfall Getting Heavier? Building a Weather Forecasting Pipeline with Singapore Weather Station Data&lt;/p&gt;

&lt;p&gt;I wasn’t really expecting to give any more talks after Pyjamas 2020, so this keynote invitation from the PyCode Conference organizing team came as a surprise.&lt;/p&gt;

&lt;p&gt;My initial thought was: Why did they invite me to be their keynote speaker, when there are more well-known speakers in the Python community? Why me?&lt;/p&gt;

&lt;p&gt;As I considered carefully on whether to accept the keynote invitation (I was asked to choose between Web and Data for the theme), I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I make use of this opportunity to tell my story while setting the pitch of the event?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that is how I decided to make my uniquely Singapore experience of “ponding” the centrepiece of my keynote speech - and tropical rainfall forecasting serves as the medium for me to explore the challenges faced by various experts in predicting rainfall amidst the backdrop of climate change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Epilogue
&lt;/h2&gt;

&lt;p&gt;After giving 7 talks within one year and most of them being virtual, I am starting to experience burnout and fatigue in some form.&lt;/p&gt;

&lt;p&gt;Can I just emphasize once again that it feels kinda weird speaking to the webcam and you don’t know how many people are actually watching your talk? Imagine that happening for most of the year.&lt;/p&gt;

&lt;p&gt;With the pandemic not showing any signs of abating, virtual conferences seem to be the “new normal” for aspiring conference speakers. That means I have to commit to making sure my speaking-related tech is in good condition and my speaking style is catered towards a virtual “on-demand” audience with little interaction.&lt;/p&gt;

&lt;p&gt;While the temptation of giving as many talks as possible without the constraints of travel seems tempting, I found myself in a delicate balancing act between my speaking-related prep, my career, and my personal life all muddled into one in year 2020.&lt;/p&gt;

&lt;p&gt;Hence, my goal in year 2021 is not to speak at even more conference compared to year 2020.&lt;/p&gt;

&lt;p&gt;My goal in year 2021 is to focus on quality and deliver talks in 4 conferences locally and internationally, and I am starting 2021 with a tally of one.&lt;/p&gt;

</description>
      <category>reflection</category>
    </item>
    <item>
      <title>#Shitoberfest: How free T-shirts ruined #Hacktoberfest2020</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Sat, 03 Oct 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/hweecat/shitoberfest-how-free-t-shirts-ruined-hacktoberfest2020-142l</link>
      <guid>https://dev.to/hweecat/shitoberfest-how-free-t-shirts-ruined-hacktoberfest2020-142l</guid>
      <description>

&lt;h2&gt;
  
  
  About Hacktoberfest
&lt;/h2&gt;

&lt;p&gt;Hacktoberfest is an annual event organized by &lt;a href="https://digitalocean.com"&gt;DigitalOcean&lt;/a&gt; that celebrates open-source contributions. Occuring every October, the goal of Hacktoberfest is to encourage developers (of all backgrounds and skill level) and companies to make positive contributions to the open-source community.&lt;/p&gt;

&lt;p&gt;To encourage developers to make more open-source contributions, the first 70,000 participants who successfully makes 4 pull requests (PRs) between 1 - 31 October in any time zone to &lt;em&gt;any&lt;/em&gt; public repository on GitHub are eligible to receive a prize in the form of a limited-edition T-shirt. Yes, no limits.&lt;/p&gt;

&lt;p&gt;Alternatively, participants can choose to plant a tree instead of getting a free T-shirt in year 2020 - as a show of support for #sustainability.&lt;/p&gt;

&lt;p&gt;All these sound like an initiative with good intentions on paper - incentivise developers to contribute to open-source projects. Unfortunately, the organizers underestimated the extent of what people are willing to do for the sake of getting free T-shirts (or freebies in general).&lt;/p&gt;

&lt;h2&gt;
  
  
  How low-quality PRs turned #Hacktoberfest2020 into #Shitoberfest
&lt;/h2&gt;

&lt;p&gt;On October 1st after work, I was just minding my business scrolling Twitter and searching GitHub for interesting #hacktoberfest issues to work on - and then I saw this tweet from a hilariously-named Twitter account called &lt;a href="https://twitter.com/shitoberfest"&gt;@shitoberfest&lt;/a&gt; that seems to be dedicated towards curating spam PRs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hi, I'm &lt;a href="https://twitter.com/shitoberfest?ref_src=twsrc%5Etfw"&gt;@shitoberfest&lt;/a&gt;. Do you maintain an opensource project?   &lt;/p&gt;

&lt;p&gt;Send a screenshot of bullshit drive by pull-requests caused by &lt;a href="https://twitter.com/hashtag/hacktoberfest?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#hacktoberfest&lt;/a&gt; and tag &lt;a href="https://twitter.com/shitoberfest?ref_src=twsrc%5Etfw"&gt;@shitoberfest&lt;/a&gt; for curation and amplification. &lt;a href="https://t.co/50wuPISbYb"&gt;pic.twitter.com/50wuPISbYb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— #shitoberfest (@shitoberfest) &lt;a href="https://twitter.com/shitoberfest/status/1311646233128181760?ref_src=twsrc%5Etfw"&gt;October 1, 2020&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;“What on earth is happening?” I wondered while looking through the tweets in the account and realizing that the PRs seemed to follow similar patterns - with “Awesome Project” and other nonsensical edits to READMEs.&lt;/p&gt;

&lt;p&gt;Next, I saw this tweet by &lt;a href="https://twitter.com/GaelVaroquaux"&gt;Gael Varoquaux (@GaelVaroquaux)&lt;/a&gt; who is a co-founder of the &lt;a href="https://scikit-learn.org"&gt;scikit-learn project&lt;/a&gt;. And it looks like the spam PR situation is MASSIVE.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A difficulty in a popular open-source project: people submit contributions to gain credit, but not always useful (below: no content + invalid markup).  &lt;/p&gt;

&lt;p&gt;scikit-learn had 10000 pull requests, 732 still open.&lt;br&gt;&lt;br&gt;
Reviewing them is costly qualified labor.  &lt;/p&gt;

&lt;p&gt;Rapid closing harms openness 🤷 &lt;a href="https://t.co/6DKcB2aHmO"&gt;pic.twitter.com/6DKcB2aHmO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Gael Varoquaux (@GaelVaroquaux) &lt;a href="https://twitter.com/GaelVaroquaux/status/1311582305559998465?ref_src=twsrc%5Etfw"&gt;October 1, 2020&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;I highly recommend checking out &lt;a href="https://twitter.com/shitoberfest"&gt;@shitoberfest&lt;/a&gt; for a good laugh at some of the spam PRs. It’s so bad that it’s hilarious to the casual observers who don’t care too much about free T-shirts.&lt;/p&gt;

&lt;p&gt;It’s not so funny for &lt;a href="https://blog.domenic.me/hacktoberfest/"&gt;open-source maintainers&lt;/a&gt; though - they had to do extra work cleaning up spam PRs and tagging them as “invalid” or “spam” so that those spam PRs do not count towards the Hacktoberfest tally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause
&lt;/h2&gt;

&lt;p&gt;What caused #Shitoberfest? It wasn’t that massive a problem last year even though the problem of “spam PRs” have always been there, so it could not have been caused solely by the incentive of getting a free T-shirt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://joel.net/how-one-guy-ruined-hacktoberfest2020-drama"&gt;This excellent narrative post by Joel Thoms&lt;/a&gt; explains in detail what caused the #Shitoberfest drama (thanks &lt;a href="https://twitter.com/eugeneyan"&gt;Eugene Yan&lt;/a&gt; for sharing that gem!): a YouTuber called CodeWithHenry who demonstrated how easy it is to make a Pull Request to a repo in order to win free stuff - by creating a low-quality PR.&lt;/p&gt;

&lt;p&gt;This led to his viewers following &lt;strong&gt;exactly&lt;/strong&gt; what he did, leading to this &lt;strong&gt;#Shitoberfest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Without alluding to any particular nationality/race/ethnicity, he was speaking a non-English language in that demonstration video. That video has since been removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Psychology and Aftermath
&lt;/h2&gt;

&lt;p&gt;The next question is: What is the psychology behind those people who created spam PRs just for the sake of getting a free T-shirt?&lt;/p&gt;

&lt;p&gt;There are two key factors that might have influenced such behaviour:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://thedecisionlab.com/insights/consumer-insights/impact-free-consumer-decision-making/"&gt;The Psychology of Free&lt;/a&gt; - in this case, the psychology of trying to get free stuff with as low an effort or opportunity cost as possible.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://online.king.edu/news/psychology-of-fomo/"&gt;The Psychology of FOMO (Fear of Missing Out)&lt;/a&gt; - in this case, the fear of missing out on a “limited edition” T-shirt that spells “bragging rights” after watching a YouTuber getting one from making PRs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s human nature to love free stuff, especially if there’s a way to get them without spending too much effort and time - it’s like getting an undeserved gift. It’s also human nature to be subject to peer pressure and FOMO when seeing others getting stuff that we wish we had but do not have.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;it’s not fair to exploit human nature in a way that causes masses of people to generate spam that leads to wastage of other people’s limited resources&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is also &lt;strong&gt;not fair to other developers who genuinely want to learn through contributing to open-source projects that they care about, be it their own projects or projects by other developers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The problem is that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DigitalOcean created the limited T-shirt incentive to attract more people to contribute to any public GitHub repositories as part of Hacktoberfest.&lt;/li&gt;
&lt;li&gt;CodeWithHarry wanted to share with his audience about ways to get free stuff through making PRs for Hacktoberfest, and demonstrated with a low-quality PR for convenience and speed - without explicitly mentioning in the video that the audience should not follow exactly what he did as it is a low-quality PR &lt;em&gt;just for demonstration&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Some of those spammers might probably be new to open source and/or Hacktoberfest, do not know how to contribute meaningfully, but really want that free Hacktoberfest T-shirt anyway.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And these cumulated in a ruined Hacktoberfest and change of rules.&lt;/p&gt;

</description>
      <category>reflection</category>
    </item>
    <item>
      <title>I feel like an impostor in tech. I'm still here.</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Sun, 08 Mar 2020 15:06:06 +0000</pubDate>
      <link>https://dev.to/hweecat/i-feel-like-an-impostor-in-tech-i-m-still-here-2i91</link>
      <guid>https://dev.to/hweecat/i-feel-like-an-impostor-in-tech-i-m-still-here-2i91</guid>
      <description>&lt;p&gt;I started my career in tech in 2014 as a research assistant at an aerospace corporate research laboratory, developing proof-of-concept experiments for advanced manufacturing techniques for additive-manufactured aircraft engine components. While I fretted over getting the required metallic test pieces to conduct experiments and was getting increasingly frustrated at the budgeting and lead-time, I became envious of the research associates and research fellows working on computational projects whose research progress depended less on materials lead-time and could work on other aspects of their projects while waiting for their simulations to run.&lt;/p&gt;

&lt;p&gt;During that low point of my career, I thought to myself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I switch from conducting physical experiments to conducting simulated experiments as a career?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dissatisfied with the state of my career, I resigned from my job towards the end of my 2-year contract with no clear path forward, knowing that my journey in pursuing a Masters in computational science and modeling would be far from smooth-sailing. I may have learnt to code in C and MATLAB during my undergraduate studies, and I did pretty well in writing numerical codes for my projects (with plenty of help from googling and reading sample codes). However, it had been more than 2 years since I wrote a line of code - how do I brush up on my coding skills to survive a computational Masters?&lt;/p&gt;

&lt;p&gt;It certainly did not help that I was also going through a very low point in my personal life, escaping from an abusive relationship and struggling with physical pain. Nevertheless, I decided that I had to move forward with my long-term goal of becoming a computational researcher, and I had to start somewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning to be human through coding and tech
&lt;/h2&gt;

&lt;p&gt;While waiting for the first semester of my Masters program to commence, I did a refresher and started writing my first lines of code for the first time in 2 years. My coding skills had gone rusty from lack of use, but I felt free for the first time in 2 years - free to experiment, and free to make mistakes in the process. The pressures of trying to upkeep a front of perfection and invulnerability in the name of trying to be extraordinary slipped away as I encountered error messages and fixed the bugs in my code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's okay to make mistakes in the process - you're not going to destroy the computer with your syntax errors.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I am reminded that syntax errors are part of the learning process and do not necessarily make me an impostor, I stopped beating myself up for making mistakes.&lt;/p&gt;

&lt;p&gt;It has been more than 3 years since I came back into coding through computational science, and I have been working as a data engineer at a government-linked corporation since 1.5 years ago.&lt;/p&gt;

&lt;p&gt;I'm very thankful that I was given a chance despite having learnt Python on my own just a few months ago, because they saw the value in the computational and research skills that I learnt through my Masters project and research stint. I have spoken at 2 regional tech conferences last year in Asia, and I am excited to be making my European speaking debut at DragonPy in Slovenia this year (assuming COVID-19 does not derail plans, fingers crossed). I have also made my very first contribution to the documentation for pandas 1.0 release, and got onto the waitlist for OSCON this year (phew, not rejected yet I guess).&lt;/p&gt;

&lt;p&gt;I still feel very much like an impostor in tech, because I don't see enough of people like me in tech events and conferences. I still fare badly at technical interviews especially for data structures and algorithms, and struggle to understand all the big data tools that are highly sought after and talked about in the data ecosystem. I still feel a great sense of fear and shiftiness when I attempt to write technical posts in my developer blogs or deliver talks on stage, worrying that I would be exposed as a fraud if I make mistakes in my writing or mess up my talk.&lt;/p&gt;

&lt;p&gt;Nevertheless, I'm still here in tech and I'm still coding.&lt;/p&gt;

&lt;p&gt;Despite my own insecurities in my tech capabilities and having to contend with implicit discrimination in male-dominated workplaces with entrenched biases, I learnt the value of community in tech.&lt;/p&gt;

&lt;p&gt;Even as I felt drained when attending tech events and conferences alone initially, the act of showing up and being radically honest in my intentions helped in making myself feel part of the tech community when people start noticing and reaching out to me.&lt;/p&gt;

&lt;p&gt;Even with COVID-19 leading to a string of cancellations of major tech events, the local tech community came together to move the meetups online so that members of the tech community could continue to share their experiences and connect with each other while staying safe.&lt;/p&gt;

&lt;p&gt;Most importantly, it is through getting involved in the tech community and staying in tech that I learn that &lt;strong&gt;it is okay to be imperfect and vulnerable&lt;/strong&gt; even as a person from an underrepresented community. I write code and do tech, but I am also human and I am still learning how to improve myself everyday. And that has given me permission to make mistakes, learn from them, and grow to become a better person in my career and personal life.&lt;/p&gt;

</description>
      <category>wecoded</category>
      <category>theycoded</category>
    </item>
    <item>
      <title>Year 2019 in review - getting started with speaking at a tech conference</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Tue, 31 Dec 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/hweecat/year-2019-in-review-getting-started-with-speaking-at-a-tech-conference-op7</link>
      <guid>https://dev.to/hweecat/year-2019-in-review-getting-started-with-speaking-at-a-tech-conference-op7</guid>
      <description>

&lt;p&gt;At the start of 2019, I set a goal to speak at a tech event. By the end of 2019, I’ve spoken at 2 meetups and 2 conferences. Here’s my journey from wide-eyed event attendee to tech conference speaker, and lessons learnt along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recap of Year 2019: Speaking
&lt;/h2&gt;

&lt;p&gt;In year 2019, I have given a total of &lt;strong&gt;4 talks&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2 Conferences&lt;/strong&gt; :

&lt;ol&gt;
&lt;li&gt;31 August 2019: &lt;a href="https://hweecat.github.io/talk_how-to-make-your-data-processing-faster"&gt;How to Make Your Data Processing Faster: Parallel Processing and JIT in Data Science&lt;/a&gt; at &lt;a href="https://asia.womenwhocode.dev/"&gt;Women Who Code CONNECT Asia 2019&lt;/a&gt; — Recording &lt;a href="https://youtu.be/RX5rlt3jAt0"&gt;courtesy of Engineers.SG&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;1 December 2019: &lt;a href="https://hweecat.github.io/talk_extracting_seasonal_insights_from_sg_weather_station_data/"&gt;Making Open Weather Data More Accessible: Extracting Seasonal Insights from Singapore Weather Station Data&lt;/a&gt; at &lt;a href="https://www.openup.global/"&gt;OpenUP Global Summit 2019&lt;/a&gt; — Recording &lt;a href="https://www.youtube.com/watch?v=x8CtEtn0vsc"&gt;courtesy of Open UP Summit&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 Meetups&lt;/strong&gt; :

&lt;ol&gt;
&lt;li&gt;27 August 2019: &lt;a href="https://hweecat.github.io/talk_parallel-programming-python"&gt;Parallel Processing in Python&lt;/a&gt; at &lt;a href="https://www.meetup.com/Singapore-Python-User-Group/events/263765155/"&gt;Python User Group Singapore Meetup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;28 November 2019: &lt;a href="https://hweecat.github.io/talk_contributing-pandas-docs-first-time"&gt;Contributing to pandas documentation for the first time - lessons from open source&lt;/a&gt; at &lt;a href="https://www.meetup.com/Women-Who-Code-Singapore/events/266037585/"&gt;Women Who Code Singapore TalksDev #5&lt;/a&gt; — Recording &lt;a href="https://youtu.be/qGPaRTG17ts"&gt;courtesy of Engineers.SG&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Speaking at Tech Events: Why?
&lt;/h2&gt;

&lt;p&gt;I’ve attended tech events and conferences since 2018, and I’ve learnt a lot from attending the sessions. As I sat among the audience watching in awe at the speakers and panelists on stage delivering their talks and speeches with confidence and style, I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What would it take for me to be up on this stage as a speaker?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’m more of an introvert in nature, and extensive networking during tech events can get very draining for me even though I do love interacting with new people. Having to find various means to start a conversation only to have it fall flat on people also adds on a lot of pressure on me, and consecutive occurrences do drain me severely of energy such that I need to take breaks from attending tech events. Moreover, I tend to get really nervous when speaking in front of an audience - unlike music performance whereby it is natural not to maintain eye contact with the audience while getting your whole self (body + mind) immersed in the music, public speaking requires a lot more attention on eye contact and body language.&lt;/p&gt;

&lt;p&gt;In short, I’m not a natural when it comes to speaking in front of an audience. Still, I want to speak at tech events for the following reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build my personal brand and raise my profile in the local tech community&lt;/li&gt;
&lt;li&gt;Gain experience in public speaking&lt;/li&gt;
&lt;li&gt;Pay it forward to the local tech community&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;In end January, I attended a panel discussion on “Getting Started with Public Speaking” by Women Who Code Singapore. It was at this event where I met three of the panelists who would turn out to be pivotal in my speaking journey - Renu Yadav (Women Who Code Singapore), &lt;a href="https://twitter.com/hj_chen"&gt;Chen Hui Jing&lt;/a&gt; (organizer of &lt;a href="https://twitter.com/singaporecss"&gt;SingaporeCSS&lt;/a&gt;) and &lt;a href="https://twitter.com/coderkungfu"&gt;Michael Cheng&lt;/a&gt; (JuniorDevSG, EngineersSG).&lt;/p&gt;

&lt;p&gt;As it turns out, Hui Jing also organizes Global CFP Diversity Day in Singapore and was promoting the event (as well as her meetup SingaporeCSS, which always needs speakers). I originally intended to attend another tech event on the same day, but woke up late and decided at the last minute to attend Global CFP Diversity Day - just to see what it was about.&lt;/p&gt;

&lt;p&gt;During the Global CFP Diversity Day workshop, we need to write a speaker profile and work through a whole list of questions on crafting a CFP submission. Uh okay, what can a data engineer who is barely 5 months into the role write or speak about that would impress the CFP panel? Wait what, I have to tell Hui Jing about myself?&lt;/p&gt;

&lt;p&gt;(insert more neverending questions that sprouted after Global CFP Diversity Day)&lt;/p&gt;

&lt;p&gt;As it turns out, role-related pains/complaints/angst can be a great source of inspiration for talk proposals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source of Inspirations for Talk Proposals
&lt;/h2&gt;

&lt;p&gt;The talks I have proposed to meetups and conferences so far are mainly derived from pains/complaints/angst that I faced while working on data analytics projects - both at work and while working on side projects during my free time. I tend to joke with fellow developers I met during tech events that I write talk proposals and give talks to “make up for the time spent on wrangling with all those dev issues and angsting/bitching about it” and to “make full use of the opportunity to speak to air my grievances while building my personal brand”.&lt;/p&gt;

&lt;p&gt;For example, my talk on How to Make Your Data Processing Faster started out as a by-product from the Shopee Data Science Challenge which two of my colleagues and I participated in. As it was our first data science challenge, we faced loads of challenges processing our images and using them as our model inputs. To milk the most out of the “suffering” and precious time lost in trying to process the images, I wrote a Medium post about it and used whatever I wrote as a CFP idea with the intention to submit to multiple conferences. It turned out that data processing is a common bottleneck and a major pain point among data and software engineers, and I was overwhelmed yet honoured by how positive the reception was when I started speaking about parallel processing in data science.&lt;/p&gt;

&lt;p&gt;In short, if you face a challenge/problem/issue at work and managed to solve it after expanding loads of time and effort, why not milk your pain’s worth by giving a talk and sharing your experiences with fellow developers so that they could learn from your experiences? After all, we can learn from each other’s experiences with a technology or a problem to solve, and that is independent of the number of years of experiences in the industry given how fast technology is changing.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Time Speaking at a Tech Event
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://www.meetup.com/Singapore-Python-User-Group/events/263765155/"&gt;Python User Group Singapore Meetup&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When: 27 August 2019&lt;/p&gt;

&lt;p&gt;Talk: &lt;a href="https://hweecat.github.io/talk_parallel-programming-python"&gt;Parallel Processing in Python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For my first-ever public tech talk, I was initially thinking of starting small by speaking at relatively more “beginner-friendly” meetups such as JuniorDevSG Code and Tell. While I submitted my first CFP to a conference, I was prepared to submit CFPs to multiple conferences and meetups until I receive a talk acceptance.&lt;/p&gt;

&lt;p&gt;Surprisingly, my first CFP submission on How to Make Your Data Processing Faster was accepted by end June before I had a meetup talk scheduled. I needed to practice my conference talk at a meetup where I could get relevant feedback fast, and I was informed by Ka Ho that there would be a long waiting list for speaker slots at JuniorDevSG Code and Tell. Due to the urgency of the situation, I booked a speaking slot with Martin for the Python User Group Singapore August 2019 meetup to practice a key portion of my conference talk. I was informed that the meetup would be held at the new Zendesk office at Marina One and the typical turnout would be around 50-60 people. The day before I was due to speak, the RSVP at Meetup.com went up to 200 people. Realising that I would have to deliver my first-ever meetup talk in front of more than 100 people even if 50% of the RSVPs dropped out, I took time off work to refine and practice my talk thoroughly.&lt;/p&gt;

&lt;p&gt;On the day itself, the actual turnout was over 100 people - way more than I expected, especially since I have been relatively low-key about the talk, even removing any references to my gender in the speaker profile. There were technical issues, my “tech-y” jokes and Spark references kinda fell flat on the audience, and there was a slight overrun. Surprisingly, more than half the audience were still seated despite the slight overrun. I thought I kinda messed up my talk, so I was pleasantly surprised when a few people in the audience approached me off-stage (even though it was already pretty late) to thank me and give positive feedback for the talk.&lt;/p&gt;

&lt;p&gt;No talk recordings here; frankly speaking, I don’t even dare to watch my own recording on my phone in full. Speaking in front of more than 100 people for a meetup is already quite a feat, and I’m glad I survived better than I thought I could despite having a nervous start.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Time Speaking at a Conference
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://asia.womenwhocode.dev/"&gt;Women Who Code CONNECT Asia 2019&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When: 31 August 2019&lt;/p&gt;

&lt;p&gt;Talk: &lt;a href="https://hweecat.github.io/talk_how-to-make-your-data-processing-faster"&gt;How to Make Your Data Processing Faster: Parallel Processing and JIT in Data Science&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the first conference that took a chance on me to deliver a full-length talk at the conference stage. I enjoyed the speakers’ dinner, got to meet international speakers such as the really awesome &lt;a href="https://twitter.com/jiaqicodes"&gt;Jiaqi Liu&lt;/a&gt;, &lt;a href="https://twitter.com/milhauschan"&gt;Millie Chan&lt;/a&gt; and &lt;a href="https://twitter.com/kaatloo"&gt;Kat Liu&lt;/a&gt;, and watched Hui Jing put up a highly-entertaining talk on &lt;a href="https://www.youtube.com/watch?v=SXwBxro6y40"&gt;Creating Art with CSS&lt;/a&gt; despite being massively jetlagged from her conference travels.&lt;/p&gt;

&lt;p&gt;I had a practice talk on the Parallel Processing portion at the Python User Group Singapore August 2019 meetup. Based on the questions and feedback collated from the audience, I made some improvements to the slides and even sought the help of the Javascript/Node.JS folks on Twitter for some ideas on how to explain async to the general audience. Having the experience of speaking on stage behind a podium in front of more than 100 people + doing the open pose as suggested by the WWCode Taipei folks did help significantly with the nerves before the talk too.&lt;/p&gt;

&lt;p&gt;Feedback from the audience was pretty good. Quite a number of people in the audience approached me off-stage, throughout the conference (including lunchtime) and/or on LinkedIn to thank me for the talk and give positive feedback on how much they learnt from the talk. I’ve also received a couple of mentions on Twitter which were really nice. Having someone come up to you and say “hey I attended your talk and it was really interesting” makes all the preparation work for the conference talk worth it.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RX5rlt3jAt0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Time Speaking at a Conference outside of Singapore
&lt;/h2&gt;

&lt;p&gt;Where: &lt;a href="https://www.openup.global/"&gt;Open UP Global Summit 2019&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Talk: &lt;a href="https://hweecat.github.io/talk_extracting_seasonal_insights_from_sg_weather_station_data/"&gt;Making Open Weather Data More Accessible: Extracting Seasonal Insights from Singapore Weather Station Data&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the first time I travelled out of Singapore to speak at a conference, and my first time delivering a talk with a demo segment. In the spirit of #opendata, the core objective of the talk is to show how we could make open weather data more accessible to anyone - developers and non-developers included, hence the title.&lt;/p&gt;

&lt;p&gt;A large reason why I got the chance to speak at Open UP Global Summit was because the main organizer was in attendance at Women Who Code CONNECT Asia and enjoyed my talk. I feel incredibly honoured to be trusted with the conference stage, and blessed to be given the opportunity to travel to Taipei and deliver a full-length talk based on my weather station data API scraping project.&lt;/p&gt;

&lt;p&gt;I refactored the code for my weather station data API scraping project (which was a product of a random weekend coding exploration) and prepared the Jupyter notebook for the time series visualizations in advance. As it was my first time delivering the talk in a demo-driven format, I prepared the presentation slides first and rehearsed the non-demo parts incessantly while conceiving of possible plan Bs in case the API scraping demo does not run smoothly. As my speaking slot was on 1st December and I wanted to analyse weather data up to end November, I ran both the API scraping and time series visualization notebook to obtain the latest data and visualizations, and tested both demo segments until 3am to ensure that I could showcase the demo with the latest weather readings on stage.&lt;/p&gt;

&lt;p&gt;On the day of the talk itself, Murphy’s Law of Demos struck with a Wifi connection that kept dropping every 10 minutes (because I was using the venue wifi had to keep re-doing the login regularly) and issues with my Jupyter server. I made a snap decision to truncate my demo, explain a bit about the scraping code and showcase an offline Jupyter notebook of the time series visualization with all the codes executed beforehand.&lt;/p&gt;

&lt;p&gt;Once again, Murphy’s Law of Demos struck. While attempting to showcase the offline Jupyter notebook on Visual Studio Code, the scrolling turned wonky and I had difficulties scrolling to the visualization that I wanted to show the audience! I had not thought of a plan B for that situation, and with only 3 minutes left, I wrestled with the scrolling and finally managed to showcase the time series box-and-whisker plots for the scraped weather data at the last minute. Not too sure if the audience picked up that I was kinda “panicking” with my demo, though at least two of the speakers couldn’t tell that it was only my first year of speaking or conducting a demo on stage.&lt;/p&gt;

&lt;p&gt;Another blunder I made on hindsight was that I did not show my last slide with my social media and GitHub repo before ending my talk, so that was a lost opportunity for self-promotion.&lt;/p&gt;

&lt;p&gt;After the talk, I was feeling fairly negative about what happened to the demo segments on stage and felt that I messed up big time, especially since the audience didn’t seem too responsive to my attempts in engaging them through questions. The feeling of “I think I messed up big time” intensified when not many people tweeted about my talk (except for my fellow speaker and tech community Korean sister &lt;a href="https://twitter.com/sujinleeme"&gt;Sujin Lee&lt;/a&gt; who delivered a highly informative talk on &lt;a href="https://www.youtube.com/watch?v=SRit-fr7bgo"&gt;data-driven design for visualization&lt;/a&gt;) or approached me off-stage throughout the conference, but I was assured by the organizers and a few of my fellow speakers not to worry too much and that I did pretty well for my talk.&lt;/p&gt;

&lt;p&gt;I still felt that I could have done much better for this talk especially in the demo segments, but it was a great international speaking experience nevertheless. I also learnt to be less harsh on myself when things do not go according to plan on stage, as it is likely that the audience may not even notice your mistakes or stumbles if you keep your composure and continue with your performance.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/x8CtEtn0vsc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Epilogue
&lt;/h2&gt;

&lt;p&gt;Fate seems to work in mysterious ways, and this time it seems to go full circle.&lt;/p&gt;

&lt;p&gt;Remember that I said I was initially thinking of starting small by speaking at JuniorDevSG Code and Tell?&lt;/p&gt;

&lt;p&gt;After my first conference talk at Women Who Code CONNECT Asia and during my speaking break, I was approached by Michael Cheng to give a talk at JuniorDevSG Code and Tell which would be held on 14 January 2020. It looks like my first talk at JuniorDevSG Code and Tell will indeed be my first talk - for the year 2020.&lt;/p&gt;

&lt;p&gt;And this time, I would like to deliver a better performance for my Singapore Weather Station talk.&lt;/p&gt;

</description>
      <category>yearinreview</category>
      <category>techtalks</category>
      <category>speaking</category>
      <category>career</category>
    </item>
    <item>
      <title>Understanding Python Dependency Management using pideptree</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Fri, 25 Oct 2019 01:44:56 +0000</pubDate>
      <link>https://dev.to/hweecat/understanding-python-dependency-management-using-pideptree-52fd</link>
      <guid>https://dev.to/hweecat/understanding-python-dependency-management-using-pideptree-52fd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Learning about tree-based dependency management for a team project developed in Python using pipdeptree&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dependency management is important, as packages depend on versions of other core packages in order to run as intended. Typically in a Python project, dependencies are downloaded using a requirements.txt file, which lists the packages and their dependencies as a flat file. While the package versions are included in the requirements.txt file, the dependency relationships are not explicitly stated. Determining the dependency relationships between packages using requirements.txt often requires "reverse engineering" in the form of tracing the dependencies of each installed package and figuring out why pip installed certain packages (since pip does the work of resolving package dependencies when installing packages).&lt;/p&gt;

&lt;p&gt;While searching for ways to resolve the multiple requirements.txt files from my colleages within a team project, I stumbled across &lt;code&gt;pipdeptree&lt;/code&gt;, a command-line utility for displaying installed Python packages in the form of a dependency tree. The output is displayed in a tree-based format instead of a flat list, showing the dependency relationships between installed Python packages and their associated dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using pipdeptree for dependency management
&lt;/h2&gt;

&lt;p&gt;To install pipdeptree, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pipdeptree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or, if you prefer to use conda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;conda &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; conda-forge pipdeptree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to be able to manage dependencies within virtual environments, &lt;code&gt;pipdeptree&lt;/code&gt; has to be installed within each individual virtual environment. If you are starting a new virtual environment for a project and would like to use &lt;code&gt;pipdeptree&lt;/code&gt; for dependency management, you would have to install &lt;code&gt;pipdeptree&lt;/code&gt; in that new virtual environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency tree output
&lt;/h3&gt;

&lt;p&gt;To view the dependency tree of every installed package within the virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipdeptree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To view the dependency tree of a particular package e.g. pandas, the flag &lt;code&gt;-p&lt;/code&gt; or &lt;code&gt;--packages&lt;/code&gt; is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipdeptree &lt;span class="nt"&gt;-p&lt;/span&gt; pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To view the reverse dependency tree - the packages that are dependent on every installed package within the virtual environment, the flag &lt;code&gt;-r&lt;/code&gt; or &lt;code&gt;--reverse&lt;/code&gt; is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipdeptree &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes we may prefer to have the dependency tree displayed as json representation to be used as input to other external tools. In this case, the flag &lt;code&gt;j&lt;/code&gt; or &lt;code&gt;--json&lt;/code&gt; outputs a flat list of all packages with their immediate dependencies, while the flag &lt;code&gt;--json-tree&lt;/code&gt; outputs a nested json representing the dependency relationships between packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipdeptree &lt;span class="nt"&gt;--json&lt;/span&gt;       &lt;span class="c"&gt;# for immediate dependencies&lt;/span&gt;

pipdeptree &lt;span class="nt"&gt;--json-tree&lt;/span&gt;  &lt;span class="c"&gt;# for nested dependencies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To lay out the dependency graph, GraphViz is required in both the command-line interface and the virtual environment. The available output formats are dot, jpeg, pdf, png and svg. For example, if I would like to output my dependency graph in pdf, I use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipdeptree &lt;span class="nt"&gt;--graph-output&lt;/span&gt; pdf &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dependencies.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Installing Graphviz in virtual environment
&lt;/h4&gt;

&lt;p&gt;First, I installed Graphviz on Ubuntu 18.04 LTS Windows Subsystem for Linux (WSL) using &lt;code&gt;apt-get install&lt;/code&gt; by using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;graphviz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I installed graphviz for Python using conda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;conda &lt;span class="nb"&gt;install &lt;/span&gt;graphviz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As of now, I have yet to get Grahpviz running successfully on Windows + Anaconda, but I will try setting up Graphviz on Windows to work with pipdeptree when I have the time (still figuring out what went wrong). Nevertheless, Graphviz works smoothly on Ubuntu 18.04 LTS WSL, so my current dependency management workflow is now on pip + venv + pipdeptree - and it works pretty smoothly without additional packages that conda installs in environments!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/pipdeptree/"&gt;pipdeptree . PyPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anaconda.org/conda-forge/pipdeptree"&gt;pipdeptree :: Anaconda Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.columbia.edu/~njn2118/journal/2016/3/24.html"&gt;Using pipdeptree in a virtualenv&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>todayilearned</category>
      <category>todayisearched</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Accelerating Batch Processing of Images in Python — with gsutil, numba and concurrent.futures</title>
      <dc:creator>Ong Chin Hwee</dc:creator>
      <pubDate>Sun, 26 May 2019 18:56:47 +0000</pubDate>
      <link>https://dev.to/hweecat/accelerating-batch-processing-of-images-in-python-with-gsutil-numba-and-concurrent-futures-59ah</link>
      <guid>https://dev.to/hweecat/accelerating-batch-processing-of-images-in-python-with-gsutil-numba-and-concurrent-futures-59ah</guid>
      <description>&lt;h4&gt;
  
  
  How to accelerate batch processing of almost a million images from &lt;strong&gt;several months&lt;/strong&gt; to just around &lt;strong&gt;a few days&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In a data science project, one of the biggest bottlenecks (in terms of time) is the constant wait for the data processing code to finish executing. Slow code, as well as intermittent connection to web and remote instances affect every step of a typical data science pipeline — data collection, data pre-processing/parsing, feature engineering, etc. Sometimes, the gigantic execution times even end up making the project infeasible and often forces a data scientist to work with only a subset of the entire dataset, depriving the data scientist of insights and performance improvements that could be obtained with a larger dataset.&lt;/p&gt;

&lt;p&gt;In fact, time bottlenecks resulting from long execution times are even more accentuated for batch processing of image data, which are often read as numpy arrays of large dimensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem Description
&lt;/h3&gt;

&lt;p&gt;About 2 months ago, my colleagues and I took part in the Advanced Category for the &lt;a href="https://careers.shopee.sg/ndsc/"&gt;Shopee National Data Science Challenge 2019&lt;/a&gt;. The competition involves extracting product attributes from product title, and we were given three main categories of items: mobile, fashion and beauty products. In addition, we were also given the image path of each item and the associated image file — all 77.6 GB of it!&lt;/p&gt;

&lt;p&gt;It was our first ever competition on Kaggle, and we started out feeling confident with the hybrid text-and-image model we had in mind — only to face bottlenecks in downloading and processing the large image datasets into Numpy arrays in order to feed them as inputs for our model. Here are some of my notes on the approach I attempted to resolve these issues, with particular focus on how I used numba and concurrent.futures to accelerate batch processing of almost a million images from &lt;strong&gt;several months&lt;/strong&gt; to just around &lt;strong&gt;a few days&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Processing Workflow
&lt;/h3&gt;

&lt;p&gt;To start off, here are the &lt;strong&gt;general steps&lt;/strong&gt; in our data processing workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download large image datasets from source using &lt;em&gt;wget&lt;/em&gt; command&lt;/li&gt;
&lt;li&gt;Upload large volume of image files to Google Cloud Storage using &lt;em&gt;gsutil&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Import each image file from Cloud Storage to Colab&lt;/li&gt;
&lt;li&gt;Convert each image to standardized _numpy _array&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Step 1: Using wget command to download large image datasets
&lt;/h4&gt;

&lt;p&gt;The first bottleneck we faced was downloading the image files from the Dropbox links provided by Shopee. Due to data leaks for the fashion category, we had to download the updated CSV files and image files again. One of the archive files containing images for the training set in the fashion category (including the original test set that had its attribute labels leaked) amounted to 35.2 GB, and our multiple attempts throughout the week at using Google Chrome to download the .tar.gz archive files containing the image files failed due to “Network error”.&lt;/p&gt;

&lt;p&gt;This bottleneck was resolved using the &lt;strong&gt;&lt;em&gt;wget&lt;/em&gt; command&lt;/strong&gt; on Ubuntu in Windows 10 WSL (Windows Subsystem for Linux). The best part about using the &lt;em&gt;wget&lt;/em&gt; command for downloading large files is that it works exceedingly well for poor or unstable connections, as &lt;em&gt;wget&lt;/em&gt; will keep retrying until the whole file has been retrieved and is also smart enough to continue the download from where it left off.&lt;/p&gt;

&lt;p&gt;I opened 2 instances of Ubuntu for WSL and ran &lt;em&gt;wget&lt;/em&gt; commands on each instance to download the &lt;em&gt;.tar.gz&lt;/em&gt; archive files containing the images for the three categories. All four archive files were downloaded successfully after 16 hours, surviving poor connection and network errors. Extracting the image files from the archive files using the &lt;strong&gt;tar -xvzf&lt;/strong&gt; command took another 12 hours in total.&lt;/p&gt;

&lt;p&gt;Tip: Working on command line is usually faster than working on GUI — so it pays to know a bit of command line as a speed hack.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Upload image files to Google Cloud Storage using gsutil cp
&lt;/h4&gt;

&lt;p&gt;My team uses Google Colab to share our Jupyter notebooks and train our models. Colab is great for developing deep learning applications using popular frameworks such as TensorFlow and Keras — and it provides free GPU and TPU.&lt;/p&gt;

&lt;p&gt;However, there are some limits that we face while using Colab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory limit:~12 GB RAM available after startup&lt;/li&gt;
&lt;li&gt;Timeout: You are disconnected from your kernel after 90 minutes of inactivity — that means we can’t just take a nap while letting our processes run and waiting for our files to be uploaded (Constant vigilance!).&lt;/li&gt;
&lt;li&gt;Reset runtimes: Kernels are reset after 12 hours of execution time — that means all files and variables will be erased, and we would have to re-upload our files onto Colab. Continuously having to re-upload tens of thousands of image files onto Colab while on constant standby is too tedious and slow.&lt;/li&gt;
&lt;li&gt;Google Drive: Technically, we could mount our Google Drive onto Colab to access the files in Google Drive. As none of us pay for additional storage space on Google Drive, we do not have enough storage space for 77.6 GB of image files. Accessing the files through a portable drive is also not a plausible option, as that would add additional consideration in terms of data connectivity between the desktop/laptop and the portable drive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, we needed a solution whereby we could store and access our files whenever needed, while at the same time paying only for what we use rather than fork out additional money just to pay for more storage on Google Drive.&lt;/p&gt;

&lt;p&gt;In the end, we decided on using &lt;strong&gt;Google Cloud Storage (GCS)&lt;/strong&gt; to store and access our image files. GCS is a RESTful online file storage web service on the Google Cloud Platform (GCP) which allows worldwide storage and retrieval of any amount of data at any time. Google provides 12 months and US$300 of GCP credits as a free tier user, which is perfect for our case since the credits would last for at least a month if we use them wisely.&lt;/p&gt;

&lt;p&gt;First, I created a GCS bucket by using &lt;strong&gt;gsutil mb&lt;/strong&gt; on &lt;strong&gt;Cloud SDK&lt;/strong&gt; (the instructions on installing and setting up Cloud SDK can be found &lt;a href="https://cloud.google.com/sdk/install"&gt;here&lt;/a&gt; and &lt;a href="https://cloud.google.com/sdk/docs/initializing"&gt;here&lt;/a&gt; respectively — I used &lt;em&gt;apt-get&lt;/em&gt; to install Cloud SDK on my Ubuntu image, while Cloud SDK is available in Colab).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Replace 'my-bucket' with your own unique bucket name
! gsutil mb gs://my-bucket
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let’s say I decide to call my storage bucket ‘shopee-cindyandfriends’:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;! gsutil mb gs://shopee-cindyandfriends
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, I proceeded to upload all my image files from each folder directory to my storage bucket using &lt;strong&gt;gsutil cp&lt;/strong&gt;. Since I have a large amount of files to transfer, I performed a parallel copy using the &lt;strong&gt;gsutil -m&lt;/strong&gt; option. The syntax is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Replace 'dir' with directory to copy from
! gsutil -m cp -r dir gs://my-bucket
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let’s say I’m uploading all the image files from the fashion_image directory to my storage bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;! gsutil -m cp -r fashion\_image gs://shopee-cindyandfriends
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, wait patiently and go about your usual day (maybe take a nap or grab some coffee to recharge) while &lt;em&gt;gsutil&lt;/em&gt; uploads your files. Don’t worry too much about poor or unstable connections as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;gsutil&lt;/em&gt; does retry handling — the &lt;em&gt;gsutil cp&lt;/em&gt; command will retry when failures occur.&lt;/li&gt;
&lt;li&gt;If your upload is interrupted or if any failures were not successfully retried at the end of the &lt;em&gt;gsutil cp&lt;/em&gt; run, you can restart the upload by running the same &lt;em&gt;gsutil cp&lt;/em&gt; command that you ran to start the upload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Uploading the image files to the GCS bucket using &lt;em&gt;gsutil cp&lt;/em&gt; command took around 12–15 hours in total, surviving poor connection and network disruptions. Do not try uploading large amounts of files using the GCP web console — your browser will crash!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A0odNfFP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A2vmUe8ClFeJL7dbt6XUU6Q.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A0odNfFP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A2vmUe8ClFeJL7dbt6XUU6Q.jpeg" alt=""&gt;&lt;/a&gt;All 4 folders in our storage bucket — success!&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Import each image file from Cloud Storage to Colab
&lt;/h4&gt;

&lt;p&gt;Now that we have our complete set of image files uploaded on Cloud Storage, we need to be able to access these files on Colab via the image path of each item in the dataset. The image path of each item is extracted from the dataframe which in turn was extracted from the CSV file of the corresponding dataset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def define\_imagepath(index):
 '''Function to define image paths for each index'''
 imagepath = fashion\_train.at[index, 'image\_path']
 return imagepath
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Remember the problem of poor connection? To ensure that retry handling is also performed during import operations from GCP, I used the &lt;strong&gt;retrying&lt;/strong&gt; package as a simplified way to add retry behavior to the Google API Client function. Here’s the Python code I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from retrying import retry
from google.colab import auth

[@retry](http://twitter.com/retry)(wait\_exponential\_multiplier=1000, wait\_exponential\_max=10000)
def gcp\_imageimport(index):
 '''Import image from GCP using image path'''
 from googleapiclient.discovery import build
 # Create the service client.
 gcs\_service = build('storage', 'v1')

from apiclient.http import MediaIoBaseDownload

 colab\_imagepath = '/content/' + define\_imagepath(index)

with open(colab\_imagepath, 'wb') as f:
 request = gcs\_service.objects().get\_media(bucket = bucket\_name, object = define\_imagepath(index))
 media = MediaIoBaseDownload(f, request)

done = False
 while not done:
 \_, done = media.next\_chunk()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Okay, let’s proceed to define our functions for pre-processing the image into numpy arrays.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4: Convert each image to standardized &lt;em&gt;numpy array&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;It is observed that the images in the dataset are of different formats (some are RGB while others are RGBA with an additional alpha channel) and different dimensions. As machine learning models usually require inputs of equal dimensions, pre-processing is required to convert each image in the dataset to a standardized format and resize the images into equal dimensions. Here’s the Python function for RGB conversion, resizing and numpy array conversion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**from**  **PIL**  **import** Image
**def** image\_resize(index):
_'''Convert + resize image'''_  
 im = Image.open(define\_imagepath(index))
 im = im.convert("RGB")
 im\_resized = np.array(im.resize((64,64)))  
**return** im\_resized
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Seems easy to follow so far? Okay, let’s put all the above steps together and attempt to write the processing code for the entire image dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**def** image\_proc(image, start, end): 

 gcp\_imageimport(image)
_#download\_blob('shopee-cindyandfriends', image)_

 im\_resizedreshaped = image\_resize(image)

**if** (image + 1) % 100 == 0 **or** (image == N - 1):
 sys.stdout.write(' **{0:.3f}% c** ompleted. '.format((image - start + 1)\*100.0/(end - start)) + 'CPU Time elapsed: **{}** seconds. '.format(time.clock() - start\_cpu\_time) + 'Wall Time elapsed: **{}** seconds. **\n**'.format(time.time() - start\_wall\_time))
 time.sleep(1)

**return** im\_resized

**def** arraypartition\_calc(start, batch\_size):
 end = start + batch\_size
**if** end \&amp;gt; N:
 end = N
 partition\_list = [image\_proc(image, start, end) **for** image **in** range(start, end)]
**return** partition\_list

_###### Main Code for Preprocessing of Image Dataset ######_
**import** sys
**import** time

N = len(fashion\_train['image\_path'])
start = 0
batch\_size = 1000
partition = int(np.ceil(N/step))
partition\_count = 0

imagearray\_list = [None] \* partition

start\_cpu\_time = time.clock()
start\_wall\_time = time.time()

**while** start \&amp;lt; N:
 end = start + batch\_size
**if** end \&amp;gt; N:
 end = N

imagearray\_list[partition\_count] = [arraypartition\_calc(image) **for** image **in** range(start, end)]

start += batch\_size
 partition\_count += 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For the code sample above, I attempted to process the ~300,000 images in the image dataset sequentially in batches of 1,000 and kept track of progress using a rudimentary output indicator within the image processing function. List comprehension was used to create a new list of numpy arrays for each processing batch.&lt;/p&gt;

&lt;p&gt;After more than 7 hours of leaving the code running on a CPU cluster overnight, barely around 1.1% (~3300) of the images were processed — and that’s just for one dataset. If we were to process almost 1 million images sequentially using this approach, it’ll take &lt;strong&gt;almost 3 months&lt;/strong&gt; to finish processing all the images — and that is practically infeasible! Besides switching to a GPU cluster, are there any other ways to speed up this batch processing code so that we could pre-process the images more efficiently?&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed Up with &lt;em&gt;numba and concurrent.futures&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;In this section, I introduce two Python modules that helps speed up computationally intensive functions such as loops — &lt;em&gt;numba&lt;/em&gt; and &lt;em&gt;concurrent.futures&lt;/em&gt;. I will also document the thought process behind my code implementation.&lt;/p&gt;

&lt;h4&gt;
  
  
  JIT compilation with numba
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Numba&lt;/strong&gt; is a Just-in-Time (JIT) compiler for Python that converts Python functions into machine code at runtime using the LLVM compiler library. It is sponsored by Anaconda Inc., with support by several organizations including Intel, Nvidia and AMD.&lt;/p&gt;

&lt;p&gt;Numba provides the ability to speed up computationally heavy codes (such as for loops — which Python is notoriously slow at) close to the speeds of C/C++ by simply applying a decorator (a wrapper) around a Python function that does numerical computations. You don’t have to change your Python code at all to get the basic speedup which you could get from other similar compilers such as Cython and pypy — which is great if you just want to speed up simple numerical codes without the hassle of manually adding type definitions.&lt;/p&gt;

&lt;p&gt;Here’s the Python function for image conversion and resizing, wrapped with &lt;strong&gt;jit&lt;/strong&gt; to create an efficient, compiled version of the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**from**  **numba**  **import** jit _# JIT processing of numpy arrays_
**from**  **PIL**  **import** Image

@jit
**def** image\_resize(index):
_'''Convert + resize image'''_

 im = Image.open(define\_imagepath(index))
 im = im.convert("RGB")
 im\_resized = np.array(im.resize((64,64)))

**return** im\_resized
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I tried using &lt;em&gt;njit&lt;/em&gt; (the accelerated &lt;strong&gt;no-Python mode&lt;/strong&gt; of JIT compilation) and &lt;em&gt;numba&lt;/em&gt; parallelization (&lt;em&gt;parallel = True)&lt;/em&gt; to achieve the best possible improvement in performance; however, compilation of the above function fails in no Python mode. Hence, I had to fall back to using the &lt;em&gt;jit&lt;/em&gt; decorator, which operates in both no-Python mode and &lt;strong&gt;object mode&lt;/strong&gt; (in which &lt;em&gt;numba&lt;/em&gt; compiles loops that it can compile into functions that run in machine code, while running the rest of the code in the Python interpreter). The reason why &lt;em&gt;njit&lt;/em&gt; fails could be due to &lt;em&gt;numba&lt;/em&gt; being unable to compile PIL code into machine code; nevertheless, &lt;em&gt;numba&lt;/em&gt; is able to compile numpy code within the function and a slight improvement in speed was observed with &lt;em&gt;jit&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Since &lt;em&gt;numba&lt;/em&gt; parallelization can only be used in conjunction with no-Python JIT, I needed to find another way to speed up my code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Parallel processing and concurrent.futures
&lt;/h4&gt;

&lt;p&gt;To understand how to process objects in parallel using Python, it is useful to think intuitively about the concept of parallel processing.&lt;/p&gt;

&lt;p&gt;Imagine that we have to perform the same task of toasting bread slices through a single-slice toaster and our job is to toast 100 slices of bread. If we say that each slice of bread takes 30 seconds to toast, then it takes 3000 seconds (= 50 minutes) for a single toaster to finish toasting all the bread slices. However, if we have 4 toasters, we would divide the pile of bread slices into 4 equal stacks and each toaster will be in charge of toasting one stack of bread slices. With this approach, it will take just 750 seconds (= 12.5 minutes) to finish the same job!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l7H3Yevj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AxdfCfJgR8ttCyXMnnFXhjQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l7H3Yevj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AxdfCfJgR8ttCyXMnnFXhjQ.png" alt=""&gt;&lt;/a&gt;Sequential vs Parallel Processing — illustrated using toasts&lt;/p&gt;

&lt;p&gt;The above logic of parallel processing can also be executed in Python for processing the ~300,000 images in each image dataset:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split the list of .jpg image files into &lt;em&gt;n&lt;/em&gt; smaller groups, where &lt;em&gt;n&lt;/em&gt; is a positive integer.&lt;/li&gt;
&lt;li&gt;Run &lt;em&gt;n&lt;/em&gt; separate instances of the Python interpreter / Colab notebook instances.&lt;/li&gt;
&lt;li&gt;Have each instance process one of the &lt;em&gt;n&lt;/em&gt; smaller groups of data.&lt;/li&gt;
&lt;li&gt;Combine the results from the &lt;em&gt;n&lt;/em&gt; processes to get the final list of results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What is great about executing parallel processing tasks in Python is that there is a high-level API available as part of the standard Python library for launching asynchronous parallel tasks — the &lt;strong&gt;concurrent.futures&lt;/strong&gt; module. All I needed to do is to change the code slightly such that the function that I would like to apply (i.e. the task to be implemented on each image) is mapped to every image in the dataset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_#N = len(beauty\_train['image\_path']) # for final partition_
N = 35000
start = 0
batch\_size = 1000
partition, mod = divmod(N, batch\_size)

**if** mod:
 partition\_index = [i \* batch\_size **for** i **in** range(start // batch\_size, partition + 1)]
**else** :
 partition\_index = [i \* batch\_size **for** i **in** range(start // batch\_size, partition)]

**import**  **sys**
 **import**  **time**
 **from**  **concurrent.futures**  **import** ProcessPoolExecutor

start\_cpu\_time = time.clock()
start\_wall\_time = time.time()

**with** ProcessPoolExecutor() **as** executor:
 future = executor.map(arraypartition\_calc, partition\_index)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;From the above code, this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**with** ProcessPoolExecutor() **as** executor:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;boots up as many processes as the number of cores available on the connected instance (in my case, the number of GPU cores in Colab that are made available during the session).&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;executor.map()&lt;/strong&gt; takes as input:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The function that you would like to run, and&lt;/li&gt;
&lt;li&gt;A list (iterable) where each element of the list is a single input to that function;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;and returns an iterator that yields the results of the function being applied to every element of the list.&lt;/p&gt;

&lt;p&gt;Since Python 3.5, executor.map() also allows us to chop lists into chunks by specifying the (approximate) size of these chunks as a function argument. Since the number of images in each dataset are generally not round numbers (i.e. not multiples of 10s) and the order of the image arrays are important in this case (since I have to map the processed images back to the entries in the corresponding CSV dataset, I manually partitioned the dataset to account for the final partition which contains the tail-end remainder of the dataset.&lt;/p&gt;

&lt;p&gt;To store the pre-processed data into a numpy array for easy “pickling” in Python, I used the following line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;imgarray\_np = np.array([x **for** x **in** future])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The end result was a numpy array containing lists of numpy arrays representing each pre-processed image, with the lists corresponding to the partitions which form the dataset.&lt;/p&gt;

&lt;p&gt;With these changes to my code and switching to the GPU cluster in Colab, I was able to pre-process 35,000 images within 3.6 hours. Coupled with running 4–5 Colab notebooks concurrently and segmenting the entire image dataset into subsets of the dataset, I was able to finish pre-processing (extracting, converting and resizing) almost 1 million images within &lt;strong&gt;20–24 hours&lt;/strong&gt;! Not too bad a speed-up on Colab, considering that we initially expected the image pre-processing to take impracticably long amounts of time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some Reflections and Takeaways
&lt;/h3&gt;

&lt;p&gt;It was our first time taking part in a data science competition — and definitely my first time working on real-life datasets of such a large scale compared with the clean-and-curated datasets that I worked with for my academic assignments. There might be plenty of spotlight on state-of-the-art data science algorithms within a data science project; however, I’ve also learnt through the experience that data processing can also often make or break a data science project if it becomes a bottleneck in terms of processing time.&lt;/p&gt;

&lt;p&gt;On hindsight, I could have created a virtual machine on Google Cloud Platform and run the codes there, instead of relying solely on Colab and having to keep track of code execution in case I hit the 12-hour time limit for the GPU runtime.&lt;/p&gt;

&lt;p&gt;In conclusion, here are my takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Command line interface is typically faster than GUI — if you can, try to work on the command line as much as possible.&lt;/li&gt;
&lt;li&gt;Facing poor connection issues? Retry handling can help save you the heartache from having to re-upload or re-download your files.&lt;/li&gt;
&lt;li&gt;Numba and concurrent.futures are useful when you are looking for a hassle-free way to speed up pre-processing of large datasets without manually adding type definitions or delving into the details of parallel processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For reference, the codes accompanying this write-up can be found &lt;a href="https://github.com/hweecat/numba-image-processing/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  References:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/storage/docs/gsutil"&gt;Official Google Cloud Storage documentation on gsutil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://colab.research.google.com/notebooks/io.ipynb"&gt;Colab Notebook with recipes for external data handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://numba.pydata.org/numba-doc/dev/user/5minguide.html"&gt;A ~5 minute guide to Numba by Anaconda Inc.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/concurrent.futures.html"&gt;Official Python documentation on concurrent.futures&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>datascience</category>
      <category>programming</category>
      <category>parallelprocessing</category>
      <category>pythonprogramming</category>
    </item>
  </channel>
</rss>
