haskell / play-haskell

Haskell Playground
129 stars 8 forks source link

Suggestion for example programs #25

Closed aaronallen8455 closed 1 year ago

aaronallen8455 commented 1 year ago

Feature request

With the aim of creating a good first impression of the language for a hypothetical newcomer visiting the playground from the haskell.org homepage, I'd like to suggest choosing some examples that are easily grokked and showcase practical features. I do like the quicksort example because it is simple and to the point, however, I find the two mergesort examples less compelling because the implementations, though interesting, are not easily followed.

I drafted a few ideas which I think demonstrate useful Haskell principles and that are relatively beginner friendly. This one shows some basic domain modeling using ADTs:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
import qualified Data.Time as Time

data Visitor
  = Member Profile
  | NonMember (Maybe T.Text)
  deriving Show

data Profile =
  Profile
    { name :: T.Text
    , birthday :: Time.Day
    } deriving Show

main :: IO ()
main = do
  let haskell = Member Profile
        { name = "Haskell Curry"
        , birthday = read "1900-09-12"
        }
  greeting <- makeGreeting haskell
  putStrLn $ T.unpack greeting

makeGreeting :: Visitor -> IO T.Text
makeGreeting visitor =
  case visitor of
    NonMember maybeName ->
      pure $ case maybeName of
        Just name -> "Hello, " <> name <> "!"
        Nothing   -> "Hello, mysterious visitor!"
    Member profile -> do
      today <- Time.utctDay <$> Time.getCurrentTime
      let monthAndDay = (\(_y, m, d) -> (m, d)) . Time.toGregorian
      if monthAndDay today == monthAndDay (birthday profile)
      then pure $ "Happy birthday, " <> name profile <> "!"
      else pure $ "Welcome back, " <> name profile <> "!"

And this one shows some event processing and instance derivation techniques:

{-# LANGUAGE DeriveGeneric, DerivingStrategies, DeriveAnyClass #-}

import Data.Foldable (foldl')
import GHC.Generics (Generic)
import qualified System.Random.Stateful as Rand

data Drone = Drone
  { xPos :: Int
  , yPos :: Int
  , zPos :: Int
  } deriving Show

data Movement
  = Forward | Back | ToLeft | ToRight | Up | Down
  deriving stock (Show, Generic)
  deriving anyclass Rand.Uniform

main :: IO ()
main = do
  let initDrone = Drone { xPos = 0, yPos = 100, zPos = 0 }
  -- Generate 15 moves randomly
  randomMoves <- Rand.uniformListM 15 Rand.globalStdGen
  let resultDrone = foldl' moveDrone initDrone randomMoves
  print resultDrone

moveDrone :: Drone -> Movement -> Drone
moveDrone drone move =
  case move of
    Forward -> drone { zPos = zPos drone + 1 }
    Back    -> drone { zPos = zPos drone - 1 }
    ToLeft  -> drone { xPos = xPos drone - 1 }
    ToRight -> drone { xPos = xPos drone + 1 }
    Up      -> drone { yPos = yPos drone + 1 }
    Down    -> drone { yPos = yPos drone - 1 }
tomsmeding commented 1 year ago

Thanks for your suggestions! I'm definitely open to different/better examples. The current ones are definitely very algorithmic.

Your examples are a bit longer than the current ones -- the two mergesort implementations are 22 and 24 lines, whereas yours are 36 and 34 lines. On my machine they still fit on the screen, though. I think the shorter the examples, the better.

I like your first example (the ADT domain modelling), though I would maybe replace putStrLn $ T.unpack with T.putStrLn after importing Data.Text.IO as well.

For your second example, while it is a nice example of deriving strategies and a custom, interesting reduction operator, it's somewhat sad that we need 3 language extensions as well as GHC Generics, all of which are not in beginner Haskell tutorials, nor necessarily in common simple Haskell code. In short, I suspect it looks daunting to the novice Haskell programmer, where the mergesort examples, while maybe algorithmically more complex, at least use very simple Haskell.

What do you think?

aaronallen8455 commented 1 year ago

Glad to hear that your open to the suggestion! I think we're mostly in agreement regarding the 2 draft examples.

Your examples are a bit longer than the current ones -- the two mergesort implementations are 22 and 24 lines, whereas yours are 36 and 34 lines. On my machine they still fit on the screen, though. I think the shorter the examples, the better.

I definitely share your concerns about the line count (Funny enough, I used T.putStrLn before but changed it to save a line by not importing Data.Text.IO). I was deliberately trying to make them fit on a single screen, maybe that's good enough? I like the idea of having examples that are not super trivial but then constraining the line count poses a challenge.

For your second example, while it is a nice example of deriving strategies and a custom, interesting reduction operator, it's somewhat sad that we need 3 language extensions as well as GHC Generics

Again, I fully agree with you. When I wrote it, I somehow missed the uniformEnumM function from random which is exactly what I needed, so with that no extensions or generics are necessary (https://play.haskell.org/saved/4kpl6pkS).

If I come up with some other examples (something with parser combinators could be cool), I'll post them here for your consideration.

tomsmeding commented 1 year ago

When I wrote it, I somehow missed the uniformEnumM function from random which is exactly what I needed, so with that no extensions or generics are necessary (https://play.haskell.org/saved/4kpl6pkS).

I like it! I assume you're okay with sharing these programs under the existing license of the playground packages (i.e. MIT)? :)

If I come up with some other examples (something with parser combinators could be cool), I'll post them here for your consideration.

Yes, please do.

Suggestions from other people are also welcome, though we try to not let the number of example programs grow without bound. :)

aaronallen8455 commented 1 year ago

Great! I have no issue with the licensing.