Interpreter - Overview

An interpreter is a program that reads and executes code. It is a way to execute code without compiling it. It is a great way to execute code on the fly. It is used in many places like scripting languages, command line tools, etc. Parts of an Interpreter ( Simplified ) A minimal interpreter can be represented in 3 parts: lexer: It reads the code and converts it into tokens. parser: It reads the tokens and converts it into an abstract syntax tree (AST). evaluator: It reads the AST and executes the code. Note: There can be more parts like optimizer, IR generator etc. for advanced interpreters. ...

Grammar For A Simple Language

Grammer in computer science is a set of rules that defines the structure of a language. It defines a language in a structured way and parsers are built based on it. BNF Backus-Naur Form, or BNF, is a notation technique used to express context-free grammars. It is used to define syntax of programming languages, datastructures, etc. EBNF Extended Backus-Naur Form, or EBNF, is a notation technique used to express context-free grammars. It is an extension of BNF. ...

Scala - Overview

Scala is a jvm based programming language. Scala is widely used in big data processing, web applications, and microservices for its scalability and performance. The code is compiled to Java bytecode and can be executed on the JVM. Scala is known for its strong static type system, concise syntax, and functional programming features. Few things I like about Scala are The expressive syntax and powerful type system. The functional programming features like pattern matching, higher-order functions, and immutability. The seamless interoperability with Java, which allows us to use existing Java libraries and frameworks. Few things I dislike about Scala are ...

Caching Patterns

Caching is one of the critical solution to improve performance and reduce latency in data access. Cache is a fast and temporary storage ( generally RAM ) When using cache there are different patterns in which we can implement reading the cache data and writing the cache data. Cache Aside Ideal where caching everything is not critical for usecase nor necessary. Application and Datastore control the data, and cache is an optimization sidecar. ...

Go - Factory Pattern

In golang, we use interfaces to define a contract for a particular type. Factory pattern uses this interface to create instance / object of a particular type like Storage, Service, Document etc. Implementing Factory pattern Define an interface with a method to create the object. package factory type Document interface { Render() string } Create different types which implement the interface. package factory type PDF struct { data string } func (p *PDF) Render() string { return "PDF: " + p.data } type Word struct { data string } func (w *Word) Render() string { return "Word: " + w.data } Create a factory function which returns the object based on some condition. package factory func NewDocument(docType string, data string) Document { switch docType { case "pdf": return &PDF{data: data} case "word": return &Word{data: data} default: return nil } } Example usage func main() { pdfDoc := DocumentFactory("pdf") wordDoc := DocumentFactory("word") pdfDoc.Render() wordDoc.Render() } This pattern is useful when we have different implementations of similar Interfaces like postgres storage, redis storage etc.. and It also helps to switch between these implementation if they’re interchangable. ...

Go - Builder Pattern

Builder is a common pattern to create or initialize a particular object. It helps to break down individual steps of the creation by providing functions we can use to set different properties and then usually with a Build method, which uses these properties to create the object. Implementing Builder pattern Define a struct with all the properties of the object. package builder type User struct { UID string Name string Email string Password string } Create methods to set the properties of the object. // creates a new UID for the user func (u *User) createUid() *User { u.UID = generateUID() return u } // sets the name and email of the user func (u *User) setNameEmail(name, email string) *User { u.Name = name u.Email = email return u } // sets the password of the user func (u *User) setPassword(password string) *User { u.Password = hashPassword(password) return u } Create a Build method to create the object. // Build creates a new user func (u *User) Build() *User { return u } Example usage package main import ( "builder-pattern/builder" "fmt" ) func main() { user := &builder.User{} user.createUid().setNameEmail("Adharsh", "dev@adharsh.in").setPassword("password").Build() fmt.Println(user) } The key is to make the methods return the pointer to the object so that we can chain the methods together. This is a common pattern in Go and is used in many places. ...

Go - Concurrency Pipeline

Pipeline is yet another common pattern we see in concurrent programming. Stages for a data processing pipline can be for example, Read the file Process the data Transform the data Write the data to another file Let’s check an example of such a pipeline in Golang. package pipeline import ( "fmt" "time" ) type FileData struct { src string data string } // readData stage where we read the file // it returns a channel of FileData to use in the next stage func readData(filePaths []string) <-chan FileData { out := make(chan FileData) go func() { for _, path := range filePaths { time.Sleep(1 * time.Second) // Simulate file read out <- FileData{src: path, data: "data"} } close(out) }() return out } // processData stage where we process the data // it returns a channel of processed FileData to use in the next stage func processData(in <-chan FileData) <-chan FileData { out := make(chan FileData) go func() { for data := range in { time.Sleep(1 * time.Second) // Simulate data processing out <- FileData{src: data.src, data: data.data + " processed"} } close(out) }() return out } // transformData stage where we transform the data // it returns a channel of transformed FileData to use in the next stage func transformData(in <-chan FileData) <-chan FileData { out := make(chan FileData) go func() { for data := range in { time.Sleep(1 * time.Second) // Simulate data transformation out <- FileData{src: data.src, data: data.data + " transformed"} } close(out) }() return out } // writeData stage where we write the data to a file func writeData(in <-chan FileData) bool { for data := range in { time.Sleep(1 * time.Second) // Simulate file write fmt.Printf("Writing data to %s\n", data.src) } return true } // RunPipeline runs the pipeline // each stage is a go routine // and they are connected via channels func RunPipeline(filePaths []string) { readDataCh := readData(filePaths) processDataCh := processData(readDataCh) transformDataCh := transformData(processDataCh) writeData(transformDataCh) } In the above example, we have implemented a pipeline with 4 stages. Each stage is a go routine and they are connected via channels. ...

Go - Concurrency Workerpool

Golang has excellent support for concurrency. Let’s see how to implement a common pattern - worker pool in Golang. Worker pool A worker pool is a collection of threads that are waiting for tasks to be assigned. We can limit the number of concurrent operations at a time with this approach. Usually starting a new concurrent thread or routine is not practical for every task especially when the number of tasks is large. ...

gRPC - Overview

gRPC is a very useful tool to use as a medium for communication or data transfer between services. It has great support for multiple languages and platforms. What is RPC? Remote Procedure Call (RPC) is the concept of calling a function on a program from another program, these programs can be running on different machines. REST API is an example of RPC where a client service sends an http request to a server to invoke some logic and get the response. ...

Interfaces

Interfaces are one of my favorite concepts in programming. It’s a clean way to define contract and decouple the implementation parts from code. Interfaces indicate what a class or struct should do, or define the type of methods which a class or struct should implement. We can refer an interface and understand how the different parts used by a code block should behave. I mostly use it to define segments of code, like http handler, service, storage to indicate that a segment is going to recieve some implementation, which will have certain methods which can be used in them and worry about the implementation later. ...