Could the Haskell starter template be clearer?

Hello, and thank you for your interest in my post. I would like to discuss the starter template for Haskell specifically, but there may be general interest in improving the starter templates as well.

I would like to begin by considering the purpose of the starter templates. For the examples here, I will be using the templates for Encryption/Decryption of Enigma Machine. Here’s the Python template, which is clear and helpful for the most part:

import sys
import math

# Auto-generated code below aims at helping you parse
# the standard input according to the problem statement.

operation = input()
pseudo_random_number = int(input())
for i in range(3):
    rotor = input()
message = input()

# Write an answer using print
# To debug: print("Debug messages...", file=sys.stderr, flush=True)

print("message")

The comments in the template explain its purpose. It encodes the input and output specification and describes the I/O model used to interact with the judge. More generally, I think the template is trying to minimize the potential stumbling blocks for beginner programmers trying the platform for the first time. The template is pretty concise, doesn’t do a lot more than necessary to fulfill its purpose, and uses idiomatic Python that a beginner will recognize.

We’ve finally made it to the fun part. Let’s look at the Haskell template:

import System.IO
import Control.Monad

main :: IO ()
main = do
    hSetBuffering stdout NoBuffering -- DO NOT REMOVE
    
    -- Auto-generated code below aims at helping you parse
    -- the standard input according to the problem statement.
    
    operation <- getLine
    input_line <- getLine
    let pseudorandomnumber = read input_line :: Int
    
    replicateM 3 $ do
        rotor <- getLine
        return ()
    message <- getLine
    
    -- hPutStrLn stderr "Debug messages..."
    
    -- Write answer to stdout
    putStrLn "message"
    return ()

In contrast to the Python template, this is not concise, does a lot more than necessary, and uses advanced IO functions. When I first tried Haskell on this platform, I was part of the beginner audience that the starter templates target, and I could not use it and had to wait until I had more experience. Making a good Haskell template is difficult because I/O is built on high-level abstractions in the language; there’s no shortcut for a beginner to understand it. The IO will always be hard for a beginner; however, some things can be addressed.

I make the following suggestions to improve the approachability of the template for new Haskellers.

First, to reduce the unnecessary verbosity of the template, which distracts the beginner from the important parts, and remove unnecessary behavior:

  • Rather than a big scary -- DO NOT REMOVE comment, let’s explain why it’s necessary or just remove that line. Python’s I/O is buffered by default, and the Python template doesn’t disable buffering. In the example later I’ll comment it out and add a comment explaining when it might be useful.
  • The template reads an integer in two lines, but it may look more familiar to simply use readLn.
  • Remove the unnecessary return ()s.

Finally, to try to make the IO a little more bearable:

  • Import specific symbols from System.IO and Control.Monad. New haskellers won’t know what these libraries are for, and it’s helpful to see what common functions they can use from these libraries.
  • replicateM is very foreign to a new Haskeller unfamiliar with the IO monad and its facilities. I was able to get the gist from documentation, but I couldn’t figure out how to store the rotor where I could access it in the rest of my code. I think a comment explaining how to do that is justified; the Python template doesn’t need a comment like this because beginners learn the necessary language features early on.

The result is something like this:

import System.IO (hPutStrLn, stderr)
import Control.Monad (replicateM, forM, mapM)

main :: IO ()
main = do
    -- uncomment to turn off output buffering
    -- hSetBuffering stdout NoBuffering
    
    -- Auto-generated code below aims at helping you parse
    -- the standard input according to the problem statement.
    
    pseudorandomnumber <- readLn :: IO Int
    
    -- rotors <- replicateM 3 $ do
    replicateM 3 $ do
        rotor <- getLine
       -- return rotor
    message <- getLine
    
    -- hPutStrLn stderr "Debug messages..."
    
    -- Write answer to stdout
    putStrLn "message"

If you have ideas for how to make the Haskell template more approachable for beginners, please share them! Feedback on the post in general is also welcome; this is my first post on the forum.

If you appreciated this topic, you may want to check out the this topic for Rust as well.

1 Like