Learn Haskell — 1 Link to heading
So, I am going to attempt to learn Haskell, yet again. I have tried a few times in the past, but every time it was an utter failure. This time, I am going to write articles as my learning notes to force myself keep going. If you are also interested in learning Haskell, join the journey with me!
First, like ever other programming language, we need to start with HelloWorld. What is fun in learning a new programming language if you don’t get to run it? Create hello.hs file as below
-- hello.hs
main = putStrLn "hello, world"
and let’s compile and run!
$ ghc --make hello
[1 of 1] Compiling Main ( hello.hs, hello.o )
Linking hello ...
$ ./hello
hello, world
Now that we are able to print out, let’s also read from stdin:
-- name.hs
main = do
putStrLn "hello, what is your name?"
name <- getLine
putStrLn ("welcome " ++ name ++ "!")
Again, let’s compile and run
$ ghc --make name
$ echo "hara" | ./name
hello, what is your name?
welcome hara!
So far so good. Let’s spice our program a bit more
-- lower_upper.hs
import Data.Char
main = do
putStrLn "your first name?"
first_name <- getLine
putStrLn "your last name?"
last_name <- getLine
let first_name_lower = map toLower first_name
let last_name_upper = map toUpper last_name
putStrLn ("hello " ++ first_name_lower ++ " " ++ last_name_upper)
Compile and run!
$ echo -e "tech\nhara" | ./lower_upper
your first name?
your last name?
hello tech HARA
Let’s do something more difficult. Read input line by line and reverse the characters within each word of the line and print.
-- reverse.hs
main = do
line <- getLine
if null line
then return ()
else do
putStrLn (reverseWords line)
main
reverseWords :: String -> String
reverseWords = unwords . map reverse . words
Here is interpretation of the program (with help from Bing Chat!)
mainis the entry point of the program. It contains adoblock which allows for sequencing of actions.line <- getLinereads a line of text from the standard input (usually the keyboard) and binds it to the variableline.if null line then return () else dochecks if the line is empty. If it is, the program does nothing and returns(), which is the unit type, signifying no meaningful value (similar tovoidin other languages). If the line is not empty, it proceeds to theelseblock.putStrLn (reverseWords line)calls thereverseWordsfunction withlineas an argument and prints the result.mainat the end of theelseblock is a recursive call to themainfunction, causing the program to start over and read another line.
The reverseWords function is defined as follows:
reverseWords :: String -> Stringis the type signature of the function, indicating that it takes aStringas input and returns aStringas output.reverseWords = unwords . map reverse . wordsis the function definition. It uses function composition (.) to chain together three functions:wordssplits the input string into a list of words.map reverseapplies thereversefunction to each word in the list, reversing them.unwordstakes the list of reversed words and concatenates them back into a single string with spaces between words.
Let’s run and test it
$ ./reverse < reverse.hs
-- sh.esrever
niam = od
enil <- eniLteg
fi llun enil
neht nruter )(
esle od
nLrtStup sdroWesrever( )enil
niam
sdroWesrever :: gnirtS -> gnirtS
sdroWesrever = sdrownu . pam esrever . sdrow
I am actually liking Haskell! The code is concise and to the point! Just for comparison, here is equivalent code in Rust
use std::io::{stdin, BufRead, Result};
fn main() -> Result<()> {
let input = stdin().lock();
let iter = input.lines().map(|line| line.map(reverse_words));
for line in iter {
println!("{}", line?);
}
Ok(())
}
fn reverse_words(line: String) -> String {
line.split_whitespace()
.map(|word| word.chars().rev().collect::<String>())
.collect::<Vec<_>>()
.join(" ")
}