Important notice about this site
I graduated from the University of Texas at Austin in December 2020. This blog is archived and no updates will be made to it.
Welcome to my blog!
Here, I post on topics relevant to my time here at the university, general topics on computer science and business administration, and anything interesting that I learn as a student and lifelong learner.
Wish to reach out to me about my blog posts? I'm open to questions, comments, and suggestions. Feel free to contact me.
July 31, 2019
Aliases are a wonderful feature of most shells (fancy name for terminal environments) that you can make use of to make using the terminal a much easier experience.
The most common shell in use today is Bash shell. However, another popular shell with features added to Bash is Zsh (z shell, also pronounced "zosh"). Whether you use Bash or Zsh, setting an alias works the same way, except you'll want to put aliases in ~/.bashrc
or ~/.zshrc
respectively.
For example, here's how I define aliases in my ~/.zshrc
file:
alias kc="kubectl"
alias g="git"
alias gpl="git pull"
alias gac="git add . && git commit -m"
alias gaa="git add ."
alias gcm="git commit -m"
alias gps="git push"
alias gco="git checkout"
alias d="docker"
alias dbd="docker build ."
alias drd="docker run -e ABC=def -p 1234:1234 -it "
alias dcl="docker container ls"
You'd use them as if they were a real shell command. (Well, they technically are, but you made them!) For instance, if you used to do this:
$ git add .
$ git commit -m "Some commit message"
you can do this instead:
$ gac "Some commit message"
and this saves so much time!
These aliases come in handy whenever I use some common commands that are long and tedious to type out each time.
If you want to make a function, or a "compound alias" as I like to think of it, you can define it in a familiar notation as C functions. For instance, these two functions do multiple things at once:
copyb64e() {
pbpaste | base64 | pbcopy
}
copyb64d() {
pbpaste | base64 --decode | pbcopy
}
You can also take in positional arguments into your new functions and refer to the arguments by the order in which they are passed in.
printb64e() {
echo $1 | base64
echo "$1 is now $($1 | base64)"
}
printb64d() {
echo $1 | base64 --decode
echo "$1 is now $($1 | base64 --decode)"
}
Final tip: once you modify your ~/.bashrc
or ~/.zshrc
file, you'll want to refresh your shell by doing source ~/.bashrc
or source ~/.zshrc
respectively.
In summary, shell aliases are really convenient for making your terminal experience more enjoyable.
June 13, 2019
Instead of using a constructor, one way to set up an object in Java is by using a builder. Since Java does not have keyword arguments like Python does, having a large amount of positional arguments in constructors might be confusing and makes code difficult to understand. Furthermore, constructors only take in one combination of data. On the other hand, builders can be created to take in a wide variety of types of data.
The premise of a builder involves creating private classes within a class for which you wish to build an object. This nested private class acts as the builder class. Each builder method returns the current builder object, modifying the state by one parameter and passing it on for the next builder method to use. Once everything's finished being built, a final method will return the actual finished object instead of another builder object.
Below is an example:
class BodyFeatures {
private String hairColor, eyeColor;
private boolean leftHanded;
public BodyFeatures(String hairColor, String eyeColor) {
this.hairColor = hairColor;
this.eyeColor = eyeColor;
}
public static Builder buildBodyFeatures() {
return new Builder();
}
private class Builder {
private String hairColor, eyeColor;
private Builder() {}
private Builder hairColor(String hairColor) {
this.hairColor = hairColor;
return this;
}
private Builder eyeColor(String eyeColor) {
this.eyeColor = eyeColor;
return this;
}
private BodyFeatures build() {
return new BodyFeatures(hairColor, eyeColor);
}
}
}
Then, in code, it could be used this way:
BodyFeatures bf = BodyFeatures.buildBodyFeatures()
.hairColor("black")
.eyeColor("blue")
.build();
This seems like a lot of work to implement, so I don't recommend using a builder unless the builder significantly reduces setup work. Using a builder is probably best if you're able to automatically generate this code programmatically.
June 7, 2019
Recently, I've been trying to get acclimated with functional programming practices, especially lambda expressions. I'm still scared of diving directly into a purely functional language like Haskell, so I've decided to take it easy and deal with tamer, more common languages where lambda expressions (or anonymous functions) have been introduced. In this post, I'll talk about how we can use streams and lambda expressions to work with collections in a functional paradigm instead of a procedural paradigm. This train of thought will allow for a different way to process data that allows for asynchronous programming in Java, which is helpful for when callbacks or futures are needed.
Many web developers use anonymous functions in JavaScript, sometimes without knowing they're using a functional programming practice. In my case, I first learned how to use anonymous functions when dealing with asynchronous functions and callbacks in JavaScript a few years ago, but I did not realize this was a functional programming practice.
Recently, I've realized that one way to simplify code is to move away from a procedural paradigm and start borrowing elements of functional programming. The first way to do so in a non-functional programming language is through lambda expressions. At the same time, I've now found the need to learn how to use lambdas in Java 8 and beyond because I'm dealing with a Java library that uses callbacks, so it makes life easier to use lambdas throughout the codebase.
First, what are lambda expressions? They are anonymous functions that take in parameters, do something, and potentially return something. Whenever you're able to write a one-line method in Java with a return statement and something being done in the return statement, this is probably a perfect place to use a lambda expression.
Lambda expressions can implement methods in interfaces. In other words, the expressions define what ought to be done whenever the expression is actually being used instead of before it's called.
Let's say we have an interface with a method header:
interface Greeting {
public String hello(String fullName);
}
Instead of creating a whole other class, let's just implement this method hello
on the fly, specifically for each instance of Greeting
. So, what hello()
does for Greeting g1
might be different from how hello()
is implemented in Greeting g2
. Also, since there's only one method in this interface, we can call it a functional interface, and this gives it a special property: whenever we define the lambda expression for each Greeting
object, we won't have to specify it's for the hello()
method, we just specify it anonymously. Cool!
The on-the-fly implementation will be used only once, so it doesn't have to be named. Hence, it's an anonymous function. Here's an example of an implementation of hello()
using a lambda expression:
class DoSomething {
public static void main(String[] args) {
Greeting greeting1 = (name) -> "Hello, " + name; // creating a new Greeting object here,
// and we're just defining hello() inline
Greeting greeting2 = (name) -> "Yo, 'sup " + name + "?"; // another implementation of hello() implemented through a lambda expression
String name1 = "John";
System.out.println(greeting1.hello(name1)); // passes in 'John' into the anonymously defined lambda expression in the greeting1 object.
// this will print out "Hello, John"
System.out.println(greeting2.hello(name1)); // passes in 'John' into the anonymously defined lambda expression in the greeting2 object.
// because hello() was defined differently for greeting2, this will print out "Yo, 'sup John?"
}
}
But let's say after each greeting, we want to add on more things to it. The first greeting might say "Hello, John" but we might want to have a function that adds ", how are you?" to the end of that. This chaining is called currying. We'll use this concept to deal with processing things step by step.
So now, let's say we have a lot of people to say hello to. We'll store their names in a list. Great. Instead of using a for
loop to go through this bit by bit, let's use streams and process to each member. This allows us to think about how we want to handle each member stage by stage instead of worrying about loops and getting to each member. We can do this through the Stream
interface introduced in Java 8.
class DoSomething {
public static void main(String[] args) {
String[] names = new String[]{"John", "Mary", "Todd", "Stephen", "Cardi B"};
Arrays.stream(names)
.map(str -> "Hello, " + str + "!")
.forEach(System.out::println); // equivalent to `str -> System.out.println(str)`
// we can use this cool syntax because there's only one parameter to System.out.println and it can be inferred that we want to pass in the Stream's string into it.
}
}
The output from this is:
Hello, John!
Hello, Mary!
Hello, Todd!
Hello, Stephen!
Hello, Cardi B!
By using a stream, we only have to worry about how to handle each individual element. The coordination work of handling everything as a collection is passed onto the Stream
interface.
Cooler things we can do with a stream include:
.sort()
(anything that's a Comparable
or sorted by a Comparator
).filter()
(providing a predicate).reduce()
and .collect()
(using a start and accumulator model)Let's say only people whose names have "o" in it can get a shoutout. Then we could use filter
in our stream.
class DoSomething {
public static void main(String[] args) {
String[] names = new String[]{"John", "Mary", "Todd", "Stephen", "Cardi B"};
Arrays.stream(names)
.filter(name -> name.contains("o"))
.map(str -> "Hello, " + str + "!")
.forEach(System.out::println);
}
}
This results in:
Hello, John!
Hello, Todd!
There are many more cool methods available in the Stream
interface that I recommend you check out and see if it will be appropriate for your needs. But more importantly, thinking in this paradigm is really interesting, helps improve code concision (especially in verbose languages like Java), and gives you another way to approach problems that might even be easier than tackling it in a procedural manner (especially with asynchronous functions).
May 21, 2019
Programmers have long said that writing while(1)
can be replaced by the more compact form for(;;)
. However, I have always wondered: apart from syntax, are there any differences under the hood in how they function? If so, we should know about it so we use the more efficient form.
To answer this question, we need to look under the hood. Whether they are the same boils down to CPU-level instructions. Each CPU runs on a set of possible instructions that it can do, and together, they form the CPU architecture. Instructions are the basic building blocks of every program; ultimately, everything is compiled down to machine instructions to be run. When an instruction is run, it completes a single action that the architecture has defined. Ultimately, a programming language will translate actions down into instructions to be run on the CPU, whether through an intermediate programming language or through directly compiling down to machine code. Therefore, we can judge if while(1)
and for(;;)
are functionally the same if their machine code matches.
Instead of dealing with a higher level language like Java or Python, I'm choosing C because it compiles to object code, which can be disassembled to get the assembly instructions. If the assembly instructions match, then while(1)
and for(;;)
are functionally equivalent. I wrote two functions in C that are nearly identical except one uses while(1)
and the other uses for(;;)
:
void func1(int x) {
while (1) {
x++;
}
}
void func2(int x) {
for (;;) {
x++;
}
}
I put these functions in their own files, func1.c
and func2.c
. In each file, I call the function afunc
to maintain consistency.
For this example, I will use AArch64 (the architecture of ARMv8 and beyond) assembly instructions to demonstrate.
On the command line, I do the following to compile these functions to object code and then dump it into a disassembled assembly file:
$ gcc -c func1.c -O0
$ gcc -c func2.c -O0
$ objdump -D func1.o > func1.disas
$ objdump -D func2.o > func2.disas
$ diff func1.disas func2.disas
The -c
flag tells GCC to only compile down to the object code (.o
file) while the -O0
flag tells GCC to not optimize the instructions that it performs.
It turns out that the diff proves that func1
and func2
are completely the same. Here they are:
func1.disas
:
func1.o: file format elf64-littleaarch64
Disassembly of section .text:
0000000000000000 <afunc>:
0: d10043ff sub sp, sp, #0x10
4: b9000fe0 str w0, [sp,#12]
8: b9400fe0 ldr w0, [sp,#12]
c: 11000400 add w0, w0, #0x1
10: b9000fe0 str w0, [sp,#12]
14: 17fffffd b 8 <afunc+0x8>
Disassembly of section .comment:
0000000000000000 <.comment>:
0: 43434700 .inst 0x43434700 ; undefined
4: 4c28203a .inst 0x4c28203a ; undefined
8: 72616e69 .inst 0x72616e69 ; undefined
c: 4347206f .inst 0x4347206f ; undefined
10: 2e352043 usubl v3.8h, v2.8b, v21.8b
14: 30322d35 adr x21, 645b9 <afunc+0x645b9>
18: 312e3731 adds w17, w25, #0xb8d
1c: 35202930 cbnz w16, 40540 <afunc+0x40540>
20: 302e352e adr x14, 5c6c5 <afunc+0x5c6c5>
...
func2.disas
:
func2.o: file format elf64-littleaarch64
Disassembly of section .text:
0000000000000000 <afunc>:
0: d10043ff sub sp, sp, #0x10
4: b9000fe0 str w0, [sp,#12]
8: b9400fe0 ldr w0, [sp,#12]
c: 11000400 add w0, w0, #0x1
10: b9000fe0 str w0, [sp,#12]
14: 17fffffd b 8 <afunc+0x8>
Disassembly of section .comment:
0000000000000000 <.comment>:
0: 43434700 .inst 0x43434700 ; undefined
4: 4c28203a .inst 0x4c28203a ; undefined
8: 72616e69 .inst 0x72616e69 ; undefined
c: 4347206f .inst 0x4347206f ; undefined
10: 2e352043 usubl v3.8h, v2.8b, v21.8b
14: 30322d35 adr x21, 645b9 <afunc+0x645b9>
18: 312e3731 adds w17, w25, #0xb8d
1c: 35202930 cbnz w16, 40540 <afunc+0x40540>
20: 302e352e adr x14, 5c6c5 <afunc+0x5c6c5>
...
Because the results are the exact same, this proves that while(1)
and for(;;)
are actually functionally equivalent when compiled for this specific case. Of course, by some anomaly, maybe they will be different, but that is highly unlikely with the GNU C Compiler, whether for AArch64 or x86-64.
I also tried this with -O3
optimization and instead of doing sub
, str
, ldr
, add
, str
, and finally b
, it skipped all of that and simply had a lone b
instruction that referred to itself. Unfortunately, the processor does not raise an exception when the program counter is the same as the argument of b
, which I found a little concerning. (The program counter is a special-purpose register inside the CPU that contains the memory address of the instruction that the CPU is currently executing. b
is an instruction that branches—or in x86 terms, jumps—to the address specified.)
This neat little technique was first shared by my Computer Architecture professor. It's a great way to ensure that programming hacks are actually good hacks below the surface, not just what appears to the programmer.
May 15, 2019
Did you know you can run Python scripts on your UTCS website? Getting Python scripts to run as a webpage is as simple as creating a file with the .cgi
extension, putting a Python shebang, and providing the proper HTTP headers.
For instance, I wrote a random CGI script in Python which is available on my website. It is nothing special, but it takes these two tricks to get it to work properly:
#!/usr/bin/python
print "Content-Type: text/html\n\n"
The first line specifies that I wish to use Python to execute this CGI script (as I very well could've put Perl in this file). The second line makes the Content-Type
header known. These two lines are all that's necessary for the script to begin running. After that, you can do whatever you want in your Python file. Hope this helps!
Thanks to Garrett Gu for discovering the Content-Type
header requirement. Without sending the Content-Type
header, things won't work correctly.
For those curious, here are the contents of apgoodluck.cgi
:
#!/usr/bin/python
print "Content-Type: text/html\n\n"
exams = [
["AP United States Government and Politics", "AP Chinese Language and Culture", "AP Environmental Science"],
["AP Seminar", "AP Spanish Language and Culture", "AP Japanese Language and Culture", "AP Physics 1: Algebra-Based"],
["AP English Literature and Composition", "AP European History", "AP French Language and Culture"],
["AP Chemistry", "AP Spanish Literature and Culture", "AP German Language and Culture", "AP Psychology"],
["AP United States History", "AP Computer Science Principles", "AP Physics 2: Algebra-Based"],
["AP Biology", "AP Physics C: Mechanics", "AP Physics C: Electricity and Magnetism"],
["AP Calculus AB", "AP Calculus BC", "AP Art History", "AP Human Geography"],
["AP English Language and Composition", "AP Italian Language and Culture", "AP Macroeconomics"],
["AP Comparative Government and Politics", "AP World History", "AP Statistics"],
["AP Microeconomics", "AP Music Theory", "AP Computer Science A", "AP Latin"]
]
for x in range(0, len(exams)):
pout = ""
for y in exams[x]:
pout += y + " and/or "
pout = pout[:len(pout)-8]
print "Week " + str((x // 5) + 1) + ", Day " + str((x % 5) + 1) + "<br />"
print "Good luck to everyone who is taking " + pout + "!<br />"
Update (May 19, 2019): For more documentation on using Python CGI scripts, see this page for Python 2 and this page for Python 3.