Saturday, October 19, 2019

Learning me a Haskell

I was doing more katas on Code Wars this afternoon and came across this one called Isograms. I quickly created a solution in Java which did the thing it needed to do.


    public static boolean isIsogram(final String str) {

        if (str == null) {
            throw new IllegalArgumentException(
               "str parameter may not be null."
            );
        }

        if ("".equals(str)) {
            return  true;
        }

        final String lowerCase = str.toLowerCase();
        final char[] chars = lowerCase.toCharArray();

        final List charactersFound = new ArrayList();

        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            if (charactersFound.contains(c)) {
                return false;
            } else  {
                charactersFound.add(c);
            }
        }

        return true;
    }


Not pretty, not concise, but it gets the job done. I knew that it could be solved far more concisely using streams, so I switched to Kotlin and wrote this.


    fun isIsogram(str: String): Boolean {

        if ("" == str) {
            return true
        }

        val distinctCharacters = str.toLowerCase()
                  .asSequence().distinct().count()

        return str.length == distinctCharacters

    }


I'm pretty sure something similar could be done in Java 8+, but I've been developing in Kotlin almost exclusively for almost two years now, so it's my go-to language for the JVM.

I've trained myself over the years to avoid function chaining so that one line makes my brain itch a bit. However, I think this implementation is much more expressive and concise than what I wrote in Java.

One of the reasons I like Kotlin is the implicit null safety. In the Kotlin solution, I didn't have to protect the function from a null argument value. It makes the solution more concise and easier to parse mentally. If I wanted to allow null as a value, I'd have to add special syntax to support it. That extra step always makes me stop and think is there a better way than allowing a null input and I can typically find a better way.

Using the stream features in my Kotlin solution reminded me that I wanted to learn more about Functional Programming. Code Wars offered a Haskell version of the kata, so I decided to give that a shot.

Unfortunately, I don't know anything about Haskell. Not even how to spell it. I always want to have just one L.

Fortunately, I listened to a lot of the early episodes of The Bikeshed and had heard Sean and Derek mention Miran Lipovaca's Learn You A Haskell website many times.

I started going through the content and found it very accessible. It's a good balance of explanation and examples. Pretty soon I was taking the individual topics and combining them to solve new problems.

For example, during the section on list operations, I was trying to write a function that would remove prune one list from another. That is, given lists A and B return A with everything in B removed. I couldn't find a solution using just the set operators, but once I got to the section on list operations a light bulb went of. After some trial and error, I came up with this:


    filterOut s f  = [ x | x <- s, not (elem x f) ]


For example:

ghci> filterOut ['A'..'Z'] ['I','P']
"ABCDEFGHJKLMNOQRSTUVWXYZ"


And, yes. Deep down inside I still find 3rd grader humor potty funny.

I still don't know enough to solve the Isogram problem in Haskell, but I'm getting close.

No comments:

Post a Comment