proglangbase / bbhw

Bye Bye Hello World
Mozilla Public License 2.0
4 stars 4 forks source link

Haskell bye bye hello world #3

Closed lonetech closed 6 months ago

lonetech commented 6 months ago

Neat idea. Here's an example of abusing Haskell to write this program; being so IO centric, it only leaves the IO monad in a few spots, and I wrote most of it with do blocks.

#! /usr/bin/env runghc
-- Ignoring the shebang line above is a GHC extension 
-- (see 6.19.6. Whitespace)

-- LANGUAGE here is a pragma, not a comment!
{-# LANGUAGE Haskell2010, NumericUnderscores #-}
-- The NumericUnderscores extension is enabled by default in
-- GHC2021 or GHC2024, it got switched off by Haskell2010. 

-- GHC specific delay. 
-- There is no standard delay, and the POSIX ones may be interrupted.
import Control.Concurrent (threadDelay)

import Control.Monad (forM_)
import Numeric (readDec)
import System.Environment (getArgs)
import System.IO (stdout, hSetBuffering, BufferMode(NoBuffering))

-- Sequential do style; this program is heavily IO centric.
main = do
  let   -- could move these functions out of main
    ensureCount s = case readDec s of
      [(n,"")] -> return n
      _ -> do
        putStrLn ("Invalid countdown "++s++", try again...")
        demandCount
    demandCount = do
      putStr "Count? "
      s <- getLine
      ensureCount s
  args <- getArgs
  hSetBuffering stdout NoBuffering
  count <- case args of
    [arg] -> ensureCount arg
    _     -> demandCount
  putStr "World, Hello..."
  forM_ [count, count-1 .. 1] $ \i -> do
    putStr (show i ++ "...")
    threadDelay 1_000_000  -- Here are our two extensions.
  putStrLn "Bye Bye."
c4augustus commented 6 months ago

Great! I will install Haskell to try it out and then get back to you.

lonetech commented 6 months ago

None of the stuff above the imports is actually needed, I just thought it cool to demonstrate. There are also plenty of other options for how to write, e.g. forM could be spelled flip mapM, the parenthesis could be replaced with $, patterns could be in function equations instead of case expressions, <> could be used instead of ++, read could be used instead of readDec for one less import (but then it accepts parenthesis around the number), and I didn't have to use a lambda. All the imports are from the base library.

The mutual tail call recursion just seemed natural when that loop had two distinct phases both used as entry points.

A fun little point: Haskell is a strongly and statically typed language, and often teaching it focuses on describing types first, which is very helpful in structuring programs and understanding functions. You may note there are no types specificed in this program at all. I expect count ends up an Integer, but Int would work too.

As an exercise in illegibility, here's a very different way to write the forM loop: `sequence (zipWith (>>) (map (putStr.(<>"...").show) (takeWhile (>0) (iterate pred count))) (repeat (threadDelay 1000000))`

lonetech commented 6 months ago

Just noticed that the IO will be buffered if built with optimization. So I added System.IO to deal with that (it is part of the standard library too). There's also hFlush but disabling buffering seemed easier.

System.IO.hWaitForInput has a timeout that could be abused to wait if you have an input handle with no incoming data.

c4augustus commented 6 months ago

If you would like to add this to the repo, please make a PR with the file code/haskell/bbhw.hs and then I will test it out. Thanks.

c4augustus commented 6 months ago

https://github.com/proglangbase/bbhw/pull/4