Friday, 28 January 2022

Web Tutorial: The Lucky Number Generator, redux (Part 2/2)

Time to draw a tiger!

We begin this process by changing the code here to reflect the year. This is 2022, which means a tiger will be drawn.

tt_cny2022.html
<body onload="cny(2022)">


In the JavaScript file, we create an If block for the tiger in the createAnimal() function.

cny.js
if (animalName == "tiger")
{

}


Start off with the face. It's a variable named - you guesse it - face. It's a div that is 80 by 70 pixels, round corners and an orange background. We make sure it floats right because we want it flush to the right of the screen. This div has no border except for the bottom, which is a 5 pixel thick white line. Since we have rounded corners, the effect is going to be nice, like a beard. Lastly, we make sure the cursor property is set to pointer.

cny.js
if (animalName == "tiger")
{
    var face = document.createElement("div");
    face.style.width = "80px";
    face.style.height = "70px";
    face.style.borderRadius = "50%";
    face.style.position = "relative";
    face.style.backgroundColor = "rgba(255, 100, 0, 1)";
    face.style.borderBottom = "5px solid rgba(255, 255, 255, 1)";
    face.style.float = "right";
    face.style.cursor = "pointer";

}


Return face, and that's it for now.

cny.js
if (animalName == "tiger")
{
    var face = document.createElement("div");
    face.style.width = "80px";
    face.style.height = "70px";
    face.style.borderRadius = "50%";
    face.style.position = "relative";
    face.style.backgroundColor = "rgba(255, 100, 0, 1)";
    face.style.borderBottom = "5px solid rgba(255, 255, 255, 1)";
    face.style.float = "right";
    face.style.cursor = "pointer";

    return face;
}


You see what we're doing here?


This tiger needs some ears. We are going to have a container div for these, in a variable called ears. We make it fit 100% of its parent, which will be face. The height will be 35 pixels. At the end, before returning face, we append ears to face.

cny.js
    face.style.cursor = "pointer";

    var ears = document.createElement("div");
    ears.style.width = "100%";
    ears.style.height = "35px";
    ears.style.position = "relative";

    face.appendChild(ears);


    return face;
}


Now we declare leftEar and rightEar. They're both oval orange divs, except that one is floated left, and one right. Both are appended into ears.

cny.js
    face.style.cursor = "pointer";

    var ears = document.createElement("div");
    ears.style.width = "100%";
    ears.style.height = "35px";
    ears.style.position = "relative";

    var leftEar = document.createElement("div");
    leftEar.style.width = "15px";
    leftEar.style.height = "35px";
    leftEar.style.float = "left";
    leftEar.style.backgroundColor = "rgba(255, 100, 0, 1)";
    leftEar.style.borderRadius = "50%";

    var rightEar = document.createElement("div");
    rightEar.style.width = "15px";
    rightEar.style.height = "35px";
    rightEar.style.float = "right";
    rightEar.style.backgroundColor = "rgba(255, 100, 0, 1)";
    rightEar.style.borderRadius = "50%";

    ears.appendChild(leftEar);    
    ears.appendChild(rightEar);


    face.appendChild(ears);
    
    return face;
}


Those are some adorable ears!


This tiger needs stripes. For this, we are going to do pretty much what we did for the ears - create a container div and fill it with content, except we'll have two divs. These divs are stripes and stripes2.

cny.js
    ears.appendChild(leftEar);    
    ears.appendChild(rightEar);

    var stripes = document.createElement("div");

    var stripes2 = document.createElement("div");


    face.appendChild(ears);
    
    return face;
}    


The height and width of stripes will fill the entirety of its parent, which will be face. It is round, floated left, and the color property has been set to black. More importantly, we set the overflow property to hidden because the text that we're putting into, three pipe symbols, may get out of the div. For this, the position property will also be set to relative. Text and font properties and the top margin are really up to you, but I would stick with what I've done here because it's all been verified through trial and error.

cny.js
    ears.appendChild(leftEar);    
    ears.appendChild(rightEar);

    var stripes = document.createElement("div");
    stripes.style.width = "100%";
    stripes.style.height = "100%";
    stripes.style.borderRadius = "50%";
    stripes.style.position = "relative";
    stripes.style.color = "rgba(0, 0, 0, 1)";
    stripes.style.float = "left";
    stripes.style.overflow = "hidden";
    stripes.style.marginTop = "-35px";
    stripes.style.fontSize = "2em";
    stripes.style.fontWeight = "bold";
    stripes.style.lineHeight = "0.5em";
    stripes.style.textAlign = "center";
    stripes.innerHTML = "|||";


    var stripes2 = document.createElement("div");

    face.appendChild(ears);
    
    return face;
}    


Append stripes to face.

cny.js
    var stripes = document.createElement("div");
    stripes.style.width = "100%";
    stripes.style.height = "100%";
    stripes.style.borderRadius = "50%";
    stripes.style.position = "relative";
    stripes.style.color = "rgba(0, 0, 0, 1)";
    stripes.style.float = "left";
    stripes.style.overflow = "hidden";
    stripes.style.marginTop = "-35px";
    stripes.style.fontSize = "2em";
    stripes.style.fontWeight = "bold";
    stripes.style.lineHeight = "0.5em";
    stripes.style.textAlign = "center";
    stripes.innerHTML = "|||";

    var stripes2 = document.createElement("div");

    face.appendChild(ears);
    face.appendChild(stripes);
    
    return face;
}


Nice, right?! There's more...


This is pretty much more of the same for stripes2, but we use a series of equal signs as text content.

cny.js
    stripes.style.textAlign = "center";
    stripes.innerHTML = "|||";

    var stripes2 = document.createElement("div");
    stripes2.style.width = "100%";
    stripes2.style.height = "100%";
    stripes2.style.borderRadius = "50%";
    stripes2.style.position = "relative";
    stripes2.style.color = "rgba(0, 0, 0, 1)";
    stripes2.style.float = "left";
    stripes2.style.overflow = "hidden";
    stripes2.style.marginTop = "-70px";
    stripes2.style.fontSize = "2em";
    stripes2.style.fontWeight = "bold";
    stripes2.style.lineHeight = "0.5em";
    stripes2.style.textAlign = "center";
    stripes2.innerHTML = "<br /><br />===<br />===";


    face.appendChild(ears);
    face.appendChild(stripes);
    face.appendChild(stripes2);
    
    return face;
}


Horizontal stripes!


Now for the eyes! This will be a container div, called eyes. In this, we will have divs, leftEye and rightEye. They're basically elliptical divs with a little bit of rotation thrown in.

cny.js
    stripes2.style.textAlign = "center";
    stripes2.innerHTML = "<br /><br />===<br />===";

    var eyes = document.createElement("div");
    eyes.style.width = "80px";
    eyes.style.height = "20px";
    eyes.style.float = "left";
    eyes.style.margin = "-45px auto 0 auto";
    eyes.style.fontSize = "15px";
    eyes.style.fontWeight = "bold";
    eyes.style.color = "rgba(0, 0, 0, 1)";

    var leftEye = document.createElement("div");
    leftEye.style.width = "20px";
    leftEye.style.height = "5px";
    leftEye.style.float = "left";
    leftEye.style.border = "3px solid rgba(0, 0, 0, 1)";
    leftEye.style.borderRadius = "50%";
    leftEye.style.backgroundColor = "rgba(255, 100, 0, 1)";
    leftEye.style.transform = "rotate(20deg)";
    leftEye.style.marginLeft = "10px";

    var rightEye = document.createElement("div");
    rightEye.style.width = "20px";
    rightEye.style.height = "5px";
    rightEye.style.float = "right";
    rightEye.style.border = "3px solid rgba(0, 0, 0, 1)";
    rightEye.style.borderRadius = "50%";
    rightEye.style.backgroundColor = "rgba(255, 100, 0, 1)";
    rightEye.style.transform = "rotate(-20deg)";
    rightEye.style.marginRight = "10px";

    eyes.appendChild(leftEye);    
    eyes.appendChild(rightEye);    


    face.appendChild(ears);
    face.appendChild(stripes);
    face.appendChild(stripes2);
    face.appendChild(eyes);

    return face;
}    


Those are some big eyes.


Next up is the snout. It's a circular orange area that will block off some of the stripes. Within it, there will be a black letter "Y" to represent the nose.

cny.js
    eyes.appendChild(leftEye);    
    eyes.appendChild(rightEye);    

    var snout = document.createElement("div");
    snout.style.width = "50px";
    snout.style.height = "40px";
    snout.style.borderRadius = "50%";
    snout.style.position = "relative";
    snout.style.float = "left";
    snout.style.margin = "-35px 0 0 15px";
    snout.style.fontSize = "15px";
    snout.style.textAlign = "center";
    snout.style.fontWeight = "bold";
    snout.style.backgroundColor = "rgba(255, 100, 0, 1)";
    snout.style.borderBottom = "5px solid rgba(255, 255, 255, 1)";
    snout.style.color = "rgba(0, 0, 0, 1)";    
    snout.innerHTML = "Y";


    face.appendChild(ears);
    face.appendChild(stripes);
    face.appendChild(stripes2);
    face.appendChild(eyes);
    face.appendChild(snout);

    return face;
}        


Definitely taking shape now.


Finally, we give the tiger a mouth. It will be superimposed on top of snout by way of the margin property. Here, I have made it a white div with black text. The "teeth" are actually a series of "V"s. Note that the letter spacing and line height are specified so as to pack them close together and make them look like a row of teeth. Also, the transition property is specified and we give it a class name of cny_mouth because we will be animating this.

cny.js
    snout.style.color = "rgba(0, 0, 0, 1)";    
    snout.innerHTML = "Y";

    var mouth = document.createElement("div");
    mouth.className = "cny_mouth";
    mouth.style.width = "40px";
    mouth.style.height = "10px";
    mouth.style.borderRadius = "5px";
    mouth.style.position = "relative";
    mouth.style.float = "left";
    mouth.style.overflow = "hidden";
    mouth.style.margin = "-20px 0 0 20px";
    mouth.style.fontSize = "15px";
    mouth.style.textAlign = "center";
    mouth.style.backgroundColor = "rgba(255, 255, 255, 1)";
    mouth.style.color = "rgba(0, 0, 0, 1)";    
    mouth.style.lineHeight = "0.7em";
    mouth.style.letterSpacing = "-0.05em";
    mouth.style.transition = "all 0.1s";
    mouth.innerHTML = "VVVVV";


    face.appendChild(ears);
    face.appendChild(stripes);
    face.appendChild(stripes2);
    face.appendChild(eyes);
    face.appendChild(snout);
    face.appendChild(mouth);

    return face;
}


This is what those teeth look like!


Now we will animate this. Go to the cny() function and add an If block after the one for the pig.

cny.js
if (animalNames[yearIndex] == "pig")
{
    setInterval
    (
        function()
        {
            var eyes = document.getElementsByClassName("cny_eyes");

            eyes[0].innerHTML = "-&nbsp;&nbsp;-";

            setTimeout
            (
                function()
                {
                    eyes[0].innerHTML = "&bull;&nbsp;&nbsp;&bull;";
                },
                500
            );
        },
        5000
    );

    setInterval
    (
        function()
        {
            var tail = document.getElementsByClassName("cny_tail");

            tail[0].style.transform = "rotate(-185deg)";

            setTimeout
            (
                function()
                {
                    tail[0].style.transform = "rotate(-175deg)";
                },
                500
            );
        },
        1000
    );        
}

if (animalNames[yearIndex] == "tiger")
{
        
}


In here, we use the setInterval() function with a 1 second interval. And inside the callback, we use a setTimeout() function with a half-second duration.

cny.js
if (animalNames[yearIndex] == "tiger")
{
    setInterval
    (
        function()
        {
            setTimeout
            (
                function()
                {

                },
                500
            );
        },
        1000
    );     
   
}


Then we declare mouth inside the first callback, setting it to the array returned by getting all elements with the class cny_mouth. The first (and only) element of mouth will have height set to 5 pixels. The callback within the setTimeout() function will set it back to 10 pixels. Note that since we had the div's overflow property set to hidden, there should be no issue with the teeth jutting out of the resized mouth.

cny.js
if (animalNames[yearIndex] == "tiger")
{
    setInterval
    (
        function()
        {
            var mouth = document.getElementsByClassName("cny_mouth");
            mouth[0].style.height = "5px";


            setTimeout
            (
                function()
                {
                    mouth[0].style.height = "10px";
                },
                500
            );
        },
        1000
    );        
}


If you refresh, the tiger should be snarling!


And clicking on the tiger still works!


The Year of the Tiger approaches!

Be bold and ferocious like the tiger, and may you achieve your goals in 2022!

It's been a pleasure presenting this web tutorial. Do enjoy your Lunar New Year.


Here's to a hu-mongous year!
T___T

Wednesday, 26 January 2022

Web Tutorial: The Lucky Number Generator, redux (Part 1/2)

Wishing you, dear readers, a fortituous Year of the Tiger!

Back in 2019 during the Year of the Pig, I released this web tutorial that took you through how to create a JavaScript library that created a lucky number generator. Today, I will be expanding on it. We will add code to draw a tiger... but at the same time we will amend the code to display the appropriate animal for the year.

You can get the original code from this GitHub repository.

We will first make this code less specific to 2019. First, change the name of the file tt_cny2019.html. Maybe to tt_cny2022.html, or something. I'm just going to change the title, and the name of the JavaScript file.

tt_cny2022.html
<title>CNY Test</title>

<style>
    body
    {
        background-color: #444400;
        color: #AAAA00;
        font-family: verdana;
    }

    p
    {
        width:80%;
        text-align: justify;
    }
</style>

<script charset="UTF-8" src="cny.js">
</script>


At the body tag's onload attribute, we change the function name from cny2019() to just cny(). And then we pass in "2019" as an argument. The idea here is to render the animal based on the year passed in.

tt_cny2022.html
<body onload="cny(2019)">


Now, we are going to take a look at the JavaScript file. We want to preserve the drawing of the pig, while allowing for other animals to be drawn. Most of the functionality existing here can be preserved, with just a few modifications.

As before, we change the function name. And we make sure it has a parameter, year.

cny.js
function cny(year)
{


In the function, replace all mentions of "pig" with the more generic "animal". Also, we want to remove all mentions of "2019".

cny.js
function cny(year)
{
    var overlay = createOverlay();
    var caption = createCaption();
    var animal = createAnimal();
    var body = document.getElementsByTagName("body");

    overlay.appendChild(caption);
    overlay.appendChild(animal);
    body[0].appendChild(overlay);

    animal.onclick = function()
    {


Now, we want a mechanism to determine which animal to draw. First, we define an array, animalNames. In it, we put in all the animals of the Chinese zodiac. We'll start with the Year of the Pig, just for simplicity.

cny.js
function cny(year)
{
    var animalNames = ["pig", "rat", "ox", "tiger", "rabbit", "dragon", "snake", "horse", "goat", "monkey", "rooster", "dog"];

    var overlay = createOverlay();
    var caption = createCaption();
    var animal = createAnimal();
    var body = document.getElementsByTagName("body");    


Now, declare yearStart and set it to 2019. That's our starting year, the Year of the Pig.

cny.js
function cny(year)
{
    var animalNames = ["pig", "rat", "ox", "tiger", "rabbit", "dragon", "snake", "horse", "goat", "monkey", "rooster", "dog"];
    var yearStart = 2019;


    var overlay = createOverlay();
    var caption = createCaption();
    var animal = createAnimal();
    var body = document.getElementsByTagName("body");    


The next variable to declare is yearIndex. This will act as a pointer into animalNames. To determine its value, we first take year and subtract yearStart. Normally, that should be enough to give us an animal. If we input 2022, the current year, the value of this with 2019 subtracted would be 3. If we take the element number 3 of animalNames, that's the tiger!

cny.js
function cny(year)
{
    var animalNames = ["pig", "rat", "ox", "tiger", "rabbit", "dragon", "snake", "horse", "goat", "monkey", "rooster", "dog"];
    var yearStart = 2019;
    var yearIndex = (year - yearStart);

    var overlay = createOverlay();
    var caption = createCaption();
    var animal = createAnimal();
    var body = document.getElementsByTagName("body");    


But what if it's, say, ten years later? What if we input 2032 as year into the cny() function? Then, we follow up by dividing the result by the length of animalNames, which is 12, and taking the remainder! That's where the modulus operator comes in.

cny.js
function cny(year)
{
    var animalNames = ["pig", "rat", "ox", "tiger", "rabbit", "dragon", "snake", "horse", "goat", "monkey", "rooster", "dog"];
    var yearStart = 2019;
    var yearIndex = (year - yearStart) % animalNames.length;

    var overlay = createOverlay();
    var caption = createCaption();
    var animal = createAnimal();
    var body = document.getElementsByTagName("body");    


Then we take yearIndex, use it to reference animalNames, and pass it into the createAnimal() function as an argument.

cny.js
function cny(year)
{
    var animalNames = ["pig", "rat", "ox", "tiger", "rabbit", "dragon", "snake", "horse", "goat", "monkey", "rooster", "dog"];
    var yearStart = 2019;
    var yearIndex = (year - yearStart) % animalNames.length;

    var overlay = createOverlay();
    var caption = createCaption();
    var animal = createAnimal(animalNames[yearIndex]);
    var body = document.getElementsByTagName("body");    


There is a piece of animation code further down, which was used to animate the pig. We will enclose it within a conditional block which checks if the animal to be drawn is the pig.

cny.js
if (animalNames[yearIndex] == "pig")
{

    setInterval
    (
        function()
        {
            var eyes = document.getElementsByClassName("cny_eyes");

            eyes[0].innerHTML = "-&nbsp;&nbsp;-";

            setTimeout
            (
                function()
                {
                    eyes[0].innerHTML = "&bull;&nbsp;&nbsp;&bull;";
                },
                500
            );
        },
        5000
    );

    setInterval
    (
        function()
        {
            var tail = document.getElementsByClassName("cny_tail");

            tail[0].style.transform = "rotate(-185deg)";

            setTimeout
            (
                function()
                {
                    tail[0].style.transform = "rotate(-175deg)";
                },
                500
            );
        },
        1000
    );        
}


We will do the same in the function that is now called createAnimal().

cny.js
function createAnimal(animalName)
{
    if (animalName == "pig")
    {

        function createLeg()
        {
            var leg = document.createElement("div");
            leg.style.position = "relative";
            leg.style.width = "8px";
            leg.style.height = "15px";
            leg.style.borderRadius = "50%";
            leg.style.backgroundColor = "rgba(255, 100, 100, 1)";

            return leg;
        }

        var torso = document.createElement("div");
        torso.style.width = "80px";
        torso.style.height = "70px";
        torso.style.borderRadius = "50%";
        torso.style.position = "relative";
        torso.style.backgroundColor = "rgba(255, 100, 100, 1)";
        torso.style.float = "right";
        torso.style.cursor = "pointer";

        var tail = document.createElement("div");
        tail.className = "cny_tail";
        tail.style.width = "80px";
        tail.style.height = "10px";
        tail.style.position = "relative";
        tail.style.margin = "10px auto 0 auto";
        tail.style.fontSize = "20px";
        tail.style.fontWeight = "bold";
        tail.style.textAlign = "center";
        tail.style.color = "rgba(255, 100, 100, 1)";
        tail.innerHTML = "&zeta;";
        tail.style.webkitTransformOrigin = "50% 0";
        tail.style.transformOrigin = "50% 0";
        tail.style.webkitTransform = "rotate(-180deg)";
        tail.style.transform = "rotate(-180deg)";
        tail.style.webkitTransition = "all 0.5s";
        tail.style.transition = "all 0.5s";
    
        var eyes = document.createElement("div");
        eyes.className = "cny_eyes";
        eyes.style.width = "80px";
        eyes.style.height = "20px";
        eyes.style.position = "relative";
        eyes.style.margin = "15px auto 0 auto";
        eyes.style.fontSize = "15px";
        eyes.style.fontWeight = "bold";
        eyes.style.textAlign = "center";
        eyes.style.color = "rgba(0, 0, 0, 1)";
        eyes.innerHTML = "&bull;&nbsp;&nbsp;&bull;";

        var snout = document.createElement("div");
        snout.style.width = "20px";
        snout.style.height = "15px";
        snout.style.borderRadius = "50%";
        snout.style.position = "relative";
        snout.style.backgroundColor = "rgba(200, 50, 50, 1)";
        snout.style.margin = "0 auto 0 auto";
        snout.style.fontSize = "10px";
        snout.style.fontWeight = "bold";
        snout.style.textAlign = "center";
        snout.style.color = "rgba(100, 20, 20, 1)";
        snout.innerHTML = "&bull;&bull;";
    
        var legs = document.createElement("div");
        legs.style.width = "70px";
        legs.style.height = "16px";
        legs.style.position = "relative";
        legs.style.margin = "-17px auto 0 auto";
            
        var leg1 = createLeg();
        leg1.style.float = "left";
        leg1.style.margin = "5px 0 0 5px";
        
        var leg2 = createLeg();
        leg2.style.float = "right";
        leg2.style.margin = "5px 5px 0 0";

        var leg3 = createLeg();
        leg3.style.float = "left";
        leg3.style.margin = "-8px 0 0 0px";
        
        var leg4 = createLeg();
        leg4.style.float = "right";
        leg4.style.margin = "-8px 0px 0 0";
        
        legs.appendChild(leg3);
        legs.appendChild(leg1);
        legs.appendChild(leg4);
        legs.appendChild(leg2);    
        
        torso.appendChild(tail);
        torso.appendChild(eyes);
        torso.appendChild(snout);
        torso.appendChild(legs);
        
        return torso;            
    }
}



What we need to do next is remove that delightful pun from the couplet in the createCaption() function and replace it with something more generic.

cny.js
leftcol.innerHTML = "<br />你<br />恭<br />喜<br />发<br />财<br />!<br />";


And there, the code still works as before.


Now with all the prep done, we will go on ahead to draw that tiger.

Next

Drawing the tiger using CSS.

Thursday, 20 January 2022

The Different Categories of Data Analytics

Data Analytics comes in different flavors. All of them are, at the core, crunching numbers and statistics to form a coherent narrative. The difference lies in what the narratives are used for.

Today, we will be examining the different kinds of Data Analytics that are used in the industry. For the purposes of relevance, we will be using the COVID-19 pandemic for examples.

Descriptive

This is by far the most common type of Data Analytics, and there is good reason for that - it is basic and provides insights to a current situation. Figures are displayed in a way that shows how the situation currently is. It describes a situation, hence the word descriptive. The question most commonly answered in these scenarios is what, which and possibly where or when.

Examining the
current situation.

One example of descriptive Data Analytics would be a day-to-day tally of new COVID-19 infections, with perhaps a monthly average and comparisons with figures from preceding years.

Diagnostic

The next step after descriptive Data Analytics is diagnostic Data Analytics. This is an analysis of the data, with possible patterns and correlations identified within. It is meant mainly to answer the question of why - why the situation is as presented and the possible causes.

Possible causes such as age
and pre-existing conditions.

One example, building on the example given above, are further breakdowns of COVID-19 data into age groups, genders and pre-existing medical issues. From this data, analysts can then determine if the numbers of infections or deaths correspond to these conditions.

Predictive

Progressing naturally from diagnostic Data Analytics, which explores how the current data came to be, is predictive Data Analytics, which projects, from the current trends and conditions, what the data will probably look like from this point forward. This is the Data Analytics version of fortune-telling.

Telling the future.

This could take the form of dashboards where variables can be adjusted to see what effect this has on the visualization. For example, if the number of infected elderly people was brought down, what effect would it have, over time, on the total number of COVID-19 infections?

Prescriptive

The final piece of the Data Analytics puzzle is prescriptive Data Analytics. After predictive Data Analytics, this comes in the form of recommendations as to how to affect the projected trend. As one might imagine, this is probably the most valuable component. The descriptive component explores the what, the diagnostic component generally explores the why, and the predictive component explores the what-if. The prescriptive component, however, adds value in the form of analyzing the gathered data to determine what steps could be taken to achieve desired outcomes.

Recommendations and
action plans.

In the case of our example, the desired outcome is lower, controllable and (hopefully) zero COVID-19 cases. Sample advice might be, vaccination programmes for elderly and high-risk groups, limitations on group sizes in public, and so on.

In a nutshell

What I have outlined is the different kinds of Data Analytics and how they stack up. The examples given above only pertain to the COVID-19 situation. But imagine these analytics being applied to profits, research and the stock market!

Analyze this!
T___T

Saturday, 15 January 2022

Some thoughts about movie reviews and movie critics

Someone asked me recently about a movie, and whether the reviews of it were any good. And my response was one of puzzlement. I have never in my life relied on the judgement of a movie critic to determine if I should watch a movie or not. If I want to watch a movie, I will; and if I don't, I won't. No amount of rave reviews or critical bombing has ever convinced me otherwise.

And for good reason. Well, reasons, actually. Some of which will be discussed today.

I also write movie reviews on this blog, though mine tend to be about movies that deal with tech. For the most part, my reviews come from a more layperson perspective. Occasionally, it comes from a tech worker perspective.

Objectives

Professional movie critics tend to judge movies by different standards than people like you or I might. They might think in terms of filmography, social messaging, diversity, storytelling and all that arty-farty stuff.

The filmography.

Me? I watch movies to be entertained. Education is the furthest thing on my mind. My objective is to have fun. If I'm not having fun, then it does not matter how many awards this movie has won or how many critics love this movie - the movie has failed where I am concerned. Remember, the critics are not the ones paying for your movie ticket. You are. Therefore, you should decide what your standards are... and most of the time, our standards are very different.

I don't feel particularly bad about having different expectations from a professional movie critic. It simply means that I am not a professional movie critic. That's hardly anything to be ashamed about.

Audience pressure

Sometimes it's a blessing to be a complete nobody in real life. I do not have a reputation to maintain, nor does my living depend upon my audience. No pressure at all.

On the other hand, professional movie critics are beholden to their employers and their audience. This means that they cannot praise or criticize however they please. Not if they want to keep their jobs.

Shush!

One such critic might hesitate to criticize a black actor for his performance in a movie, or suggest that it was the wrong casting choice, if the critic's audience comprises of racial activists or something. Cancel Culture being what it is, these professional movie critics could hardly be faulted for acting in the interest of self-preservation.

My reviews are independent. My living does not depend upon the approval of my audience. This is a fun pasttime for me, nothing more.

Biases and agendas

Recently, I watched Ghostbusters: Afterlife and proceeded to read some reviews. It was surprising to me how many reviewers slated this movie despite the fact that audience ratings were so high. And some of these reviews seemed to get awfully personal and there seemed to be an unhealthy amount of resentment that this movie dared to exist at all after the Ghostbusters, featuring an all-female team, was released in 2016.

I watch a movie because I want to watch a movie. Not because I want to promote a feminist agenda. Or indeed, any kind of agenda. In fact, I hate it when movies get preachy and heavy with the moral posturing. I'm in a motherfucking movie theater, people. Not church.

Promoting a feminist agenda.

Sure, everyone has biases, myself included. But I was under the impression that part of being a professional movie critic was at least being subtle about your biases.

Are my movie reviews better?

Using the judgement of a movie critic who has no idea what you like or don't like as a gauge as to whether you would enjoy a movie or not, is pure foolishness. I enjoy reading reviews to see what different people think. But my views remain my own.

The language used by professional reviewers is infinitely more colorful, and there are times when I aspire to that standard.

My reviews are definitely more relatable, even if the reader does not work in the software industry. That is because I think largely like the average layperson, I am beholden to no larger agenda; and most importantly, unlike some of these reviewers, I don't act like people should like or dislike a movie based on my opinion.

Pleasant viewing,
T___T

Tuesday, 11 January 2022

Functional Terminology

Some terms in programming tend to confuse new programmers. There's a certain historical context to them, and with the rise of more recent technology, new terms muddy the waters a bit.

And among these terms are: function, subroutine and method. They all seem to be referring to the same thing - procedures - but there are some significant differences.

Today I will be attempting to provide some clarity.

Subprocedures

These are procedures that exist in a block of code, and can be called from anywhere. Arguments could be passed into them, optionally. In QBasic back in the day, there was a specific keyword for that.

SUB HELLOWORLD()
    PRINT "HELLO WORLD"
END SUB


CALL HELLOWORLD()


Basically, the subprocedure would perform whatever operations the programmer put in it, and that would be the end of it. This has all but vanished from the programming landscape. These days, it's all about functions, which we will examine below.

Functions

Functions are also procedures that exist in a block of code, and can be called from anywhere. Arguments could be passed into them, optionally. The difference is that after performing the operations, they return a value.

This is how it was done in QBasic or Visual Basic.

FUNCTION HELLOWOWLD(str)
    HELLOWORLD = str
END FUNCTION


PRINT HELLOWORLD("Hello world")


Or in JavaScript...
function helloWorld(str)
{
    return str;
}


console.log(helloWorld("Hello world"));


...and in certain cases may not even return anything.
function helloWorld(str)
{
    console.log(str);
}


helloWorld("Hello world");


So in these cases, a function works just like a subroutine, and thus subroutines are not really needed any more.

Methods

Now we come to methods. Methods are just like functions in every way... except for access control.

We call a function a method when it's part of an object. This is an example in JavaScript.
let obj =
{
    helloWorld: function (str)
    {
        console.log(str);
    }
}


So if we were to call this method, we can only call it when specifying the parent object.
obj.helloWorld("Hello world");


If we were to treat it like any other function, it would not work.
helloWorld("Hello world");


In some object-oriented programming languages such as Java or C++, if you specify that the class is Private (for instance), the method can only be called within the class itself. There's actually a fair bit to cover on this subject, but it's out of scope for this blogpost.

Finally()

As long as you have a clear idea of what you are referring to when you use these terms, end of the day it doesn't really matter what terms you use. Communication is key. Just don't be confused when the context changes, and the terms change with it. Ultimately, it's generally the same thing.

Play that func-y music!
T___T

Friday, 7 January 2022

Five Software Development Takeaways From the COP Saga in 2021

In sunny Singapore, the Committee of Privileges (COP) saga took place last December, just a month ago. It all began when Raeesah Khan, an MP from the Worker's Party, was caught speaking a falsehood in Parliament which directly implicated the Singapore Police Force.

Time passed, and she repeated the lie in Parliament, and subsequently confessed when pressed on it. The fallout from this was that she had to resign from the Worker's Party, and the Committee of Privileges was formed to investigate the possible culpability of Worker's Party leadership in this debacle.

There has been a lot of coverage on YouTube concerning this investigation, with the hearings (the combined durations more than thirty hours in all) of different witnesses publicized. As a Singaporean, I have taken an interest and listened to almost all of the footage. As a software developer, these were the lessons I gleaned.

1. Diversity Hires

When Raeesah Khan first won her seat in 2020, there was this article from The Independent Singapore that made this big song and dance over the fact that she was the first female minority Opposition, and the youngest MP in Parliament. This turned out to age badly, considering what trouble Khan has gotten herself into since.

An MP deceitful enough to lie once in Parliament, and foolish enough to double down on that lie a month later? What really gets me was that there was so little to gain from this. Politicians lie and cheat in order to embezzle huge amounts of money. What were Khan's motivations? She wanted to make the police look bad. Furthermore, the incident she had described in Parliament had not been personally experienced, but something she had read off a WhatsApp chat group conversation. Passing off WhatsApp anecdotes as fact is the province of clueless Boomers, not an MP.

Make the police
look bad.

During her hearing, it was revealed that Khan was against resigning from the Worker's Party because she was worried about how this would impact the image of female minorities in politics. With all due respect, that ship appears to have left the harbor the moment she got caught in the lie. If it's any comfort, unlike those idiots at The Independent Singapore, I never saw Raeesah Khan as a female minority, much less considered her any representative of such. All I ever thought of her, even when she won her seat, was as this dangerous loose cannon with an axe to grind against the police.

This is a valuable lesson for hiring in tech. Stop focusing on age, gender and race - largely immutable traits - of your candidates and instead focus on what they have accomplished, what they are currently doing, and what they can further accomplish. Hiring people for the sake of fulfilling some nonsensical diversity quota can come back to bite you in the ass when they inevitably screw up because you cared more about the color of their skin or their genital plumbing than their actual ability.

2. Explicit Instructions

According to Pritam Singh's testimony, his instructions to Raeesah Khan were to "take ownership and responsibility" and that she would not be judged. Somehow, according to her, these were license to double down on that lie later in Parliament. I'm not sure how "take ownership and responsibility" translates to a choice for anyone to continue lying in Parliament. Maybe to a complete child. But this is not a child we are talking about. This is a democratically elected Member of Parliament.

Clear and unambiguous
instructions.

Could Pritam Singh have been more explicit about ensuring that Khan clarified her falsehood? Sure. Should he have to? Absolutely not. Remember, this is not a child we are talking about.

But this is the thing about operating in a professional environment. Sometimes, you feel that certain things go without saying. And this can go badly.

As a software developer, I find myself having to check the input that users give me via the system. Of course, I feel that anyone with even an ounce of common sense would be responsible about faulty data and do their due diligence before sending me a potential mess that I have to clean up later. Unfortunately, people tend to do what they think they can get away with, especially if their hands are full. Therefore, now my correspondence with users contain explicit (sometimes sternly so) instructions to ensure cleanliness of data before handing it off to me. Should I have to remind people to do their goddamn jobs? Fuck, no. But this is the reality I have to deal with.

3. The Price of Authority

Regardless of whether or not you believe that Pritam Singh was complicit in the lies that Raeesah Khan continued to tell in Parliament, there is one glaring, inescapable fact: he is the leader of the Worker's Party. The buck stops with him. As such, Khan's membership in the party is at least partially his fault. As for her candidacy in 2019, that is definitely his fault. The fact that such a flawed character ended up as a Member of Parliament, is down to his failure in terms of character judgement.

Real leaders
don't play
the blame game.

That is the price of authority. When something happens, you ultimately have to shoulder some of the blame, if not all of it.

In tech, I have encountered CTOs and team leads who love pointing fingers, not understanding how completely stupid this makes themselves look. They are in charge; everything is their fault. Even if they did not personally write faulty bug-ridden code, they are responsible for approving it. If bugs persist in the product, they have to shoulder the blame for not addressing the issue sooner.

They are in charge. The buck stops with them. Failure to accept responsibility is poor leadership. Very poor.

4. Due Process

The long and drawn out process of the COP has had mixed reactions. From supporters of the Worker's Party, mental fatigue and lamentation that this was a waste of time and taxpayers' money. From supporters of the People's Action Party, baying for blood. Both predictably so. But these are simple-minded villagers masquerading as political commentators, and their views are inconsequential.

Instead, consider this: why did the PAP make this public, and publicize these videos on YouTube? Bear in mind that Singapore is a democracy. As such, we are under certain obligations to due process, no matter how onerous and how tiresome the process may be. You cannot claim to be a democracy and yet eschew the portions that are unpalatable. That is simply not how things work.

Due process is important.

Singapore's Government needed to show the rest of the world just how seriously we take democracy. Singapore's image as a stable and conducive place to conduct business, was at stake.

Due process is another big factor in Agile Methodology. Code sprints are capped off with code reviews. System Integrated Testing. User Acceptance Testing. You cannot claim to be "Agile" and yet do away with integral components of said Methodology.

Would I get more work done without having to deal with Sprints? Are Retrospectives a gigantic pain in the ass? Undoubtedly. Yet, they are necessary.

5. Layperson Expectations

There are more than thirty hours of footage on YouTube. Some enterprising souls have created clickbait by making compilations of all the heated moments. Viewers gravitate towards these, so as to save themselves hours of listening to it all, and entertain themselves by pointing out how Edwin Tong stutters at some parts, or jeer at Pritam Singh, depending on which party they support. And hearing Pritam Singh utter "agree" and more often, "disagree" or "completely disagree".

However, if they took the trouble to actually listen to the entire proceedings, they would find it much less adversarial than the short compilations suggest. For the most part, Tong and Singh are pretty respectful and gentlemanly towards each other - cordial even. There are even parts where Singh elicits a chuckle from Tong, and Tong makes a corny joke or two.

But no - viewers just want the political theater from that little "PAP vs WP" narrative they have in their head. As mentioned before, these are pitchfork-wielding, simple-minded villagers at heart, and they're incapable of seeing anything else.

This isn't magic.

It is much the same in software development. Laypeople only see the end result. They do not see the trial and error, the painstaking process, and the mess that results when requirements are unclear or worse, ambiguous.

What we do is not magic. If something does not make sense outside of the system, it will still not make sense in a program. Programs run on logic, not fantasy. But because laypeople are blind to the process, they may have expectations that can be divorced from reality.

To conclude...

I'm not entirely sure how I managed to sit through all that footage, and actually make sense of it all. But like it or not, this is Singapore's Parliamentary process at work. Perhaps I managed to endure all that precisely because I was thinking like a software developer. And as such, this managed to retain my attention.

"Agree? Disagree?"
T___T


Sunday, 2 January 2022

A dish best served cold?

Former employers may rank among some of the most disliked people on the planet, especially for those of us who have ever left a job due to a toxic work environment, or have been dismissed unfairly. For some of us, it feels like there is should be a special place in hell reserved for nightmarish ex-bosses.

As such, I am dedicating today's blogpost to a popular activity: revenge. Some of us may practice this only in our fantasies, and some might have even dedicated some time to actually materialize this.

Getting even is sweet

Let's face it: most of us, at some point or other in our lives, are capable of vindictiveness. After all, unpleasant bosses made our lives miserable. We spent time and effort trying to do the job, and what we got in return were unrealistic expectations, financial exploitation or bullying - or all of the above. It's only human to want some payback once the work relationship has ended.

Want your
pound of flesh?

However, there's a problem - it's generally considered unprofessional to actually actively take revenge.

No prospective employer, whether he or she is an asshole or not, is going to feel comfortable employing somebody who will openly and willingly trash his former employers. And your professional reputation does count in such cases. So, if your former employer is really such an utter rear end, is it worth affecting your own professional reputation to take him or her down a notch?

Well, here's the good news: you don't have to.

The best revenge

I've had my fair share of unpleasant bosses. Some of them were just demanding. Some of them were huge wankers.

In 2010, my then-boss disappeared after I worked for him for ten months, for free. Years later, I am drawing more than twice what he used to pay me.

In 2013, I had a boss who tried his best to make my life untenable while not-so-subtly promoting a replacement he hired. His wish came through; I quit. I'm sure his candidate tried her best, but it was a sinking ship. To be fair, it was a sinking ship long before I ever left. Last I heard, that unpleasant boss is having legal troubles (due to not paying his staff or some shit) and his good name is in shambles. And I am drawing more than twice what he used to pay me.

In 2015, I ran into a boss who unilaterally cut our pay citing the economy as an excuse. The company collapsed and he had to let us go. Years later, he's encountered one failure after another trying to set up a profitable business, and yes, I'm drawing more than twice what he used to pay me.

In 2020, I encountered one of the biggest jackasses to ever walk God's green earth. He got rid of me, and two weeks later I landed a job at a far larger company, with a raise. He's still running his shitty startup and trying to convince himself he's relevant.

Each of them amounted
to this, nothing more.

In all of these cases, I did not have to lift a finger. I simply outgrew all of them. They were good as stepping stones, and I used my experience with them to move upwards. At the risk of sounding cocky, they can no longer afford me. I don't have to be bitter, or seek to destroy their reputations. It isn't worth the effort it would take from me. They're still struggling entrepreneurs, while I'm a far more highly-paid employee than when I used to slave for them.

Those who made my life difficult back then are already being punished in the worst way possible - they have to be themselves. I don't have to do shit. They're no longer worthy of my time and attention.

You know what's even sweeter than revenge? Being above such things. You can be obsessed with hurting them the way you felt that they hurt you... or you can work on yourself and leave them in the dust. After all, what's one more bad boss in a long line of bad bosses? Making the effort to exact payback makes them seem special. They're really not. Terrible bosses are a dime a dozen.

Conclusion

Revenge is overrated. Revenge is for children. I have no hard feelings towards any of these guys. At the end of the day, it's just business.

Revenge is a dish best served when you no longer give a shit. Best for you, anyway.

Revengers, assemble!
T___T