Giter Site home page Giter Site logo

typeclasses / haskell-phrasebook Goto Github PK

View Code? Open in Web Editor NEW
209.0 12.0 22.0 303 KB

The Haskell Phrasebook: a quick intro to Haskell via small annotated example programs

Home Page: https://typeclasses.com/phrasebook

Haskell 95.28% Nix 4.72%
haskell haskell-learning

haskell-phrasebook's Introduction

Haskell Phrasebook logo

The Haskell Phrasebook is a free quick-start Haskell guide comprised of a sequence of small annotated programs. It provides a cursory overview of selected Haskell features, jumping-off points for further reading, and recommendations to help get you writing programs as soon as possible.

This repository contains only the code files; you may find them useful if you want to follow along while reading the Phrasebook, which can be found at typeclasses.com/phrasebook.

Contributing -- We love to hear any requests or ideas for how to expand or improve the Phrasebook! Please see the contributor guide.

Build tools -- See the build tools page for information on how to build and run the Phrasebook examples.

License -- The code in this repository is offered under the Creative Commons CC BY-NC 4.0 license, which allows free non-commercial use with attribution. You can read the full text in the license.txt file.

Support -- You can help support this community resource with funding via Liberapay or by becoming a member of Type Classes (which gives you access to much more of our writing).

haskell-phrasebook's People

Contributors

ammatsui avatar argumatronic avatar chris-martin avatar cidem avatar friedbrice avatar gutierrezje avatar tfausak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

haskell-phrasebook's Issues

How to define functions

For inspiration, Go By Example has a page on defining functions. Go By Example also has a separate page on multiple return values, but perhaps we could combine the two into one page.

Here are the beginnings of some multiple-return-values ideas:

vals = (3, 7)

f x = (x, x + 1)

main =
  do
    let (a, b) = vals
    putStrLn (show a)
    putStrLn (show b)

    let (_, c) = f 4
    putStrLn (show c)

Reducing a list to a single value

It might be nice to put this page right after mutable references and show how some loops involving mutation can be written as folds, like this Rust page does.

If this page is limited to base, it might use fold, foldMap, and foldl'.

We could also consider using the foldl package if there's a significant benefit. The average example in the docs seems somewhat compelling. I'm not sure what the right tradeoff is here between the extra high-level expressivity of Control.Foldl versus having to introduce another thing.

How to work with directories/ how to work with processes/ how to do things concurrently

This is how I check my git repos every morning. This is a pretty long example, so it probably needs to be split into three smaller examples (which I'm happy to do if you think it'll make for good content).

#!/usr/bin/env stack
{- stack script --resolver lts-13.26 -}

import Control.Concurrent.Async (mapConcurrently)
import Control.Monad (filterM, join)
import GHC.IO.Exception (ExitCode(ExitSuccess))
import System.Directory (doesDirectoryExist, getHomeDirectory, listDirectory)
import System.Process (CreateProcess(cwd), createProcess, shell, waitForProcess)

-- config, relative to user home directory
dirs :: [FilePath]
dirs = ["abs/aur", "friedbrice", "lumihq"]

-- Concat paths without fear
(+/) :: FilePath -> FilePath -> FilePath
(+/) "" "" = ""
(+/) parent child = case (last parent, head child) of
    ('/', '/') -> parent ++ tail child
    ('/', _) -> parent ++ child
    (_, '/') -> parent ++ child
    _ -> parent ++ "/" ++ child

fetchRepo :: FilePath -> CreateProcess
fetchRepo dir = (shell "git fetch --prune --all") { cwd = Just dir }

listRepos :: FilePath -> IO [FilePath]
listRepos parentdir = do
    files <- listDirectory parentdir
    let paths = (parentdir +/) <$> files
    filterM (doesDirectoryExist . (+/ ".git")) paths

concurrentlyRetryForever :: [CreateProcess] -> IO ()
concurrentlyRetryForever procs = do
    handles <- mapConcurrently createProcess procs
    codes <- traverse (waitForProcess . \(_,_,_,h) -> h) $ handles
    let failures = [ p | (p, c) <- zip procs codes, c /= ExitSuccess ]
    if null failures then pure () else concurrentlyRetryForever failures

main :: IO ()
main = do
    home <- getHomeDirectory
    let fullPaths = (home +/) <$> dirs
    repos <- join <$> traverse listRepos fullPaths
    concurrentlyRetryForever (fetchRepo <$> repos)

Time

There's some extremely common time-related tasks it'd be really nice to demonstrate.

  • Rendering and parsing yyyy-mm-dd format
  • Getting the current unix timestamp as an integer in the units of your choice (e.g. seconds or milliseconds)
  • Converting a unix timestamp to an appropriate time type (e.g. if we're consuming a JSON API that expresses a time as a timestamp)
  • Basic time arithmetic, like getting a time that represents "a year from now"

If using the time package, make sure you're using the latest version, because the API has changed a bit recently. time has gotten a bit better with the new changes but I wouldn't mind recommending a different library if anybody can recommend something friendlier.

Potential idea for a practial example: use the cookies library to generate a set-cookie header for a cookie that is set to expire one month in the future.

Asking for the type of an expression

We've already covered this once in using the REPL, but here's another quick way of asking for a type that's useful when you're using the text editor + ghcid setup.

If you don't know the type of some expression, leave an underscore (this is called a "hole") and the display in ghcid will tell you what type goes there.

loadConfig :: _
loadConfig = Data.Ini.readIniFile "config.ini"
    • Found type wildcard ‘_’
        standing for ‘IO (Either String Data.Ini.Ini)’

It's pretty magical that you can just type :: _ after an expression to make the type pop up in ghcid.

Maps and sets

As a jumping-off point for an example involving the Map type from containers here is a fairly direct translation from Go By Example:

import qualified Data.Map as Map

main =
  do
    let m = Map.fromList [("k1", 7), ("k2", 13)]
    putStrLn ("map: " ++ show m)
    putStrLn ("v1: " ++ show (Map.lookup "k1" m))
    putStrLn ("size: " ++ show (Map.size m))

    let m' = Map.delete "k2" m
    putStrLn ("map: " ++ show m')
    putStrLn ("v2: " ++ show (Map.lookup "k2" m'))
$ runhaskell maps.hs
map: fromList [("k1",7),("k2",13)]
v1: Just 7
size: 2
map: fromList [("k1",7)]
v2: Nothing

Since Set is closely related to Map, it might be nice to discuss sets at the same time.

We might also want to show using a custom datatype as the key in a map, to demonstrate how you need to derive Eq and Ord.

How to work with files/how to work with processes/how to do things concurrently

This is how I check my git repos every morning. This is a pretty long example, so it probably needs to be split into three smaller examples (which I'm happy to do if you think it'll make for good content).

#!/usr/bin/env stack
{- stack script --resolver lts-13.26 -}

import Control.Concurrent.Async (mapConcurrently)
import Control.Monad (filterM, join)
import GHC.IO.Exception (ExitCode(ExitSuccess))
import System.Directory (doesDirectoryExist, getHomeDirectory, listDirectory)
import System.Process (CreateProcess(cwd), createProcess, shell, waitForProcess)

-- config, relative to user home directory
dirs :: [FilePath]
dirs = ["abs/aur", "friedbrice", "lumihq"]

-- Concat paths without fear
(+/) :: FilePath -> FilePath -> FilePath
(+/) "" "" = ""
(+/) parent child = case (last parent, head child) of
    ('/', '/') -> parent ++ tail child
    ('/', _) -> parent ++ child
    (_, '/') -> parent ++ child
    _ -> parent ++ "/" ++ child

fetchRepo :: FilePath -> CreateProcess
fetchRepo dir = (shell "git fetch --prune --all") { cwd = Just dir }

listRepos :: FilePath -> IO [FilePath]
listRepos parentdir = do
    files <- listDirectory parentdir
    let paths = (parentdir +/) <$> files
    filterM (doesDirectoryExist . (+/ ".git")) paths

concurrentlyRetryForever :: [CreateProcess] -> IO ()
concurrentlyRetryForever procs = do
    handles <- mapConcurrently createProcess procs
    codes <- traverse (waitForProcess . \(_,_,_,h) -> h) $ handles
    let failures = [ p | (p, c) <- zip procs codes, c /= ExitSuccess ]
    if null failures then pure () else concurrentlyRetryForever failures

main :: IO ()
main = do
    home <- getHomeDirectory
    let fullPaths = (home +/) <$> dirs
    repos <- join <$> traverse listRepos fullPaths
    concurrentlyRetryForever (fetchRepo <$> repos)

Enum ranges

A few ideas:

import Data.Time.Calendar

main =
  do
    putStrLn (show [3 .. 8])
    putStrLn (show ['a' .. 'z'])
    putStrLn (show [Tuesday .. Friday])
$ runhaskell range.hs
[3,4,5,6,7,8]
"abcdefghijklmnopqrstuvwxyz"
[Tuesday,Wednesday,Thursday,Friday]

Might be nice to also show:

  • How to define your own type that derives Enum
  • Leaving off one of the bounds: [.. x] and [x ..]
  • Using [minBound .. maxBound] to list all values

Queues

Haskell really shines at concurrent programming and it makes me happy. I've love to have an example that uses TQueue and/or TBQueue.

Errors

There are a lot of things we could talk about on this topic.

Just as a jumping-off point, here's some code that is more or less a direct translation from Go By Example:

import Data.Foldable (for_)

data Error =
  Error
    { arg :: Int
    , problem :: String
    }
  deriving Show

f arg =
  case arg of
    42 -> Left (Error arg "Can't work with it")
    x  -> Right (x + 3)

t1 =
  do
    x <- f 7
    y <- f 8
    z <- f 9
    return (x, y, z)

t2 =
  do
    x <- f 41
    y <- f 42
    z <- f 43
    return (x, y, z)

main =
  do
    for_ [7, 42] $ \i ->
        case (f i) of
            Left e  -> putStrLn ("f failed: " ++ show e)
            Right r -> putStrLn ("f worked: " ++ show r)

    putStrLn (show t1)

    putStrLn (show t2)
$ runhaskell errors.hs
f worked: 10
f failed: Error {arg = 42, problem = "Can't work with it"}
Right (10,11,12)
Left (Error {arg = 42, problem = "Can't work with it"})

I don't think a demonstrating involving ExceptT Error IO would be out of line either.

Records (with optics?)

Here's a records demo we wrote a while back:

import Numeric.Natural

data Person =
  Person
    { name :: String  -- ^ The person's given name
    , age :: Natural  -- ^ How many years old
    }
    deriving Show

main =
  do
    let a = Person { name = "Alice", age = 47 }
    let b = Person { name = "Bob", age = 50 }
    let c = b{ age = 51 }

    putStrLn (show a)
    putStrLn (show b)
    putStrLn (show c)

    putStrLn ("name: " ++ name c)
    putStrLn ("age: " ++ show (age c))
$ runhaskell records.hs
Person {name = "Alice", age = 47}
Person {name = "Bob", age = 50}
Person {name = "Bob", age = 51}
name: Bob
age: 51

Lately I'm feeling like the Phrasebook should just immediately introduce optics from the start.

{-# LANGUAGE TemplateHaskell #-}

import Numeric.Natural
import Optics

data Person =
  Person
    { _name :: String  -- ^ The person's given name
    , _age :: Natural  -- ^ How many years old
    }
    deriving Show

makeLenses ''Person

main =
  do
    let a = Person { _name = "Alice", _age = 47 }
    let b = Person { _name = "Bob", _age = 50 }
    let c = set age 51 b

    putStrLn (show a)
    putStrLn (show b)
    putStrLn (show c)

    putStrLn ("name: " ++ view name c)
    putStrLn ("age: " ++ show (view age c))

Maybe that's too radical. On the other hand, maybe introducing it now in an extremely simple context is good setup for a later page on doing "deep updates" with composed lenses. I think in a later page we could end up showing how to get a lot of of optics using only view, set, over, and (%) without being overwhelming.

Logging

I like concurrent logging as the subject as a Phrasebook example because it's a common need and a good excuse to concisely put together a lot of topics.

Here's some code that comes straight out of the repo for the Type Classes server. It probably contains a few too many topics and needs to be significantly simplified and better focused.

  • Starting threads with withAsync
  • Concurrent queues TQueue
  • Catching exceptions with catchAny
  • Cleaning up after interrupts with finally
  • Introducing strictness with ($!)
import Control.Concurrent.Async (withAsync)
import Control.Concurrent.STM
import Control.Exception.Safe (Exception (displayException), catchAny, finally)
import Control.Monad (forever)
import Control.Monad.Trans.Cont
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import System.IO (stderr)

data Log =
  Log
    { logText :: Text -> IO ()
    , logString :: String -> IO ()
    }

withLogging :: ContT a IO Log
withLogging = ContT \continue ->
  do
    q <- atomically newTQueue

    let
        logText msg = atomically (writeTQueue q $! msg)
        logString = logText . Text.pack
        l = Log {..}

    withAsync (runLogger q) \_ -> (continue l)

runLogger :: TQueue Text -> IO ()
runLogger q = finally runForever runUntilEmpty
  where
    runForever = forever $ atomically (readTQueue q) >>= write

    runUntilEmpty =
        atomically (tryReadTQueue q) >>=
        \case
            Nothing -> return ()
            Just msg -> write msg *> runUntilEmpty

    write msg = Text.hPutStrLn stderr msg

recover :: Log -> IO a -> IO (Maybe a)
recover log a = catchAny (fmap Just a) (\e -> logException log e *> return Nothing)

logException :: Exception e => Log -> e -> IO ()
logException log e = logString log (displayException e)

Types of numbers

This would be sort of a followup to common types that goes more in depth on numbers.

  • Signed integers: Integer, Int8, Int16, Int32, Int64
  • Unsigned integers: Natural, Word8, Word16, Word32, Word64
  • Int and Word are hardware-specific type aliases (e.g. Int is probably Int32 or Int64 on a 32-bit or 64-bit processor)
  • Floating point: Float, Double
  • The default types for numeric literals are Integer for whole numbers are Double for fractions.
  • Also: Complex, Ratio, Rational
  • Conversions between types of numbers

If there are too many things to show, this topic might span multiple examples.

[nix-shell] moving build output from the sandbox to the Nix store: Permission denied

When running

nix-shell tools/shell.nix

I get (on a Debian and on an Ubuntu machine on which I have freshly installed nix):

...
building '/nix/store/d7wmn4239kg32jv0i4y600nxdl2a0v3v-alex-plan-to-nix-pkgs.drv'...
error: moving build output '/nix/store/cx2gfhixdiv6kxdlijj7ifn29mvqy6sl-alex-plan-to-nix-pkgs' from the sandbox to the Nix store: Permission denied

Is anyone else having the same issue?

Mutable vectors

Here's an example I jotted down a while ago. A slightly more practical example might be nice, but I don't think I've ever actually used mutable vectors in Haskell, nothing is immediately coming to mind... ideas welcome.

import qualified Data.Vector as V
import qualified Data.Vector.Mutable as MV

main =
  do
    a <- MV.replicate 5 0
    do a' <- V.freeze a; putStrLn ("empty: " ++ show a')

    MV.write a 4 100
    do a' <- V.freeze a; putStrLn ("write: " ++ show a')
    do x <- MV.read a 4; putStrLn ("read: " ++ show x)

    putStrLn ("length: " ++ show (MV.length a))

    b <- V.thaw (V.fromList [1..5])
    do b' <- V.freeze b; putStrLn ("dcl: " ++ show b')
$ runhaskell vectors.hs
empty: [0,0,0,0,0]
write: [0,0,0,0,100]
read: 100
length: 5
dcl: [1,2,3,4,5]

Defining a typeclass

We wrote this a while back to show what defining your own typeclass looks like. I'm not sure whether I like it, or whether this is actually a good topic for a Phrasebook page (because "I want to define a typeclass" is not an end goal in itself). It would be cool to include an example like this if we could figure out how to motivate it better and change the title to something that is an understandably practical objective.

class Geometry a where
  area :: a -> Double
  perimeter :: a -> Double

data Rectangle =
  Rectangle
    { width :: Double
    , height :: Double
    }
  deriving Show

data Circle =
  Circle
    { radius :: Double
    }
  deriving Show

instance Geometry Rectangle where
  area r = width r * height r
  perimeter r = (2 * width r) + (2 * height r)

instance Geometry Circle where
  area c = pi * radius c * radius c
  perimeter c = 2 * pi * radius c

measure x =
  do
    putStrLn (show x)
    putStrLn ("area: " ++ show (area x))
    putStrLn ("perimeter: " ++ show (perimeter x))

main =
  do
    measure Rectangle{ width = 3, height = 4 }
    measure Circle{ radius = 5 }
$ runhaskell classes.hs
Rectangle {width = 3.0, height = 4.0}
area: 12.0
perimeter: 14.0
Circle {radius = 5.0}
area: 78.53981633974483
perimeter: 31.41592653589793

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.