Engineering stack

Most startups think of the engineering stack as if it is a single cohesive thing. However, I believe that there are three different engineering stacks that are loosely coupled to each other.

Read More

Spicy vs Chilli

When Americans say spicy, they usually mean chili. For some weird reason, other spices that are more aromatic like Turmeric, cloves, cardamom, etc. do not seem to fit the definitely of “spicy”. That’s why in a typical Indian/Thai restaurant in the US when they ask how spicy, they mean “How much chili should I put in”

However, when an Indian says that the spices were good, s/he usually means that the food had an aroma and a balanced composition of contrasting tastes.

Sometimes the middle ground is most empowering

Writing a blog post in plain text isn’t fun. Neither is writing it in a bulky word processor like Microsoft Word. The former is too constrained, the latter too unbridled.  Writing in Markdown is fun. It has sufficient features without creating bloat.

Building a backend in lambda functions has a strong lock-in. On the other hand, deploying web services on Virtual Machines (or physical machines!), while having little lock-in, is complicated. Docker, however, treads the middle path. Sufficiently robust without pulling in the complications of maintaining a full-fledged virtual/physical machine.

Sometimes the middle ground is the most empowering one. It trades off some power in lieu of reducing complexity.

 

Generics in Go

Generics in Go were added about a year back in Go 1.18. In my experience they are great and they fix one of the biggest roadblocks in terms of writing reusable code in Go. I’ll illustrate that with an example.

First, do ensure that you have at least Go 1.18

$ go version
go version go1.19.1 darwin/amd64

Let’s consider a simple case of implementing a Map function in Go that maps an array (well, a slice) to another array (again, actually, a slice).

First, let’s start with a non-generic implementation

package main

import (
  "fmt"
)

func Map(input []int, mapFunc func(int)int) []int {
  output := make([]int, 0, len(input))
  for _, val := range input {
    output = append(output, mapFunc(val))
  }
  return output
}

func main() {
  input := []int {1,2,3}
  squared := func(x int) int {
    return x * x
  }
  output := Map(input, squared)
  fmt.Printf("%+v\n", output)
}

And now let’s generalize the Map function, notice that, in most cases, the caller wouldn’t change at all.

// A is the input type
// B is the output type
func Map[A any,B any](input []A, mapFunc func(A)B) []B {
  output := make([]B, 0, len(input))
  for _, val := range input {
    output = append(output, mapFunc(val))
  }
  return output
}