Monday, August 14, 2023

Haskell Kaiseki Cookbook

A Haskell Kaiseki Cookbook: Project-Focused Microtutorials for Complete Beginners To Haskell



   


Kaiseki meal, taken from Wikimedia. Attribution "663highland". https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Fuyoen99st3200.jpg/2880px-Fuyoen99st3200.jpg


    Executive Summary


-Haskell Kaiseki Cookbook(HKC) aims to index practical, project-oriented tutorials for Haskell.

-HKC also aims to provide its own project-oriented tutorials itself, with a focus on the project, not on Haskell itself, to help demonstrate what realistic Haskell projects look like, provide quickstarts for Haskell, help learners through the projects phase of Haskell learning, and provide a minimum tutorial guide to the Haskell library ecosystem.

-HKC is soliciting contributions from Haskellers to help populate the cookbook.


Foreword for Contributors


    It used to be that there was a dearth of Haskell learning materials and the primary way to learn Haskell was via academic papers, or "A Gentle Introduction to Haskell" hosted on Haskell.org. As interest in Haskell grew, over the past 10-15 years, we've seen a profusion of Haskell learning materials hit the market such that the total Haskell bibliography easily exceeds 20-30 books, especially if you consider publications made through less rigorous publishing houses.


    Yet despite this growth in the Haskell publishing industry, Haskell continues to be seen as forbidding, strange and difficult. Haskell onboarding times, while tolerable, are seen to be unacceptably high for production use, and Haskell still retains a reputation as academic and impractical.

    Moreover, quite a few learners complain about the difficulty in bridging the gap from learning Haskell the language, as seen in books like Haskell Programming from First Principles, to making quality Haskell projects, often leading to the end of interest in Haskell.

    There are books that can help, such as Effective Haskell and Haskell in Depth, but when you've gone through 1200 pages of Haskell Programming from First Principles, another textbook of 600 some pages can be an arduous task.

    Lastly, it is often challenging to figure out which libraries to use, and when the libraries are available, they have varying levels of documentation; while we are past the "types are sufficient docs" era, even when libraries are documented, they may not have examples, may have documentation too intimidating to newbies, or may not have accessible tutorials.


    The Haskell Kaiseki Cookbook (HKC) tries to address these problems by providing a set of microproject microtutorials, i.e, it seeks to provide a catalog of realistic, small example projects that both demonstrate what it's like to work in Haskell, while being accessible to the absolute beginner, teaching only the amount of Haskell necessary to get a particular project working. It is inspired by tutorials like "Haskell Phrasebook", "Learn Haskell by Building Yourself a Blog Generator", Python books like Impractical Python Projects and The Big Book of Small Python Projects, as well as the Japanese Kaiseki meal, composed of many small courses, artfully presented with care to appearance, taste, and quality of ingredients.


    The project is aimed at four separate use cases. First, as with Kaiseki, the cookbook is intended as an advertising flyer, i.e, many Haskellers would like to use Haskell at work, and at least the online version of HKC is intended to demonstrate to IT managers that Haskell is a pragmatic, expressive language suited for production use, providing short, readable, and maintainable programs that are also highly bug-resistant due to the combination of powerful (strong and expressive) types and functional programming.


    Second, there is a potential subset of Haskell learners who have a hands-on learning style, and would rather do before they read through the documentation or theory. This book is intended to make onboarding simpler for them; i.e, provide small, guided projects they can implement themselves and extend. An important difference, of course, between HKC and existing tutorials is that HKC is oriented toward working in Haskell, not teaching Haskell. Providing a strong understanding of the language, the theory, and functional programming is outside the purview of HKC; there are many excellent tutorials that can teach Haskell in a more conventional way, and as we will see with the third and fourth audiences, HKC is intended as a supplement, not the main course.


    Third, HKC is intended to, like Python project books, help learners who have already gone through existing Haskell introductions and books, go through the project stage of their Haskell journey. HKC will provide multiple, small projects to provide familiarity with the Haskell ecosystem and how to utilize them for effective, beautiful programs.


    Fourth, given the variable level of library documentation (some libraries have full docs, the issues for others are handled via, well, the Github issues page), HKC is also intended as a basic ecosystem guide, and provide fast and accessible tutorials for basic use of various Haskell libraries.


***


    Of course, here, HKC is described as a project, not a book. In reality, there are already many project-oriented Haskell tutorials already available, such as those on Haskell at Work, Monday Morning Haskell, and Typeclasses. One part of this is that HKC will be aiming to provide a directory of existing Haskell tutorials, spread all across the blogosphere, some being paid products, and others being freely accessible.

    The other part is that HKC is intended to be a joint project of many Haskellers, soliciting contributions from production and Haskell hobbyists, working to some degree within the specification (project-oriented, not fully Haskell-oriented) provided by HKC, with HKC recipes spread throughout the blogosphere.

    A partial model might be the Clojure Cookbook, which involves over 70 different contributors and 200 different examples. In contrast to Clojure Cookbook, however, HKC aims to provide projects, not a phrasebook, although brief blogs on various techniques can be included.

    As to whether this might one day become a book, yes, but only if HKC reaches sufficient maturity, and more importantly, a reputable Haskell organization (Haskell.org, Haskell Foundation) would be willing to adopt it and contact the authors for rights to editing and redistribution.


***


    So at this stage, basically, HKC is a request for contribution. I (Liam Zhu) won't launch this myself until I've built together some core recipes / tutorials and brought them up to standards. I'm thinking that matching the Big Book of Small Python Projects might be a useful initial target, since the book contains 81 projects. I myself would like to have 9 projects prepared myself before I bring this public, and writing this foreword is, well, my personal starting point and a design spec for what is intended to follow.



Haskell logo, taken from numi's website.

Friday, July 7, 2023

Translation of Vigenere Cipher from "The Big Book of Small Python Projects" into Haskell

 Original is here:


https://inventwithpython.com/bigbookpython/project80.html


Haskell version:


{-| Vigniere Cipher console program based on the version in _The Big Book of

Small Python Projects_ by Al Sweigart. -}

{-# LANGUAGE LambdaCase #-} -- Used to enable LambdaCase syntax to simplify a code section.


module Main where


import Data.Char (chr, isAlpha, isUpper, ord, toUpper)

import System.Clipboard (setClipboardString)

import Data.List (mapAccumL) -- used to map while holding an accumulator

import Data.Foldable (traverse_) -- used to turn a list into an fstring-like structure


type VigenereKey = [Int] -- ^ Type synonym for the key structure (list of keys) to make it a bit more readable.


makeKey :: String -> VigenereKey -- ^ Converts a string to a list of offsets

makeKey = fmap (subtract 65 . ord . toUpper) . filter isAlpha


data VigDirection = Encrypt | Decrypt -- ^ Enum type to avoid using strings; idiomatic.


encrypt, decrypt :: VigenereKey -> String -> String -- ^ Convenient synonyms to avoid directly using applyKey.

encrypt = applyKey Encrypt; decrypt = applyKey Decrypt


{-| Actual encryption decryption function, takes advantage of the fact that

encryption and decryption are just one function apart. -}


applyKey :: VigDirection -> VigenereKey -> String -> String

applyKey vigDirection [] text = text

applyKey vigDirection key text =


{-mapAccumL allows us to map with an accumulator, which is basically a mutable variable,

from left to right with the accumulator being updated by the mapping function.

The result is a tuple of the final accumulator and the mapped data structure.

Haskell's pervasive laziness allows me to use an infinite list generated by cycle as an accumulator!-}


    snd $ mapAccumL adjustAlphaNumerics (cycle key) text

  where

    adjustAlphaNumerics keySpool@(keyShift:restOfKeys) character

      | not $ isAlpha character = (keySpool, character) -- Keep the accumulator unchanged, return character.

      | otherwise =                                     -- take the first element off the accumulator, encrypt / decrypt the character

          ( restOfKeys

          , chr . (+ shiftFactor) . flip mod 26

          . shiftEncryptOrDecrypt . subtract shiftFactor

          $ ord character)

      where

        shiftFactor

            | isUpper character = 65

            | otherwise         = 97

        shiftEncryptOrDecrypt = case vigDirection of

            Encrypt -> (+ keyShift)

            Decrypt -> subtract keyShift


main :: IO () -- ^ Actual user-facing code. "Imperative shell, functional core."

main = do -- Build a basic schematic of the program in main.

    introduction

    getAndProcessInputs >>= -- Bind to allow the following function to act directly

                            -- on the output of the preceding IO action.

        showResultsAndCopyToClipboard

  where -- fill out the details in the where clause.

    introduction = putStrLn

        "Haskell Vigenere Cipher, by Liam Zhu Liam.Zhu@protonmail.com\n\

        \Adapted from Vigenere Cipher in _The Big Book of Small Python\n\

        \Projects_ by Al Sweigart al@inventwithpython.com.\n\

        \The Vigenere cipher is a polyalphabetic substitution cipher that was\n\

        \powerful enough to remain unbroken for centuries."


    getAndProcessInputs = do

        programMode <- getProgramMode

        key  <- makeKey <$> entryPrompt "Please specify the key to use.\n\

                                        \It can be a word or any combination of letters."

        text <- entryPrompt $ "Enter the message you wish to " <> case programMode of

            Encrypt -> "encrypt."

            Decrypt -> "decrypt."

        let processedMessage = (case programMode of

                Encrypt -> encrypt

                Decrypt -> decrypt) key text

        pure (programMode, processedMessage)


    {-| Here, we need a separate definition for getProgramMode since it loops/recurses into itself.

    We direct bind the LambdaCase for concision here; the LambdaCase reads the result and

    chooses either to return a Encrypt or Decrypt enum, or show an error and loop back into itself.-}


    getProgramMode = entryPrompt "Do you want to (e)ncrypt or (d)ecrypt?" >>= \case

          "e" -> pure Encrypt

          "d" -> pure Decrypt

          _   -> do

              putStrLn "Unrecognized input."

              getProgramMode


    showResultsAndCopyToClipboard (programMode, processedMessage) = do

        setClipboardString processedMessage

        traverse_ putStrLn -- traverse is used here to apply putStrLn to a list, which would

                           -- be an f-string in Python.

            [ case programMode of; Encrypt -> "Encrypted message:"; Decrypt -> "Decrypted message:"

            , processedMessage

            , "Full " <> (case programMode of; Encrypt -> "encrypted"; Decrypt -> "decrypted")

                      <> " text copied to clipboard."

            ]


entryPrompt :: String -> IO String -- ^ Supporting prompt function.

entryPrompt str = do

    putStrLn str

    putStr "> "

    getLine


A relatively simple program, with the user-facing IO section being quite large, helped in part by the "pure" section being tiny. I took the liberty of choosing a more Haskelly idiom with the main being outlined and then defined through the where clause over a Pythonic block-oriented approach.


Even then, because of the complexity of the Vigenere enciphering code, we still end up beating the Python version by around 5-10%, with more verbose comments to make clear some unique Haskell features / idioms.


Haskell in general can easily beat out Python when it comes to data transformation code, but starts to lag when it comes to the IO layer (imperative side), especially when you take a more principled Haskell approach, name your blocks, and build an outline.

Haskell Kaiseki Cookbook

A Haskell Kaiseki Cookbook: Project-Focused Microtutorials for Complete Beginners To Haskell     Kaiseki meal, taken from Wikimedia. Attribu...