Writing Haskell with Chat GPT

In our last couple of articles, we've asked Chat GPT a bunch of questions about Haskell and functional programming. Today we're going to do one more exploration to see what kinds of Haskell code this chatbot can write.

Hello World

We'll start with a simple "Hello World" program.

How do I write a "Hello World" program in Haskell?

Chat GPT:

To write a "Hello World" program in Haskell, you can use the putStrLn function from the Prelude, which is Haskell's default library. This function takes a string as an argument and prints it to the console. Here's an example of a simple "Hello World" program in Haskell:

main :: IO ()
main = putStrLn "Hello, World!"

This program defines a function main that has the type IO (). The IO type is used for actions that perform input/output, and the () type represents the empty tuple, which has no contents. The main function is special in Haskell because it is the entry point of every executable Haskell program. When the program is run, the main function will be called and the string "Hello, World!" will be printed to the console.

You can compile and run this program using the ghc compiler, which is included with the Haskell Platform. First, save the program to a file named HelloWorld.hs. Then, open a terminal and navigate to the directory where you saved the file. To compile the program, type the following command:

ghc HelloWorld.hs

This will produce an executable file named HelloWorld. To run the program, type the following command:

./HelloWorld

This will print "Hello, World!" to the console.

Commentary

This not only gives the code to run the program, it also gives detailed instructions for running it using GHC. Recommending Stack and GHCup would be better advice for beginners though. I'm sure it would know how to describe using these tools if asked about them. But it would be best if it knew to recommend these tools to someone who didn't know about them. Still, I'm nit-picking a bit here.

Sorting Algorithms

So a "Hello World" program is one of the simplest things you can do though. How does ChatGPT do with writing some algorithmic code? Let's start with a couple simple sorting algorithms, quicksort and mergesort.

How can I write quicksort in Haskell?

Chat GPT:

Here is an implementation of quicksort in Haskell:

quicksort :: Ord a => [a] -> [a]
quicksort [] = []
quicksort (x:xs) = quicksort [y | y <- xs, y <= x] ++ [x] ++ quicksort [y | y <- xs, y > x]

This function defines a quicksort that sorts a list of elements that are part of the Ord type class (which includes types like Int, Char, and Double). The function uses a recursive approach to sort the list.

The base case of the recursion is the empty list, which is already sorted. For the recursive case, the function divides the list into three parts:

  1. A sublist of elements that are less than or equal to the pivot element (x)
  2. The pivot element itself
  3. A sublist of elements that are greater than the pivot element

The function then recursively sorts the first and third sublists, and concatenates the sorted sublists with the pivot element to produce the final sorted list.

You can test this implementation by calling quicksort with a list of elements:

quicksort [4, 6, 9, 8, 3, 5, 1, 7, 2]

This should return the sorted list [1, 2, 3, 4, 5, 6, 7, 8, 9].

How can I write mergesort in Haskell?

Chat GPT:

Here is an implementation of mergesort in Haskell:

merge :: Ord a => [a] -> [a] -> [a]
merge xs [] = xs
merge [] ys = ys
merge (x:xs) (y:ys)
  | x <= y    = x : merge xs (y:ys)
  | otherwise = y : merge (x:xs) ys

mergesort :: Ord a => [a] -> [a]
mergesort []  = []
mergesort [x] = [x]
mergesort xs  = merge (mergesort left) (mergesort right)
  where
    mid   = length xs `div` 2
    left  = take mid xs
    right = drop mid xs

This implementation defines a merge function that merges two sorted lists into a single sorted list, and a mergesort function that sorts a list using the merge sort algorithm.

The merge function works by comparing the head elements of the two input lists, and adding the smaller of the two to the result list. It repeats this process until one of the input lists is empty, at which point it appends the remaining elements of the other list to the result.

The mergesort function works by dividing the input list into two halves, sorting each half using a recursive call to mergesort, and then merging the two sorted halves using the merge function.

You can test this implementation by calling mergesort with a list of elements:

mergesort [4, 6, 9, 8, 3, 5, 1, 7, 2]

This should return the sorted list [1, 2, 3, 4, 5, 6, 7, 8, 9].

Commentary

For quicksort, it comes up with a simple 3-line solution, and the mergesort solution is also quite succinct. However, one change I would make to the solution is to tail recursion. As with the "Hello, World" example though, the descriptions are very helpful for someone just learning the language.

Heap Sort

Quicksort and mergesort are one thing. These algorithms have fairly straightforward recursive implementations that work well in Haskell. What about heapsort?

How can I write heapsort in Haskell?

Chat GPT:

Here is an implementation of heapsort in Haskell:

heapify :: Ord a => [a] -> [a]
heapify xs = heapify' (length xs `div` 2 - 1) xs
  where
    heapify' (-1) xs = xs
    heapify' i xs = heapify' (i - 1) (heapifyOne i xs)

heapifyOne :: Ord a => Int -> [a] -> [a]
heapifyOne i xs = case cmp of
                    GT -> xs
                    _  -> heapifyOne' i cmp xs
  where
    cmp = compare (xs !! i) (xs !! child)
    child = if r < length xs && xs !! r > xs !! l then r else l
    l = 2 * i + 1
    r = 2 * i + 2

heapifyOne' i GT xs = xs
heapifyOne' i _  xs = heapifyOne (if r < length xs && xs !! r > xs !! l then r else l) $ swap i (if r < length xs && xs !! r > xs !! l then r else l) xs
  where
    l = 2 * i + 1
    r = 2 * i + 2

swap i j xs = [if k == i then xs !! j else if k == j then xs !! i else xs !! k | k <- [0..length xs - 1]]

sort :: Ord a => [a] -> [a]
sort [] = []
sort xs = y : sort ys
  where
    (y:ys) = heapify xs

This implementation defines a heapify function that turns a list into a heap, and a sort function that sorts a list using the heap sort algorithm.

The heapify function works by recursively transforming the list into a heap, starting from the middle of the list and working towards the beginning. It does this by repeatedly calling the heapifyOne function, which restores the heap property to a single element and its children in the heap.

The sort function works by repeatedly extracting the maximum element (the root of the heap) and placing it at the end of the result list, until the heap is empty.

You can test this implementation by calling sort with a list of elements:

sort [4, 6, 9, 8, 3, 5, 1, 7, 2]

This should return the sorted list [1, 2, 3, 4, 5, 6, 7, 8, 9].

Commentary

Very impressive, especially with the detailed description! This time, we do see tail recursion applied in the heapify function.

List Functions

How does it do with a more abstract prompt? I came up with this question based on Day 1 of the recently concluded 2022 Advent of Code.

Write a Haskell function to solve the following problem. The input is a list of lists of integers. We want to find the three lists with the largest sums, and add their sums together. So the output is just an integer.

Chat GPT:

Here is a function that solves the problem you described:

largestThreeSums :: [[Int]] -> Int
largestThreeSums xs = sum $ take 3 $ sortBy (flip compare) $ map sum xs

This function first applies the sum function to each sublist in xs, producing a list of sums. It then sorts this list in descending order using the sortBy function, and takes the first three elements using the take function. Finally, it sums these three elements using the sum function and returns the result.

To use this function, you can call it with a list of lists of integers as an argument:

largestThreeSums [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

This should return the result 33, which is the sum of the three largest sublists [7, 8, 9], [10, 11, 12], and [4, 5, 6].

Commentary

This is quite similar to my solution! So Chat GPT did an effective job parsing my description, and determining the right list functions to use to make an efficient, one-line solution.

Making a Server

For one more example, let's try doing something more practical, like a web server. This will require using libraries beyond Haskell's base packages. Once again, this works quite well!

How can I write a basic HTTP server in Haskell?

Chat GPT:

Here is an example of a basic HTTP server in Haskell using the wai and wai-extra libraries:

{-# LANGUAGE OverloadedStrings #-}

import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types (status200)
import Blaze.ByteString.Builder (copyByteString)
import qualified Data.ByteString.UTF8 as BU

app :: Application
app request respond = respond $
    case rawPathInfo request of
        "/hello" -> hello
        _        -> notFound
  where
    hello = responseBuilder
        status200
        [("Content-Type", "text/plain")]
        (copyByteString "Hello, World!")
    notFound = responseBuilder
        status404
        [("Content-Type", "text/plain")]
        (copyByteString "Not Found")

main :: IO ()
main = do
    putStrLn "Listening on port 3000"
    run 3000 app

This server listens for incoming HTTP requests on port 3000, and responds with a "Hello, World!" message for requests to the /hello path, and a "Not Found" message for all other paths.

To run this server, save the code to a file and compile it using the ghc compiler:

ghc Server.hs

This will produce an executable file named Server. To start the server, run the executable:

./Server

You should see the message "Listening on port 3000" printed to the console. You can then send HTTP requests to the server using a tool like curl or by opening a web browser and going to http://localhost:3000/hello. You should receive a "Hello, World!" response for the /hello path, and a "Not Found" response for all other paths.

Conclusion

So overall, Chat GPT does quite well with these basic challenges! It would be interesting to take this further still and see if we could make our server program more and more complex, like adding custom functionality for different routes. But Chat GPT definitely seems useful enough to help with basic tasks, even in a less well-known language like Haskell!

As a final note, I did check (out of curiosity) to see if Chat GPT seemed to know much about this blog. The answer appeared to be a resounding "no", leading to a good laugh for me! I asked, "do you know about Monday Morning Haskell?" This was the response I got from Chat GPT (emphasis is mine):

Yes, I am familiar with the term "Monday Morning Haskell." It refers to a programming style or approach that involves using the Haskell programming language to solve problems or write software on a Monday morning, with the implication being that Haskell is a particularly well-suited language for starting the work week off with a clear and focused mind. Haskell is a functional programming language known for its strong static type system, expressive type inference, and purity (lack of side effects). Many developers find that programming in Haskell can help them write clearer, more concise, and more maintainable code, and that it is especially well-suited for tasks that require reasoning about abstract concepts or dealing with complex data.

I might just have to add that description to the home page!

Previous
Previous

Advent of Code: Days 17 & 18 Videos

Next
Next

Advent of Code Video Catchup Begins!