Skip to content

Commit

Permalink
Merge pull request #365 from JordanMartinez/development
Browse files Browse the repository at this point in the history
Make next minor release: ps-0.13.x-v0.17.1
  • Loading branch information
JordanMartinez authored Jul 29, 2019
2 parents 707282b + 049100f commit 4eb2ff8
Show file tree
Hide file tree
Showing 17 changed files with 1,263 additions and 1,584 deletions.
2 changes: 1 addition & 1 deletion .travis/spago--build-and-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ cd ../../
cd 22-Projects/
pwd
# Build but do not run benchmark tests
spago build -p "benchmark/**/*.purs"
spago build
PROJECTS_BUILT_OK=$?

# Node-based tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import Effect (Effect)
import Effect.Console (log)

{-
This file will demonstrate why we can't use `Effect` to
work with `Node.ReadLine`.
This file will demonstrate why using `Effect` to work with `Node.ReadLine`
creates the Pyramid of Doom.
Look through the code and then run it to see what happens.
Look through the code and then use the command in the folder's
ReadMe.md file to run it using Node (not Spago) to see what happens.
-}

-- new imports
Expand All @@ -23,64 +24,28 @@ main :: Effect Unit
main = do
log "\n\n" -- separate output from program output

interface <- createInterface
useInterface interface
closeInterface interface

where

createInterface :: Effect Interface
createInterface = do
log "Creating interface..."
interface <- createConsoleInterface noCompletion
log "Created!\n"

pure interface

useInterface :: Interface -> Effect Unit
useInterface interface = do
log "Requesting user input..."
interface # question "Type something here: "
\answer -> log $ "You typed: '" <> answer <> "'\n"

closeInterface :: Interface -> Effect Unit
closeInterface interface = do
log "Now closing interface"
close interface
log "Finished!"

{-
One might expect the last part of this program to output the following:
... create interface output ...
Requesting user input...
Type something here: [user types 'something']
You typed: 'something'
Now closing interface
Finished!
[Program exit]
In reality, it outputs this:
... create interface output ...
Requesting user input...
Type something here: Now closing interface
Finished!
[Program exit]
The user never has a chance to type anything. Why?
Because `question` adds a listener to the input stream
that will run an action when the user has inputted some
text and pressed Enter, and then continues evaluating
the next statement. The next expression closes the interface,
prevnting the user from ever inputting anything.
In other words, it doesn't wait for the user to type in anything
before continuing its evaluation.
Now it's time to see how we would write the same thing above
using Aff.
-}
log "Creating interface..."
interface <- createConsoleInterface noCompletion
log "Created!\n"

log "Requesting user input..."
interface # question "Type something here (1): " \answer1 -> do
log $ "You typed: '" <> answer1 <> "'\n"
interface # question "Type something here (2): " \answer2 -> do
log $ "You typed: '" <> answer2 <> "'\n"
interface # question "Type something here (3): " \answer3 -> do
log $ "You typed: '" <> answer3 <> "'\n"
interface # question "Type something here (4): " \answer4 -> do
log $ "You typed: '" <> answer4 <> "'\n"
interface # question "Type something here (5): " \answer5 -> do
log $ "You typed: '" <> answer5 <> "'\n"

log "Now closing interface"
close interface
log "Finished!"

log "This will print as we wait for your 5th answer."
log "This will print as we wait for your 4th answer."
log "This will print as we wait for your 3rd answer."
log "This will print as we wait for your 2nd answer."
log "This will print as we wait for your 1st answer."
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ Since our present interests do not require cancellation, we can use a no-op `Can

For our purposes, we need an `Aff` to run inside of an `Effect` monadic context. If one looks through `Aff`'s docs, the only one that does this besides `launchAff` and its variants is `runAff_`:
```purescript
runAff_ :: forall a. (Either Error a -> Effect Unit) -> Aff a -> Effect Unit
runAff_ :: forall a.
(Either Error a -> Effect Unit) -> -- arg 1
Aff a -> -- arg 2
Effect Unit -- outputted value
```
Breaking this down, `runAff_` takes two arguments (explained in reverse):
- an `Aff` computation to run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,6 @@ import Effect.Console (log)
import Node.ReadLine (Interface, createConsoleInterface, noCompletion, close)
import Node.ReadLine as ReadLine

createInterface :: Effect Interface
createInterface = do
log "Creating interface..."
interface <- createConsoleInterface noCompletion -- no tab completion
log "Created!\n"

pure interface

closeInterface :: Interface -> Effect Unit
closeInterface interface = do
log "Now closing interface"
close interface
log "Finished!"

useInterface :: Interface -> Aff Unit
useInterface interface = do
-- lifting `log`'s output into an Aff monad context
liftEffect $ log $ "Requesting user input..."

-- querying user for info and waiting until receive user input
answer <- interface # question "Type something here: "
liftEffect $ log $ "You typed: '" <> answer <> "'\n"

-- This is `affQuestion` from the previous file
question :: String -> Interface -> Aff String
question message interface = makeAff go
Expand All @@ -41,14 +18,42 @@ question message interface = makeAff go
go runAffFunction = nonCanceler <$
ReadLine.question message (runAffFunction <<< Right) interface


main :: Effect Unit
main = do
log "\n\n" -- separate output from program

interface <- createInterface {-
log "Creating interface..."
interface <- createConsoleInterface noCompletion
log "Created!\n"
{-
runAff_ :: forall a. (Either Error a -> Effect Unit) -> Aff a -> Effect Unit -}
runAff_
-- Ignore any errors and output and just close the interface
(\_ -> closeInterface interface)
(useInterface interface)
where
closeInterface :: Interface -> Effect Unit
closeInterface interface = do
log "Now closing interface"
close interface
log "Finished!"

-- Same code as before, but without the Pyramid of Doom!
useInterface :: Interface -> Aff Unit
useInterface interface = do
liftEffect $ log "Requesting user input..."

answer1 <- interface # question "Type something here (1): "
liftEffect $ log $ "You typed: '" <> answer1 <> "'\n"

answer2 <- interface # question "Type something here (2): "
liftEffect $ log $ "You typed: '" <> answer2 <> "'\n"

answer3 <- interface # question "Type something here (3): "
liftEffect $ log $ "You typed: '" <> answer3 <> "'\n"

answer4 <- interface # question "Type something here (4): "
liftEffect $ log $ "You typed: '" <> answer4 <> "'\n"

answer5 <- interface # question "Type something here (5): "
liftEffect $ log $ "You typed: '" <> answer5 <> "'\n"
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ These were found using a [purescript-aff-](https://pursuit.purescript.org/search
- [`purescript-aff-bus`](https://pursuit.purescript.org/packages/purescript-aff-bus/4.0.0)
- [`purescript-aff-retry`](https://pursuit.purescript.org/packages/purescript-aff-retry/1.2.1)
- [`purescript-aff-promise`](https://pursuit.purescript.org/packages/purescript-aff-promise/2.0.1)
- This library makes JavaScript Promises properly work/communicate together with PureScript Aff computations and vice versa.
- [`purescript-aff-parallel`](https://pursuit.purescript.org/packages/purescript-aff-parallel/0.1.1)
- [`purescript-aff-reattempt`](https://pursuit.purescript.org/packages/purescript-aff-reattempt/5.0.0)
- [`purescript-aff-throttler`](https://pursuit.purescript.org/packages/purescript-aff-throttler/0.0.2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## MonadThrow

`MonadThrow` is used to immediately stop `bind`'s sequential computation and return a value of its error type because of some unforeseeable error (e.g. business logic error).
`MonadThrow` is used to immediately stop `bind`'s sequential computation and return a value of its error type because of some unforeseeable error (e.g. error encountered when connecting to a database, file that was supposed to exist did not exist, etc).

It's default implmentation is `ExceptT`:
```purescript
Expand All @@ -16,6 +16,43 @@ class (Monad m) => MonadThrow e (ExceptT e m) where
throwError a = ExceptT (pure $ Left a)
```

### ExceptT: Before and After

Before using `ExceptT`, we would write this ugly verbose code:
```purescript
getName :: Effect (Either Error String)
getAge :: Effect (Either Error Int)
main :: Effect Unit
main = do
eitherName <- getName
case eitherName of
Left error -> log $ "Error: " <> show error
Right name -> do
eitherName <- getAge
case maybeAge of
Left error -> log $ "Error: " <> show error
Right age -> do
log $ "Got name: " <> name <> " and age " <> show age
```

After using `ExceptT`, we would write this clear readable code:
```purescript
getName :: Effect (Either Error String)
getAge :: Effect (Either Error Int)
main :: Effect Unit
main = do
eitherResult <- runExceptT $ ExceptT do
name <- getName
age <- getAge
pure { name, age }
case eitherResult of
Left error -> log $ "Error: " <> show error
Right rec -> do
log $ "Got name: " <> rec.name <> " and age " <> show rec.age
```

## MonadError

`MonadError` extends `MonadThrow` by enabling a monad to catch the thrown error, attempt to handle it (by changing the error type to an output type), and then continue `bind`'s sequential computation. If `catchError` can't handle the error, `bind`'s sequential computation will still stop at that point and return the value of the error type.
Expand All @@ -31,6 +68,20 @@ class (Monad m) => MonadError e (ExceptT e m) where
Right a -> pure $ Right a))
```

For example,
```purescript
getFileContents :: forall m.
MonadError m =>
String ->
m String
getFileContents pathToFile = do
readFileContents pathToFile `catchError` \fileNotFound ->
pure defaultValue
where
defaultValue = "foo"
```

## Derived Functions

`MonadThrow` does not have any derived functions.
Expand All @@ -50,7 +101,7 @@ value1 <- otherComputation stopped
value2 <- otherComputation value1
-- MonadError
mightRun <- catchError computation attemptToHandleErrorFunction
mightRun <- computationThatMayFail `catchError` computationWhenPreviousFailed
left_Error <- try computationThatFails
right_Output <- try computationThatSucceeds
Expand Down
2 changes: 1 addition & 1 deletion 22-Projects/spago.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Welcome to a Spago project!
You can edit this file as you like.
-}
{ sources =
[ "src/**/*.purs", "test/**/*.purs" ]
[ "src/**/*.purs", "test/**/*.purs", "benchmark/**/*.purs" ]
, name =
"ignore"
, dependencies =
Expand Down
31 changes: 31 additions & 0 deletions 22-Projects/src/01-Libraries/Node.ReadLine/01-Syntax.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Node.ReadLine.Aff where

import Prelude

import Data.Either (Either(..))
import Effect (Effect)
import Effect.Aff (Aff, bracket, launchAff_, makeAff, nonCanceler)
import Effect.Class (liftEffect)
import Effect.Console (log)
import Node.ReadLine (Interface, createConsoleInterface, noCompletion, close)
import Node.ReadLine as RL

question :: String -> Interface -> Aff String
question message interface = makeAff \runAffFunction ->
nonCanceler <$ RL.question message (runAffFunction <<< Right) interface

main :: Effect Unit
main = do
log "\n\n" -- separate output from program

launchAff_ $ bracketInterface \interface -> do
answer <- interface # question "Type something here: "
liftEffect $ log $ "You typed: '" <> answer <> "'\n"

where
bracketInterface :: (Interface -> Aff Unit) -> Aff Unit
bracketInterface useInterface = do
bracket
(liftEffect $ createConsoleInterface noCompletion)
(liftEffect <<< close)
useInterface
9 changes: 9 additions & 0 deletions 22-Projects/src/01-Libraries/Node.ReadLine/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Node ReadLine and Aff

To use receive input from a user via the terminal, we need to use `Node.ReadLine`'s API. To deal with callback hell / Pyramid of Doom issues, we'll use `Aff` bindings.

## Compilation Instructions

```bash
spago run -m Node.ReadLine.Aff
```
Loading

0 comments on commit 4eb2ff8

Please sign in to comment.