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:
- Pick a non-trivial challenge.
- Write a pseudocode solution following Cal Poly pseudocode standard.
- Write the code for each programming language.
- Write unit tests for each programming language.
- Explain what I learned.
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 🤓.
Table of Contents
- Code Challenge
- Pseudocode Solution
- Golang implementation
- Rust implementation
- Crystal implementation
- Kotlin implementation
- Elixir implementation
- Conclusion
Code Challenge: FizzBuzz
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
Golang Implementation
Solution: fizzbuzz/fizzbuzz.go
package fizzbuzz
func FizzBuzz(number int) (string, bool) {
if number%3 == 0 && number%5 == 0 {
return "FizzBuzz", true
} else if number%3 == 0 {
return "Fizz", true
} else if number%5 == 0 {
return "Buzz", true
} else {
return "", false
}
}
What I've learned
-
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
package fizzbuzz
import "testing"
func TestFizzCases(t *testing.T) {
cases := [10]int{3, 6, 9, 12, 18, 21, 24, 27, 33, 36}
for _, n := range cases {
result, _ := FizzBuzz(n)
if result != "Fizz" {
t.Errorf("Error, %v should be 'Fizz'", n)
}
}
}
func TestBuzzCases(t *testing.T) {
cases := [10]int{5, 10, 20, 25, 35, 40, 50, 55, 70, 80}
for _, n := range cases {
result, _ := FizzBuzz(n)
if result != "Buzz" {
t.Errorf("Error, %v should be 'Buzz'", n)
}
}
}
func TestFizzBuzzCases(t *testing.T) {
cases := [10]int{15, 30, 45, 60, 75, 90, 105, 120, 135, 150}
for _, n := range cases {
result, _ := FizzBuzz(n)
if result != "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 asvar 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).
Rust Implementation
Solution: fizzbuzz/src/lib.rs
pub fn fizzbuzz(number: i32) -> Option<&'static str> {
if number%3 == 0 && number%5 == 0 {
return Some("FizzBuzz");
} else if number%3 == 0 {
return Some("Fizz");
} else if number%5 == 0 {
return Some("Buzz");
} else {
return None;
}
}
What I've learned
- 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 thelib.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 beNone
. -
if
statements has no parenthesis - The type of a simple string needs the
'static
lifetime (I don't know what does that mean... yet).
Unit Tests: fizzbuzz/src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fizz_cases() {
let cases: [i32; 10] = [3, 6, 9, 12, 18, 21, 24, 27, 33, 36];
for number in cases.iter() {
let result: Option<&'static str> = fizzbuzz(*number);
if let Some(text) = result {
assert_eq!(text, "Fizz");
} else {
assert!(false);
}
}
}
#[test]
fn buzz_cases() {
let cases: [i32; 10] = [5, 10, 20, 25, 35, 40, 50, 55, 70, 80];
for number in cases.iter() {
let result: Option<&'static str> = fizzbuzz(*number);
if let Some(text) = result {
assert_eq!(text, "Buzz");
} else {
assert!(false);
}
}
}
#[test]
fn fizzbuzz_cases() {
let cases: [i32; 10] = [15, 30, 45, 60, 75, 90, 105, 120, 135, 150];
for number in cases.iter() {
let result: Option<&'static str> = fizzbuzz(*number);
if let Some(text) = result {
assert_eq!(text, "FizzBuzz");
} else {
assert!(false);
}
}
}
}
What I've learned
-
#[whatever]
is an attribute, useful to add metadata to a module, crate or item. In this case the module is usingcfg
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 (unlesspub
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, soarray.iter()
returns an Iterator. - Maybe you are wondering why by calling
fizzbuzz
function I need to use*number
and notnumber
, well that's becausearray.iter()
returns an Iterator with references (so, number variable type is&i32
and noti32
).
Crystal Implementation
Solution: fizzbuzz/fizzbuzz.cr
module FizzBuzz
def fizzbuzz(number)
if number%3 == 0 && number%5 == 0
return "FizzBuzz"
elsif number%3 == 0
return "Fizz"
elsif number%5 == 0
return "Buzz"
else
return nil
end
end
end
What I've learned
- Syntax is very similar to Python and Ruby.
- 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"
include FizzBuzz
describe "FizzBuzz" do
it "should be 'Fizz' for multiples of 3" do
[3, 6, 9, 12, 18, 21, 24, 27, 33, 36].each do |number|
result = fizzbuzz(number)
result.should eq("Fizz")
end
end
it "should be 'Buzz' for multiples of 5" do
[5, 10, 20, 25, 35, 40, 50, 55, 70, 80].each do |number|
result = fizzbuzz(number)
result.should eq("Buzz")
end
end
it "should be 'FizzBuzz' for multiples of 3 and 5" do
[15, 30, 45, 60, 75, 90, 105, 120, 135, 150].each do |number|
result = fizzbuzz(number)
result.should eq("FizzBuzz")
end
end
end
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 likeFizzbuzz::fizzbuzz
without it. - Test look very similar to Mocha Framework in Javascript.
Kotlin Solution
Configuration: fizzbuzz/gradle.build
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}
repositories {
mavenCentral()
}
dependencies {
implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
testImplementation('org.junit.jupiter:junit-jupiter-api:5.4.2')
testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}
test {
useJUnitPlatform()
}
What I've learned
- Gradle configuration is just like any Java project, meh.
- I tried to use built-in Test suite but I couldn't find out how, so I ended up using JUnit 5.
Solution: fizzbuzz/src/main/kotlin/FizzBuzz.kt
fun fizzbuzz(number: Int): String? {
if (number%3 == 0 && number%5 == 0) {
return "FizzBuzz"
} else if (number%3 == 0) {
return "Fizz"
} else if (number%5 == 0) {
return "Buzz"
} else {
return null
}
}
What I've learned
- 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
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
class FizzBuzzTest {
@Test
@DisplayName("It should be 'Fizz' for multiples of 3")
fun shouldBeFizzForMultiplesOfThree() {
val cases: 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")
fun shouldBeBuzzForMultiplesOfFive() {
val cases: 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")
fun shouldBeFizzBuzzForMultiplesOfThreeAndFive() {
val cases: 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 isIntArray
. -
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 namedit
by default.
Elixir Solution
Solution: fizzbuzz/lib/fizzbuzz.ex
defmodule Fizzbuzz do
def fizzbuzz(number) do
cond do
rem(number, 3) == 0 && rem(number, 5) == 0 -> "FizzBuzz"
rem(number, 3) == 0 -> "Fizz"
rem(number, 5) == 0 -> "Buzz"
true -> nil
end
end
end
What I've learned
-
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 aswitch
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.
defmodule FizzbuzzTest do
use ExUnit.Case
doctest Fizzbuzz
test "should be 'Fizz' for multiples of 3" do
Enum.each([3, 6, 9, 12, 18, 21, 24, 27, 33, 36], fn x ->
assert(Fizzbuzz.fizzbuzz(x) == "Fizz")
end)
end
test "should be 'Buzz' for multiples of 5" do
Enum.each([5, 10, 20, 25, 35, 40, 50, 55, 70, 80], fn x ->
assert(Fizzbuzz.fizzbuzz(x) == "Buzz")
end)
end
test "should be 'FizzBuzz' for multiples of 3 and 5" do
Enum.each([15, 30, 45, 60, 75, 90, 105, 120, 135, 150], fn x ->
assert(Fizzbuzz.fizzbuzz(x) == "FizzBuzz")
end)
end
end
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? 🤓.
Here is the Github repository:
jorgeramon / learning-languages
Repository of "Learning Programming Languages with Code Challenges" post series.
Learning Programming Languages with Code Challenges
Programming Languages
Challenges
- Fizz Buzz
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".
Top comments (0)