Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Running a web-compatible recipe:
| | :heavy_check_mark: | [CardsReactHooks](recipes/CardsReactHooks) | A React port of the ["Random - Cards" Elm Example](https://elm-lang.org/examples/cards). |
| | :heavy_check_mark: | [CatGifsHalogenHooks](recipes/CatGifsHalogenHooks) | A Halogen port of the ["HTTP - Cat GIFs" Elm Example](https://elm-lang.org/examples). |
| | :heavy_check_mark: | [CatGifsReactHooks](recipes/CatGifsReactHooks) | A React port of the ["HTTP - Cat GIFs" Elm Example](https://elm-lang.org/examples/cat-gifs). |
| :heavy_check_mark: | :heavy_check_mark: | [DebuggingLog](recipes/DebuggingLog) | This recipe shows how to do print-debugging using the `Debug` module's `spy` and `traceM` functions. The compiler will emit warnings to remind you to remove these debug functions before you ship production code. |
| :heavy_check_mark: | | [DiceCLI](recipes/DiceCLI) | This recipe shows how to create an interactive command line prompt that repeatedly generates a random number between 1 and 6. |
| :heavy_check_mark: | :heavy_check_mark: | [DiceLog](recipes/DiceLog) | This recipe shows how to log a random integer between 1 and 6 (representing a roll of a die) in either the node.js or web browser console. |
| | :heavy_check_mark: | [DragAndDropHalogenHooks](recipes/DragAndDropHalogenHooks) | A Halogen port of the ["Files - Drag-and-Drop" Elm Example](https://elm-lang.org/examples/drag-and-drop). |
Expand Down
13 changes: 13 additions & 0 deletions recipes/DebuggingLog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/bower_components/
/node_modules/
/.pulp-cache/
/output/
/generated-docs/
/.psc-package/
/.psc*
/.purs*
/.psa*
/.spago
/web-dist/
/prod-dist/
/prod/
9 changes: 9 additions & 0 deletions recipes/DebuggingLog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# DebuggingLog

This recipe shows how to do print-debugging using the `Debug` module's `spy` and `traceM` functions. The compiler will emit warnings to remind you to remove these debug functions before you ship production code.

## Expected Behavior:

Does console-printing-based debugging in various contexts to show the pros/cons of each approach and how reliable/unpredictable they can be.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also want to include that these functions include a compiler warning, which helps ensure this code gets removed in say the code review process rather than make it to production.

It's similar to why some folks define a custom todo function like this:

todo
  :: forall sym a
   . IsSymbol sym
  => Warn (Beside (Text "TODO: ") (Text sym))
  => SProxy sym
  -> a
todo sym =
  unsafeThrowException $ error $ "TODO: " <> reflectSymbol sym

which allows you to make a TODO comment that also includes a compiler warning so you don't forget it.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've updated the readme to include a note about this, so that it shows up in the recipe ToC, too.


For the browser, make sure to open the console with dev tools first, then reload/refresh the page.
2 changes: 2 additions & 0 deletions recipes/DebuggingLog/nodeSupported.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This file just indicates that the node backend is supported.
It is used for CI and autogeneration purposes.
6 changes: 6 additions & 0 deletions recipes/DebuggingLog/spago.dhall
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{ name = "DebuggingLog"
, dependencies =
[ "aff", "console", "debug", "effect", "psci-support", "st" ]
, packages = ../../packages.dhall
, sources = [ "recipes/DebuggingLog/src/**/*.purs" ]
}
89 changes: 89 additions & 0 deletions recipes/DebuggingLog/src/Main.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module DebuggingLog.Main where

import Prelude

import Control.Monad.ST.Internal as ST
import Data.Tuple (Tuple(..))
import Debug.Trace (spy, traceM)
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class (liftEffect)
import Effect.Console (log)

data MyADT = MyADT Int (Tuple Int (Array String)) { foo :: String }

main :: Effect Unit
main = do
log "When we are in the 'Effect' monad, we can print content to the console."
launchAff_ do
liftEffect $ log $ "We can still print values to the console as long as \
\the monad in question implements the MonadEffect \
\type class. Since `Aff` implements MonadEffect, we \
\can lift that effect into `Aff`."

log "However, there are times when we want to debug some code and wish \
\to use print-based debugging. Since PureScript is pure, how do we \
\do that?"

usingSpy

usingTraceM

compareSpyAndTraceM

usingSpy :: Effect Unit
usingSpy = do
log "usingSpy"
-- `spy` returns the value it receives. However, it prints that value
-- to the console before it "returns."
-- spy :: forall a. DebugWarning => String -> a -> a
-- You can use this in a `let` clause pretty reliably
let
x = 5
y = spy "y" 8
adt = spy "adt" $ MyADT 1 (Tuple 4 ["a", "b"]) { foo: "foo value" }
function = spy "function" $ \intValue -> show $ 4 + intValue

-- quick way of debugging something without showing it or using a
-- variable name
_ = spy "debug for me what x + y is" $ x + y
arrayOfStrings = spy "debug some array" $ map show [1, 2, 3, 4, 5]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other place it's common to use spy is, for example, in a render function:

render state =
  HH.div
    [ ]
    [ HH.text state.someValue ]

becomes

...
    [ HH.text $ spy "state.someValue" state.someValue ]

This makes spy really convenient to drop into arbitrary code. You simply add spy "label" in front of whatever value it is you had before, and you get the logging without any other change to your code.

Another example: let's say you've written a little algorithm with a loop and you want to spy at each step:

fact :: Int -> Int -> Int
fact 0 acc = acc
fact n acc = fact (spy "n" (n - 1)) (spy "acc" (acc * n))

Being able to insert spy inline is really nice. I wanted to call it out because you can't do this with traceM.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the fact example and updated the traceM usage to include two spys to show how that works.


-- quickly drop-in `spy` to see every step of a recursive function
fact :: Int -> Int -> Int
fact 0 acc = acc
fact n acc = fact (spy "n" (n - 1)) (spy "acc" (acc * n))

_ = fact 5 1

log $ "Thus, spying is an effective way of quickly adding debugging where \
\you need it without affecting any other part of your code. \
\Note: you should not use `spy` to do logging in production code. \
\Use a proper logger in production code."

Copy link
Collaborator

@milesfrain milesfrain Jul 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments on what to expect in usingTraceMInAffRace would be helpful

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe traceM in Haskell doesn't happen in a predictable way. I was trying to do the same here. On second thought, I'm not sure what the problems are with using traceM in Haskell and whether those problems still arise in PureScript. Maybe this should be removed entirely?

usingTraceM :: Effect Unit
usingTraceM = do
log "usingTraceM"
traceM "Notice how this text's color is different than what is outputted \
\via `log`."

let
localMutationComputation
:: forall ensureMutationStaysLocal. ST.ST ensureMutationStaysLocal Int
localMutationComputation = do
-- the ST monad does not implement `MonadEffect`,
-- so `traceM` is the only way to log output to the console in a
-- `log`-like fashion.
localReference <- ST.new 0
traceM localReference
four <- ST.modify (_ + 4) localReference
traceM four
ST.write (four + 2) localReference

log $ "Local reference value is: " <> show (ST.run localMutationComputation)

compareSpyAndTraceM :: Effect Unit
compareSpyAndTraceM = do
let x = 5
_ = spy "x" x
traceM x
13 changes: 13 additions & 0 deletions recipes/DebuggingLog/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>DebuggingLog</title>
</head>

<body>
<script src="./index.js"></script>
</body>

</html>
2 changes: 2 additions & 0 deletions recipes/DebuggingLog/web/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
require("../../../output/DebuggingLog.Main/index.js").main();