Finding A Better Tool

A diary about learning a different programming language

“So how can I run this?”

A message popped up and my fingers suddenly froze above a keyboard. Slowly shifted my focus, I replied it by giving a link to install some requirements. Shortly, another notification popped up,
“Ok, so which one should I install?”
I patiently wrote a minimal tutorial in a text box then sent it. I waited for some time.

No response.

Seems it works.

Two hours later, I was about to wrap up my work and suddenly a notification popped up “Hey, it seems not working. Did I miss anything? <screenshot error message> ”
I sighed, and replied “Not sure. Never tried it on that system though. A call?”
“Ok, let me figure it out then.”
I raised my eyebrow, wondered if it will work.

Ten minutes passed, my eyes kept staring blankly on the chat. Will it work? I wondered.

Maybe better to let me do it
I just need the file, run it, then upload the result. No hassle.
But what if it requires a day to upload the file?
Well I can share an executable file
There would be no more hassle to install
They click it and all goes well
But, this tool doesn’t support that, oh well.

Overwhelmed by my thoughts, I turned off the computer and left my workplace to get some refreshment.

It was quite and dark again on the outside. My shoe sole brushed against a paved surface, creating a short scratching sound, dispelled the calm atmosphere that surrounded me. My eyes gazed into a faint light at the sky. Enjoying the view after a long tiring day.

A few solemn moments later, I started to recall a chat that I previously had. I was thinking I shouldn’t ask them to install anything. It was just a simple script I made for them. Why would a small script requires installing a huge dependency just to run it. Beside, that script would only be used once in other machine. I knew I should’ve built an executable rather than shared a raw file yet it required me to setup my machine to be similar like theirs first. I’d need time for that. If only I could make my script portable without requiring to install anything.

Will my problem with the tool can be solved with other tool?


Exploration

Few days have passed, I tried to keep using the tool while consciously thought what I wish this tool could do better for me. Many people have made extension to augment its capability but I tried to use this tool as it was designed for. Diligently explored everything in the documentation before adding other people’s code.

Truth was, the more I spent time to explore it, the more it impressed me. So many interesting thoughts were put in this tool plus, they documented it clearly. It’s just, the problem I had with this tool would not go away. It was not designed for that purpose so people crafted an extension to achieve that but it wasn’t officially included. Else, it would lead to a catastrophe.

I started to note down what I missed from the tool:

  1. Ability to create a portable executable
  2. Catch error early
  3. Type validation

The second and third problem could be solved by installing other extension, though it would be nice if the tool was designed for that from the beginning. The first problem was what urged me to find an alternative. Creating an executable is necessary to handover the script I wrote, especially for non-tech folks. Moreover, if the script needs to run on different machine with different system I shouldn’t need to use that machine to build an executable.

Other than its drawback, it will be better if there is one which has nice things this tool already offer:

  1. Write less to do more
  2. Comprehensive documentation
  3. Verbose type
  4. Community support

Since I was planning to use it mostly for writing a script and for tooling, huge built-in library is essential to write less code. The huge library will only be useful if it’s documented properly so I know on what case that library is useful. Documentation also helped to onboard with the tool quickly.

While documentation itself is necessary, having a type itself also helpful to write a self-documented code. The tool that I used has, surprisingly, quite a verbose type but it’s just there as a label. Not a constraint or rule. I need to manually add validation or install additional tool that would enforce that rule. So having a type as a rule would be great to prevent putting invalid input. Other than documentation, community support would be great so I can learn how others use the tool plus if there are many resources (books, articles, etc.) taught how to use the tool for specific use case.

I started to explore an alternative every spare time. Annual developer survey and Wikipedia were my reference. I checked every name which I wasn’t familiar with then digging to its syntax, documentation and library.

A couple months had passed, I finally made a decision to do hands-on practice. I prevented myself to explore countless of alternatives further. Instead, I found a book that helped me to onboard with my new toy. It’s quite like my type so I gave it a go.


Hello World

It was weekend, I didn’t have anything much on my schedule. A great opportunity to experiment with something new.

printfn "hello world!"

The sample above is a working code. A new toy that I was playing is called F#. It is a general-purpose functional programming language. I saved the code under a file name called Program.fsx where .fsx is an extension for F# script.

$ dotnet fsi Program.fsx
hello world!

It requires .NET to compile since the language is part of .NET ecosystem. I executed it with the above command and immediately had doubt to continue. It was slow just to print 12 characters. It was .NET 6 that I used.

$ time dotnet fsi Program.fsx 
hello world!

real	0m1.150s
user	0m1.045s
sys	0m0.088s

The thing was, I learned that this tool isn’t just an interpreted language but also a compiled language. The compiled version would be named with Program.fs but I’d need to create a new project first to be able to build it

$ dotnet new console -lang F# -o hello

Once it created a project for me. I edited the code and built it. Would it be that slow? I wonder.

$ time ./hello
hello world!

real	0m0.047s
user	0m0.032s
sys	0m0.015s

Interesting, the difference was huge. I no longer worried about the time to execute it, since my only concern was the executable file. I accepted one trade off it has. I learned about this after watching a conference from FOSDEM by Scott Hanselman.

There is also one more way to execute F# script. It is by typing dotnet fsi to enter into its REPL or interactive version.

$ dotnet fsi

Microsoft (R) F# Interactive version 12.0.0.0 for F# 6.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> printfn "hello world!";;
hello world!
val it: unit = ()

I didn’t consider this feature important previously but, now I realized I usually wrote a small code as POC in REPL to see if it worked before adding it to an already huge codebase. It was a normal thing to do. With this feature already built-in, I started to get more confident with my new toy.

I continued to dive into the book to find more about it

type Customer = string * bool * bool

So a customer is a string multiplied by a boolean times boolean was my first thought. The entire room would laugh at me. I was indeed wrong, the book says that actually a tuple type. It can be used like this

// string * bool * bool
let fred: Customer = ("Fred", true, true)

Now that’s clear for me, assigning tuple value seems familiar but declaring the tuple type still became a struggle. Anyway, by reading throughout the book I started to get familiar with tuple symbol. Funny still, I never learned functional language before so this idea often mixed up.

What’s not shown on above sample is that value is actually immutable. That’s the default. Creating mutable value is also possible though

let mutable fred: Customer = ("Fred", true, true)
fred <- ("Henry", false, false)

fred was declared with value (“Fred”, true, true) and then I assigned a new value to fred which is (“Henry”, false, false) . Instead of using =, it uses <- to indicate it’s assigning a mutable value.

Another interesting type I found was Record

type Customer = { Id:string; IsEligible:bool; IsRegistered:bool }

Wait, is that a semicolon? Usually a comma is used to separate each key but this language is different. After looking at that sample, the book showed another surprising alternative way to declare a record type

type Customer = {
    Id : string
    IsEligible : bool
    IsRegistered : bool
}

Hold on, that seems wrong. Where is the comma? In fact, it compiles. Also it can be written this way

type Customer = {
    Id : string;
    IsEligible : bool;
    IsRegistered : bool;
}

It uses semicolon instead of comma, that’s interesting. I wonder just how many “surprise” this language have. During my college until working with professional, I never saw this kind of code. Intriguing.

There’s still more.

let fred = { Id = "Fred"; IsEligible = true; IsRegistered = true }

Somehow I felt unsettled, it actually assigning Customer record type. My mind still struggled to grasp what happened because I initially thought record is similar like object or dictionary type. So if we translate it to a Javascript it would look like this

let fred = { Id: "Fred", IsEligible: true, IsRegistered: true }

It assigns the value with = instead of : and separated with ; instead of ,. This record type will be validated in case we mistype the value or key. I believed many language had implemented this validation. The syntax is subtly different. Yet I needed time to get used to it.

Another interesting sample is Option type.

type Option<'T> =
  | Some of 'T
  | None

Hm, so weird. That ’T must mean generic but what is that | before Some I wonder. “It is a discriminated union with two cases to handle whether there is data or not.” the book explained. Discriminated Union means “it can be either this or that”. So the Option type helped us to tell if the value might not exists, which is None. The | sign is a match expression, when a value has Option type, it can have either Some type or None type. It can also be written like this

type Option<'T> = Some of 'T | None

F# already has Option type so I don’t need to create one, here’s a sample to use it.

open System

let tryParseDateTime (input:string): Option<DateTime> =
  match DateTime.TryParse input with
  | true, result -> Some result
  | false, _ -> None

The code above is using .NET library called System. After that it declares a function tryParseDateTime which has input parameter with string type. The function may return a value with DateTime type or not at all hence, the return value type is Option<DateTime>. What the function does is it will parse a string and convert it into a valid datetime in F# using DateTime.TryParse function from System library. If the input string is valid, it will return a value with DateTime type, otherwise it will return None. This is done using a pattern matching with match … with.

The more I read it, the more it created a culture shock in my brain.

// Forward pipe operator
let upgradeCustomerPiped customer =
    customer
    |> getPurchases
    |> tryPromoteToVip
    |> increaseCreditIfVip

When the first time I noticed |> symbol, I thought my computer failed to render the book content properly. Shame on me, I might be too tired or something. Apparently, this is a legitimate syntax. The |> is called forward pipe operator. Since it’s a functional language, we’re breaking down a problem into modular part, then we created a small unit called a function that can be “chained” together. The sample above is how F# chained functions together using |>. If we convert it to one of popular programming language, it would be like this

def upgradeCustomerPiped(customer):
    purchaseValue = getPurchases(customer)
    isVip = tryPromoteToVip(purchaseValue)
    creditValue = increaseCreditIfVip(isVip)
    return creditValue

However, F# did it in a more succinct way. Also notice that F# doesn’t use return key to return value in a function.

I admitted that the book introduced a lot of new concepts of programming with F#. It was quite challenging to read it but the author was quite humble enough to guide the reader. The sample above just a tiny fraction about the language. I spent my evening and weekend to read through it. Although not diligently learned it everyday, I did spend some time to dabble on it when there was less workload.


Experiment

Around a month later, I still can’t get the idea of F#. My goal was to grasp all the book’s content in a week. I felt failed to be considered as an intelligent creature.

This didn’t mean the book failed to teach me, there was a lot of example and the author explained each line of code. I could understand what the code did, my problem was I still couldn’t express my thought through the code. That skill, I believed required me to do more hands-on practice. I didn’t want to stop playing with my new toy and seeking out an alternative. It’s because it had a feature to build an executable file for different system other than its host machine. I’d like to see how compatible the executable would be. So I reconsidered my learning method instead.

I didn’t finish the book, I stopped reading at chapter 12 about Computation Expression then just skimmed through until the last chapter. My goal was switched to build a toy project with F# while I treated the book as a handbook instead of a textbook.

‌The toy project that I’d like to work was an image combinator. I’d been thinking about this from the beginning. I was working on a project that related to this but I wasn’t satisfied with the result. It was a lot of hack to achieve the result, though not perfectly. I looked around the internet and found other project that also worked on the same type of problem. Interesting, so others already built their own tool. Well, my goal was to practice so I didn’t mind reinventing a tool.

I followed a guide from the book on how to create a new project in F#. My first to-do was writing a code to combine multiple images together. I read through .NET documentation and found a library that could help me, System.Drawing. However, it was only supported for Windows. My plan for this project was to build a cross-platform tool. So I decided to use ImageSharp to help me combine an image.

ImageSharp was a third party library that’s written with C#. I read on some forum that F# could play along with C#. Hm, let’s see how would it work. I tried to create a simple test.

T‌he picture above was my first goal. It would become the foundation of the tool. Here’s how the code looked like

open SixLabors.ImageSharp
open SixLabors.ImageSharp.Processing

let square = Image.Load("square.png")
let circle = Image.Load("circle.png")
square.Mutate(fun action -> action.DrawImage(circle, 1f) |> ignore)
square.Save("square-circle.png")

On the first 2 lines, I imported ImageSharp library. After that, I loaded the image using Image.Load and save it as immutable value. I used square.Mutate to do image manipulation on square image, and inside it I created an anonymous function to add a circle image on top of it using action.DrawImage(circle, 1f). Since action.DrawImage has a return value, I didn’t care about it so I ignore it instead. Once I’d done the manipulation, I saved the image using square.Save function and the output name would be square-circle.png.

It worked! I could import C# code and used it with no issue. Cool! Let’s see if I could combine more images.

The next test, I tried to combine 3 images just to see whether it would work.

open SixLabors.ImageSharp
open SixLabors.ImageSharp.Processing

let images = ["Basic.png"; "Dark.png"]
let baseImage = Image.Load("Gray.png")

for image in images do
  use otherImage = Image.Load(image)
  baseImage.Mutate(fun action -> action.DrawImage(otherImage, 1f) |> ignore)
done

baseImage.Save("square-face.png")

It seems similar but here I created a list of image name that would be combined and stored it in images. After that I iterated the image name using for loop and combine them into the base image. However, this time I assigned the image with use otherImage instead of let otherImage. The different between let and use is the former mainly used for storing value or create a function while use is used for resource management. When it called Image.Load it reserved some memory. If we didn’t put use then the memory would increase over time because we put it in a for loop. So use is helpful to prevent that of happening. I didn’t pick use for baseImage because it’s a small script that ended after baseImage.Save was called. When the script has stopped, all resource would be released automatically.

Yap, that worked! So I could use it to combine multiple images. While I’m working on my toy project, I stumbled upon an article from F# for Fun and Profit about tips for learning F#. It provided some dos and don’ts when writing F# code. I might not be able to follow them all at once, so I picked one first.

Don’t use for loops

Hm. Which mean I could use while, isn’t? Well, I’d rather to assume it as “Don’t use loops”. I recalled the sample from the book and most of them didn’t use loop indeed. It was mostly List.iter , List.map, List.reduce, Seq.iter, Seq.map, …. Anyway, I tried to refactor the code without using loops.

open SixLabors.ImageSharp
open SixLabors.ImageSharp.Processing

let combineAllImages (output: string) (images: List<string>) =
  let combineImage (baseImage: Image) (image: string) =
    use otherImage = Image.Load(image)
    baseImage.Mutate(fun action -> action.DrawImage(otherImage, 1f) |> ignore)

  use baseImage =
    images
    |> List.head
    |> Image.Load

  images
  |> List.skip 1
  |> List.iter (combineImage baseImage)

  baseImage.Save(output)

["Gray.png"; "Basic.png"; "Dark.png"] |> combineAllImages "square-face.png"

Whoa, there. That seems a lot.

Indeed, I made the function so that it can accept any number of images and combine them together. I replaced the for loop with List.iter function where it would iterate the value and combine the image. After that, in the last line, I could call the function by passing a list of images and put the name of the output.

For now, this is the POC of the tool I wanted to build. The next test was building an executable, I tried to create an executable for Linux x64 and Windows x64. I built the tool from a Linux x64 machine.

I edited the project settings so that it would create a single executable file, this would make it easier to distribute. After that I ran the following command

dotnet publish --os win -c Release --self-contained true
dotnet publish --os linux -c Release --self-contained true

—os win indicated the build was for Windows while —os linux indicated the build was for Linux. Here’s the output

So by using a Linux machine, I could build an executable for Windows and Linux. There was also an option for Mac OS but I didn’t have a machine to test it so I skipped it. Let’s test the result on Windows!

Unfortunately, uploading GIF is not supported in this platform. Anyway, we can see the screenshot above that the tool manage to combine images in Windows. Finally, I found a tool that solved my problem! I’d like to learn more about this language. It has a great documentation although most of example use C# but it’s still doable in F#. It’s statically typed so I didn’t need to add code to validate each input. Its quirky syntax made it possible to write less code. Since it’s part of .NET, the community support was great. During working on my toy project, I often got stuck and asked a newbie question. The response was enlightening.

The project that I was working on could be found here https://codeberg.org/ky64/pic


T‌his post is intended to share my experience on finding the right tool to solve my problem, so it is not reliable as reference for evaluating pros and cons of tool that are being used here.‌ Thanks for taking time to read it!


References

Credits

I don’t own most of the images, I mostly took from Unsplash.