top of page
  • Youtube
  • Black Instagram Icon
  • Black Facebook Icon

Why I love Haskell as a Java engineer

Picture this: A Java engineer walks into a bar. They start declaring interfaces, implementing abstract classes, and setting up a factory pattern just to order a drink. Meanwhile, a Haskell programmer in the corner just wrote getDrink :: Int -> [Beverage] and called it a day.


Haskell programmer battling Java programmer


Hi, I'm that Java engineer who fell down the Haskell rabbit hole, and I'm here to tell you why I've been sneaking functional programming concepts into my enterprise codebase like a teenager smuggling candy into a movie theater.


I've always appreciated how Java code is written; its verbosity ensures that, when done correctly, it can be comprehended by everyone from Senior Staff Engineers to Interns. This is Java's charm. However, I must admit, its verbosity can become tedious, particularly when experimenting with new features or products, or even when just enjoying some time on HackerRank (feel free to judge me).


Today, I would like to present a few code snippets for similar Hacker Rank problems in both Java and Haskell, and then compare them.


Haskell Problems overview


Yeah, let's start easy. There are 3 problems on Hacker Rank that ask you to do this. I'll also add a stretch just for fun. And here they are:


Solve me first


This is a very simple problem that any software engineer needs to be able solve without thinking. Here is the code in Java that solves this problem.

public class Solution {
	public static void main(String args[]) {
		Scanner scanner = new Scanner(System.in);
        int first = scanner.nextInt();
        int second = scanner.nextInt();
        scanner.close();

		System.out.println(first + second);
    }
}

This is a Haskell one-liner (well, almost):

main = interact 
	 $ show . sum . map (read :: String -> Integer) . words

Initially, this may appear shorter, but Java's structure enables the code to be more strict with the input and output format. For instance, if we provide the following input to both programs, they will produce different outputs:

1 2 3
Java will return 3 and Haskell will return 6. Can you tell why?

Clearly, our Haskell code lacks input validation, resulting in it reading all integers from the input and returning their sum. Let's explore how we can address this issue:

main = interact 
	 $ show 
	 . sum 
	 . map (read :: String -> Integer) 
	 . take 2
	 . words

This will guarantee that we only read the first two numbers from the input (as Java would do by default). However, for now, let's retain the initial code and explore how we can solve the second problem.


Simple Array Sum


Probably the next easiest problem to solve in HackerRank, here is the Java code for it:


public class Solution {
	public static void main(String args[]) {
		Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
	    int sum = 0;
        for (int i = 0; i < n; i ++) {
			int value = scanner.nextInt();
			sum += value;
		}
        scanner.close();

		System.out.println(sum);
    }
}

Becoming somewhat complex, isn't it? Let's examine the Haskell code from the earlier example and see if we can modify it slightly to address this issue too (since, if you recall, the original code was already adding up all the values in the input).


This is the original code.

main = interact 
	 $ show . sum . map (read :: String -> Integer) . words

To modify it, we only need to drop the first element (the size of the list) from the list of words. And there's a method in Haskell for that (called tail).

main = interact 
	 $ show . sum . map (read :: String -> Integer) . tail . words

This program will calculate the sum of all integers in the input except for the first one. Exactly what we wanted!


Do you think the next problem would be much different from this one? Let's see.


A Very Big Sum


This is an instance where Java's type system poses a limitation. The int type in Java can only accommodate values up to 32 bits, and even the long type is insufficient as it can only handle up to 64 bits. A larger integer type is necessary. BigInteger for the rescue (I guess?).


Here is the java code for this problem:


public class Solution {
	public static void main(String args[]) {
		Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        String line = scanner.nextLine();
		String[] tokens = line.split(" ");

		BigInteger sum = BigInteger.valueOf(0);
        for (int i = 0; i < n; i ++) {
			BigInteger value = new BigInteger(tokens[i]);
			sum = sum.add(value);
		}
        scanner.close();

		System.out.println(sum);
    }
}

This is starting to get ugly. Let's see the Haskell code for this problem:

main = interact 
	 $ show . sum . map (read :: String -> Integer) . tail . words

Seems quite familiar, doesn't it? That's because it's the identical code used in the prior problem. In Haskell, the Integer type corresponds to BigInteger in Java, allowing it to store a number as large as your computer's memory capacity permits.


Something we haven't addressed in the previous problem is that we still don't validate our input. This can quickly become problematic if we're not cautious. Let's modify the code to ensure it only retrieves the first n elements from the input.

takeFirstN :: [Int] -> [Int]
takeFirstN (x:xs) = take x xs

main = interact 
	 $ show 
	 . sum 
	 . takeFirstN 
	 . map (read :: String -> Integer) 
	 . words

This will give us some assurance that the input is valid.

There is a bug in the Java code. Can you find it? Comment below.

Now that you found it, let's get to the bonus problem.


Project Euler #13: Large sum


Same problem as above actually. But now we only need to return the first 10 digits of the number. Let's see the Java implementation:

public class Solution {
	public static void main(String args[]) {
		Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
		BigInteger sum = BigInteger.valueOf(0);
        for (int i = 0; i < n; i ++) {
			String line = scanner.nextLine();
			BigInteger value = new BigInteger(line);
			sum = sum.add(value);
		}
        scanner.close();

		System.out.println(sum.toString().substring(0, 10));
    }
}

Simple, now let's see the Haskell code again (yes, almost the same one).


main = interact 
	 $ show 
	 . take 10 
	 . sum 
	 . map (read :: String -> Integer) 
	 . tail 
	 . words

Just beautiful!


Conclusion


Haskell is an excellent tool for experimenting with new features and rejuvenating your weary mind after a long day of coding in Java. It's enjoyable and helps keep your brain agile by learning new ways of thinking. Try it out if you enjoy using HackerRank or any problem-solving websites that support Haskell. You'll be amazed at how much fun you can have by simply adding a small change to your daily routine.

Comments


© 2024 Silviu Popescu

  • Facebook
  • Instagram
  • LinkedIn
bottom of page