Learning Programming Languages with Code Challenges
Jorge Ramón
Posted on December 2, 2019
I don't know about you but sometimes I get bored by using the same tools over and over again.
So to have fun I wanted to learn a new programming language but as you may think I don't have the time to do so, maybe buying an Udemy course or watch some videos on YouTube are good idea but still... I wanted to do more than just writing Hello World.
But there was a bigger problem... which programming language should I be learning? I had the following options in mind:
Golang, is a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.
Rust, is a modern systems-level programming language designed with safety in mind. It provides zero-cost abstractions, generics, functional features, and plenty more.
Crystal, is a programming language with the following goals:
Have a syntax similar to Ruby (but compatibility with it is not a goal).
Be statically type-checked, but having to specify the type of variables or method arguments.
Be able to call C code by writing bindings to it in Crystal.
Have compile-time evaluation and generation of code, to avoid boilerplate code.
Compile to efficient native code.
Kotlin, is a general purpose, open source, statically typed “pragmatic” programming language for the JVM and Android that combines object-oriented and functional programming features.
Elixir, is a dynamic, functional language designed for building scalable and maintainable applications.
Hard decision, isn't it? but then I got an idea 💡... What about learning all those programming languages at once?
This is an experiment to see if it's possible to learn many programming languages at once by solving the same code challenge.
The rules
Before go on, we need some rules in order to do the experiment the best as possible:
If you know a better solution or code improvement of any programming language feel free to leave a comment and it will be mentioned in the next post. It's about continual improvement 🤓.
Let's start easy, FizzBuzz is a small game that interviewers use to determine whether the job candidate can actually write code. Here is the description:
Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".
Pseudocode Solution
FOR each number "i" from 1 to 100
IF i is divisible by 3 AND is divisible by 5 THEN
PRINT "FizzBuzz"
ELSE IF i is divisible by 3 THEN
PRINT "Fizz"
ELSE IF i is divisible by 5 THEN
PRINT "Buzz"
ELSE
PRINT i
ENDIF
ENDFOR
package keyword must be the first line in every Go program.
In this case the package name is fizzbuzz (same as directory name) because this is an utility package (i.e. it's not standalone executable).
To export the fizzbuzz function, its name must start with uppercase letter. Lowercased function names are private functions (i.e. can be only used in the same file).
A function must start with func keyword.
Like using Java or Javascript I wanted to return nil (null) if the given number is not divisible by 3 or 5 but that was a big mistake because the compiler throws the following error: cannot use nil as type string in return argument. So, since functions can return more than one value then fizzbuzz function returns two values; the first one (string) is the result text (Fizz, Buzz or FizzBuzz) and the second one (boolean) means if the operation was done successfully.
% (modulus) operator is used to check if the given number is divisible by 3 or 5.
if statements has no parenthesis.
Unit Tests: fizzbuzz/fizzbuzz_test.go
packagefizzbuzzimport"testing"funcTestFizzCases(t*testing.T){cases:=[10]int{3,6,9,12,18,21,24,27,33,36}for_,n:=rangecases{result,_:=FizzBuzz(n)ifresult!="Fizz"{t.Errorf("Error, %v should be 'Fizz'",n)}}}funcTestBuzzCases(t*testing.T){cases:=[10]int{5,10,20,25,35,40,50,55,70,80}for_,n:=rangecases{result,_:=FizzBuzz(n)ifresult!="Buzz"{t.Errorf("Error, %v should be 'Buzz'",n)}}}funcTestFizzBuzzCases(t*testing.T){cases:=[10]int{15,30,45,60,75,90,105,120,135,150}for_,n:=rangecases{result,_:=FizzBuzz(n)ifresult!="FizzBuzz"{t.Errorf("Error, %v should be 'FizzBuzz'",n)}}}
What I've learned
testing is the built-int package to do tests (lol).
Every test function name must start with Test keyword and latter name should start with uppercase letter (for example, TestMultiply and not Testmultiply)
:= is a short variable declaration (for example, i := 0 is the same as var i int = 0).
[N] T { v1, v2, ..., vN } is the way to declare an Array with fixed size N
of type T with v1, v2, ..., vN as values.
Every test function has the same parameter.
There is no assert function or something like that, if the test fails it must use the t.Errorf function with a message.
range is the way to iterate over an Array or Slice (dynamic sized array), the first return value is the index and the second one is the value itself.
When a function returns more than one value, you can't ignore any of them (for example, result := FizzBuzz(n) is wrong because it returns two values).
To create a new project just run cargo new [project-name] --lib(lib flag because it's not a standalone executable) and it will create the basic folder structure and the lib.rs file.
pub keyword is the way to export anything.
fn keyword is for functions.
There is no null value in Rust, so the best way to do the same is using the Option type. To give a value it must be Some(value), to give a "null" value it must be None.
if statements has no parenthesis
The type of a simple string needs the 'static lifetime (I don't know what does that mean... yet).
#[whatever] is an attribute, useful to add metadata to a module, crate or item. In this case the module is using cfg attribute to add metadata to the compiler.
#[test] attribute is used to mark functions as unit tests.
mod whatever { ... } is the way to create a module. By default is private (unless pub keyword is added).
let keyword is used to create a variable (immutable by default).
[T;N] = [v1, v2, ..., vN] is the way to create an Array with fixed size N of type T with v1, v2, ..., vN as values.
for in iterates over an Iterator, so array.iter() returns an Iterator.
Maybe you are wondering why by calling fizzbuzz function I need to use *number and not number, well that's because array.iter() returns an Iterator with references (so, number variable type is &i32 and not i32).
A module is a way to group functions and classes. It's more like a namespace.
def keyword is the way to create methods or functions.
nil is the null type.
Unit Tests: fizzbuzz/spec/fizzbuzz_spec.cr
require"spec"require"../fizzbuzz"includeFizzBuzzdescribe"FizzBuzz"doit"should be 'Fizz' for multiples of 3"do[3,6,9,12,18,21,24,27,33,36].eachdo|number|result=fizzbuzz(number)result.shouldeq("Fizz")endendit"should be 'Buzz' for multiples of 5"do[5,10,20,25,35,40,50,55,70,80].eachdo|number|result=fizzbuzz(number)result.shouldeq("Buzz")endendit"should be 'FizzBuzz' for multiples of 3 and 5"do[15,30,45,60,75,90,105,120,135,150].eachdo|number|result=fizzbuzz(number)result.shouldeq("FizzBuzz")endendend
What I've learned
Import a package (spec in this case) or file (fizzbuzz solution) is very similar to Node.js.
include is very similar to Java's static import, I would have had to write every fizzbuzz call like Fizzbuzz::fizzbuzz without it.
Test look very similar to Mocha Framework in Javascript.
I didn't need to create a class and I really liked that, just like any scripting programming language.
func keyword is the way to create a method/function.
Primitive types use uppercased letters.
String? return type is declared because it can be null.
Unit tests: fizzbuzz/src/test/kotlin/FizzBuzzTest.kt
importorg.junit.jupiter.api.Assertions.assertEqualsimportorg.junit.jupiter.api.DisplayNameimportorg.junit.jupiter.api.TestclassFizzBuzzTest{@Test@DisplayName("It should be 'Fizz' for multiples of 3")funshouldBeFizzForMultiplesOfThree(){valcases:IntArray=intArrayOf(3,6,9,12,18,21,24,27,33,36)cases.iterator().forEach{assertEquals(fizzbuzz(it),"Fizz")}}@Test@DisplayName("It should be 'Buzz' for multiples of 5")funshouldBeBuzzForMultiplesOfFive(){valcases:IntArray=intArrayOf(5,10,20,25,35,40,50,55,70,80)cases.iterator().forEach{assertEquals(fizzbuzz(it),"Buzz")}}@Test@DisplayName("It should be 'FizzBuzz' for multiples of 3 and 5")funshouldBeFizzBuzzForMultiplesOfThreeAndFive(){valcases:IntArray=intArrayOf(15,30,45,60,75,90,105,120,135,150)cases.iterator().forEach{assertEquals(fizzbuzz(it),"FizzBuzz")}}}
What I've learned
intArrayOf is the way to create an Array of Integers and the type is IntArray.
val is for readonly variables.
There is no way to iterate over the array itself, it's only through an Iterator.
forEach method receives a function and if there isn't an explicit parameter in the function definition then it will be named it by default.
defmodule is the way to create a module (a group of functions, think about a class in OOP).
Functions and variables are created with def keyword.
cond is like a switch statement but with boolean conditions.
Elixir is a functional programming language, so everything is a function.
nil is the null type.
rem is the way to calculate modulus.
defmoduleFizzbuzzTestdouseExUnit.CasedoctestFizzbuzztest"should be 'Fizz' for multiples of 3"doEnum.each([3,6,9,12,18,21,24,27,33,36],fnx->assert(Fizzbuzz.fizzbuzz(x)=="Fizz")end)endtest"should be 'Buzz' for multiples of 5"doEnum.each([5,10,20,25,35,40,50,55,70,80],fnx->assert(Fizzbuzz.fizzbuzz(x)=="Buzz")end)endtest"should be 'FizzBuzz' for multiples of 3 and 5"doEnum.each([15,30,45,60,75,90,105,120,135,150],fnx->assert(Fizzbuzz.fizzbuzz(x)=="FizzBuzz")end)endend
What I've learned
fn is an anonymous function (like a lambda).
Enum module is useful to work with Collections.
Conclusion
Golang, Rust were the hardest ones, but I can't wait to learn more about them.
Kotlin is much like Java and was the only programming language that needed an external build system to run the code.
Thank you so much for reading, Did you like the experiment? 🤓.
Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".