class: inverse layout: true --- class: center, middle, inverse # Compiler-assisted
web development #### [.handle[@maciejsmolinski]](https://twitter.com/maciejsmolinski) --- class: center, middle ## Majority of our debugging time
is spent
tracking down type errors
--- class: center, middle ## Majority of our debugging time
is spent
tracking down type errors
~ me --- # The problems of the web development today --- # The problems of the web development today ### - Type Errors ??? .red.bold[Uncaught TypeError: Cannot read property 'trim' of undefined] .red.bold[Uncaught TypeError: this.onSubmit is not a function] .red.bold[Uncaught TypeError: Cannot read property 'querySelector' of null] -- ### - Poor or no error handling ??? .red.bold[Uncaught (in promise) Error: API Error] -- ### - Mutable by default ??? .red.bold[debugger; console.log()] -- ### - No documentation -- ### - Outdated documentation -- ### - Missing input validation -- ### - Late feedback loop (runtime) --- # What some functional languages offer -- ### - Type-safe by design -- ### - Strong contracts -- ### - Total functions -- ### - Quick feedback loop -- ### - Immutable by default -- ### - Compiler-assisted refactoring --- class: center, middle #
logic errors are difficult to find
## spend your efforts there and let the compiler handle the rest ??? the cheapest bugs are these that do not get into production --- class: middle center # Compile to JavaScript languages -- ### PureScript -- ### Elm -- ### Reason -- ### ClojureScript -- ### & co --- class: middle center #
syntax
#
techniques
--- class: center, middle, inverse ## PureScript -- general purpose -- compact -- strongly typed -- type inference -- immutable by default -- FFI --- class: middle # Let's build stuff ### with the help from the compiler --- class: middle ### Clone the project template and instal dependencies ```bash $ npx degit maciejsmolinski/purescript-webpack-template purescript-1 && \ cd purescript-1 && \ npm install ``` --- class: middle # A simple logger --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect.Console as Console log message = Console.log message ``` --- class: middle ```haskell -- src/Logger.purs *module Logger where import Effect.Console as Console log message = Console.log message ``` --- class: middle ```haskell -- src/Logger.purs module Logger where *import Effect.Console as Console log message = Console.log message ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect.Console as Console *log message = Console.log message ``` --- class: middle .hljs-default[.hljs[.warning[ ``` No type declaration was provided for the top-level declaration of log. It is good practice to provide type declarations as a form of documentation. The inferred type of log was: String -> Effect Unit in value declaration log ``` ]]] --- class: middle .hljs-default[.hljs[.warning[ ``` No type declaration was provided for the top-level declaration of log. It is good practice to provide type declarations as a form of documentation. The inferred type of log was: `String -> Effect Unit` in value declaration `log` ``` ]]] --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect.Console as Console *log :: String -> Effect Unit log message = Console.log message ``` --- class: middle .hljs-default[.hljs[.error[ ``` Unknown type Effect ``` ]]] --- class: middle ```haskell -- src/Logger.purs module Logger where *import Effect (Effect) import Effect.Console as Console log :: String -> Effect Unit log message = Console.log message ``` --- class: middle .hljs-default[.hljs[.error[ ``` Unknown type Unit ``` ]]] --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console *import Prelude log :: String -> Effect Unit log message = Console.log message ``` --- class: middle # The entrypoint: Main module --- class: middle ```haskell -- src/Main.purs module Main where import Effect (Effect) import Logger as Logger import Prelude main :: Effect Unit main = Logger.log "PureScript rocks!" ``` --- class: middle ```haskell -- src/Main.purs *module Main where import Effect (Effect) import Logger as Logger import Prelude main :: Effect Unit main = Logger.log "PureScript rocks!" ``` --- class: middle ```haskell -- src/Main.purs module Main where import Effect (Effect) *import Logger as Logger import Prelude main :: Effect Unit main = Logger.log "PureScript rocks!" ``` --- class: middle ```haskell -- src/Main.purs module Main where import Effect (Effect) import Logger as Logger import Prelude *main :: Effect Unit main = Logger.log "PureScript rocks!" ``` --- class: middle ```haskell -- src/Main.purs module Main where import Effect (Effect) import Logger as Logger import Prelude main :: Effect Unit *main = * Logger.log "PureScript rocks!" ``` --- class: middle ```bash $ npx spago run Installation complete. Compiling Type.Data.RowList Compiling Type.Data.Row Compiling Record.Unsafe Compiling Data.Symbol Compiling Data.NaturalTransformation Compiling Data.Boolean Compiling Control.Semigroupoid Compiling Data.Show Compiling Control.Category Compiling Data.Unit ... Compiling Test Compiling Effect.Unsafe Compiling Effect.Class Compiling Effect.Uncurried Compiling Effect.Console Compiling PSCI.Support Compiling Logger Compiling Effect.Class.Console Compiling PureScript Compiling Test.Main Build succeeded. PureScript rocks! ``` --- class: middle # Improved Logger --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console data Level = Log | Warn | Error debug :: Level -> String -> Effect Unit debug Log message = Console.log message log :: String -> Effect Unit log message = debug Log message ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console *data Level = Log * | Warn * | Error debug :: Level -> String -> Effect Unit debug Log message = Console.log message log :: String -> Effect Unit log message = debug Log message ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console data Level = Log | Warn | Error debug :: Level -> String -> Effect Unit debug Log message = Console.log message log :: String -> Effect Unit *log message = debug Log message ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console data Level = Log | Warn | Error *debug :: Level -> String -> Effect Unit debug Log message = Console.log message log :: String -> Effect Unit log message = debug Log message ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console data Level = Log | Warn | Error debug :: Level -> String -> Effect Unit *debug Log message = Console.log message log :: String -> Effect Unit log message = debug Log message ``` --- class: middle .hljs-default[.hljs[.error[ ``` A case expression could not be determined to cover all inputs. The following additional cases are required to cover all inputs: Warn _ Error _ Alternatively, add a Partial constraint to the type of the enclosing value. ... in value declaration debug ... ``` ]]] --- class: middle .hljs-default[.hljs[.error[ ``` A case expression could not be determined to cover all inputs. The following additional cases are required to cover all inputs: * Warn _ * Error _ Alternatively, add a Partial constraint to the type of the enclosing value. ... * in value declaration debug ... ``` ]]] --- class: middle ```haskell debug :: Level -> String -> Effect Unit debug Log message = Console.log message debug Warn message = Console.warn message debug Error message = Console.error message ``` --- class: middle ```haskell debug :: Level -> String -> Effect Unit debug Log message = Console.log message *debug Warn message = Console.warn message *debug Error message = Console.error message ``` --- class: middle ```haskell log :: String -> Effect Unit log message = debug Log message warn :: String -> Effect Unit warn message = debug Warn message error :: String -> Effect Unit error message = debug Error message ``` --- class: middle ```haskell log :: String -> Effect Unit log message = debug Log message *warn :: String -> Effect Unit *warn message = debug Warn message *error :: String -> Effect Unit *error message = debug Error message ``` --- class: middle ```haskell log :: String -> Effect Unit log `message` = debug Log `message` warn :: String -> Effect Unit warn `message` = debug Warn `message` error :: String -> Effect Unit error `message` = debug Error `message` ``` --- class: middle ```haskell log :: String -> Effect Unit log = debug Log warn :: String -> Effect Unit warn = debug Warn error :: String -> Effect Unit error = debug Error ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console import Prelude data Level = Log | Warn | Error debug :: Level -> String -> Effect Unit debug Log message = Console.log message debug Warn message = Console.warn message debug Error message = Console.error message log :: String -> Effect Unit log = debug Log warn :: String -> Effect Unit warn = debug Warn error :: String -> Effect Unit error = debug Error ``` --- class: middle # Adding missing trace ### Foreign-Function Interface (FFI) --- class: middle ```haskell data Level = Log | Warn | Error | Trace ``` --- class: middle ```haskell data Level = Log | Warn | Error * | Trace ``` --- class: middle .hljs-default[.hljs[.error[ ``` A case expression could not be determined to cover all inputs. The following additional cases are required to cover all inputs: Trace _ Alternatively, add a Partial constraint to the type of the enclosing value. ... in value declaration debug ... ``` ]]] --- class: middle .hljs-default[.hljs[.error[ ``` A case expression could not be determined to cover all inputs. The following additional cases are required to cover all inputs: * Trace _ Alternatively, add a Partial constraint to the type of the enclosing value. ... * in value declaration debug ... ``` ]]] --- class: middle ```haskell debug :: Level -> String -> Effect Unit debug Log message = Console.log message debug Warn message = Console.warn message debug Error message = Console.error message debug Trace message = customTrace message ``` --- class: middle ```haskell debug :: Level -> String -> Effect Unit debug Log message = Console.log message debug Warn message = Console.warn message debug Error message = Console.error message *debug Trace message = customTrace message ``` --- class: middle .hljs-default[.hljs[.error[ ``` Unknown value customTrace ``` ]]] --- class: middle ```haskell -- src/Logger.purs foreign import customTrace :: String -> Effect Unit ``` --- class: middle ```haskell -- src/Logger.purs `foreign` import customTrace :: String -> Effect Unit ``` --- class: middle ```haskell -- src/Logger.purs foreign import `customTrace` :: String -> Effect Unit ``` --- class: middle ```haskell -- src/Logger.purs foreign import customTrace :: `String -> Effect Unit` ``` --- class: middle .hljs-default[.hljs[.error[ ``` The foreign module implementation for module Logger is missing. ``` ]]] --- class: middle ```javascript // src/Logger.js exports.customTrace = function (data) { return function () { console.trace(data); } } ``` --- class: middle ```javascript // src/Logger.js *exports.customTrace = function (data) { return function () { console.trace(data); } } ``` --- class: middle ```javascript // src/Logger.js exports.customTrace = function (data) { * return function () { console.trace(data); } } ``` --- class: middle ```haskell -- src/Logger.purs trace :: String -> Effect Unit trace = debug Trace ``` --- class: middle ```haskell -- src/Logger.purs trace :: String -> Effect Unit *trace = debug Trace ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console import Prelude foreign import customTrace :: String -> Effect Unit data Level = Log | Warn | Error | Trace debug :: Level -> String -> Effect Unit debug Log message = Console.log message debug Warn message = Console.warn message debug Error message = Console.error message debug Trace message = customTrace message log :: String -> Effect Unit log = debug Log warn :: String -> Effect Unit warn = debug Warn error :: String -> Effect Unit error = debug Error trace :: String -> Effect Unit trace = debug Trace ``` --- class: middle ```haskell -- src/Logger.purs module Logger where import Effect (Effect) import Effect.Console as Console import Prelude *foreign import customTrace :: String -> Effect Unit data Level = Log | Warn | Error * | Trace debug :: Level -> String -> Effect Unit debug Log message = Console.log message debug Warn message = Console.warn message debug Error message = Console.error message *debug Trace message = customTrace message log :: String -> Effect Unit log = debug Log warn :: String -> Effect Unit warn = debug Warn error :: String -> Effect Unit error = debug Error *trace :: String -> Effect Unit *trace = debug Trace ``` --- class: middle # Incremental integration with PureScript ### JavaScript interop --- class: middle ## With purs-loader ```javascript import * as Logger from './Logger.purs'; Logger.log('Easy to use from JavaScript!')(); ``` ## Consuming compilation output ```javascript const Logger = require('../output/Logger'); Logger.log('Easy to use from JavaScript!')(); ``` --- class: middle ## With purs-loader ```javascript import * as Logger from `'./Logger.purs'`; Logger.log('Easy to use from JavaScript!')(); ``` ## Consuming compilation output ```javascript const Logger = require(`'../output/Logger'`); Logger.log('Easy to use from JavaScript!')(); ``` --- class: middle ## With purs-loader ```javascript import * as Logger from './Logger.purs'; *Logger.log('Easy to use from JavaScript!')(); ``` ## Consuming compilation output ```javascript const Logger = require('../output/Logger'); *Logger.log('Easy to use from JavaScript!')(); ``` --- class: middle # Types serve multiple purposes --- class: middle ## they serve as documentation ## ## ## --- class: middle ## they serve as documentation ## they help with design ## ## --- class: middle ## they serve as documentation ## they help with design ## they validate the implementation ## --- class: middle ## they serve as documentation ## they help with design ## they validate the implementation ## they ease refactoring --- class: middle focused ```haskell range :: Int -> Int -> Array Int ``` -- ```haskell contains :: Pattern -> String -> Boolean ``` -- ```haskell last :: List Int -> Maybe Int ``` -- ```haskell div :: Array Props -> Array ReactElement -> ReactElement ``` -- ```haskell querySelector :: QuerySelector -> ParentNode -> Effect (Maybe Element) ``` --- class: middle # What else can the compiler do? --- ### Point typos and type errors -- ```haskell add :: Int -> Int -> Int add a b = a + b ``` -- ```haskell ad 1 "2" ``` -- .hljs-default[.hljs[.error[ ``` Unknown value ad ``` ]]] -- ```haskell add 1 "2" ``` -- .hljs-default[.hljs[.error[ ``` Could not match type String with type Int ... ``` ]]] -- ```haskell add 1 2 ``` --- ### Error on missing interface implementation (type classes) ```haskell data Action = Increase Int | Decrease Int | Reset ``` -- ```haskell main = Console.logShow (Increase 3) ``` -- .hljs-default[.hljs[.error[ ``` No type class instance was found for Data.Show.Show Action while applying a function logShow of type Show t1 => t1 -> Effect Unit to argument Increase 3 ... ``` ]]] ??? TypeClasses very useful for library authors -- ```haskell instance showAction :: Show Action where show (Increase a) = "Increase " <> (show a) show (Decrease a) = "Decrease " <> (show a) show (Reset) = "Reset" ``` --- ### Give suggestions -- ```haskell formatted :: Array Int -> Array String formatted array = ?iDontRemember toString array where toString :: Int -> String toString = show ``` --- ### Give suggestions ```haskell formatted :: Array Int -> Array String formatted array = `?iDontRemember` toString array where toString :: Int -> String toString = show ``` -- .hljs-default[.hljs[.error[ ``` Hole 'iDontRemember' has the inferred type (Int -> String) -> Array Int -> Array String You could substitute the hole with one of these values: Control.Applicative.liftA1 :: forall f a b. Applicative f => (a -> b) -> f a -> f b Control.Monad.liftM1 :: forall m a b. Monad m => (a -> b) -> m a -> m b Data.Functor.map :: forall a b f. Functor f => (a -> b) -> f a -> f b Data.Monoid.mempty :: forall m. Monoid m => m in the following context: array :: Array Int toString :: Int -> String in value declaration formatted ``` ]]] --- ### Final words -- ### 🕵️♀️ Get out of comfort zone It's not going to be easy. You will make mistakes. You will also learn a lot.
Great path to find about things you didn't know existed.
-- ### 🍜 Explore PureScript, Elm, ReasonML, CLJS they're out there for you to discover.
Different flavors, there's no one right choice. Find one to have fun with. -- ### 🍼 Enjoy the newbie experience Remember your first success? First a-ha! moments? Unlearn to learn.
It's joy to make breakthroughs and discover things again. --
Finally, take it easy.
When stuck, just take a break from it!
--- # Useful resources Official website
https://www.purescript.org/ Package database and search
https://pursuit.purescript.org/ PureScript by Example by Phil Freeman
https://leanpub.com/purescript PureScript workshop by Nicholas Kariniemi & Antti Holvikari
https://github.com/reaktor/purescript-workshop (repository)
https://reaktor.github.io/purescript-workshop (slides) Functional Programming Concepts in PureScript by Vincent Orr
https://egghead.io/courses/functional-programming-concepts-in-purescript PS Unscripted on PureScript community YouTube channel https://www.youtube.com/channel/UCPtHLGu_WXh-OvX8NAVtDEw --- class: middle ## thank you! [.handle[@maciejsmolinski]](https://twitter.com/maciejsmolinski) ??? Nice to have: - dark theme - handle styling improved - language logos Other: - pursuit (type search) - compiles down to javascript - show output example - the next invention, try without catch? solvable - newtypes, lightweight no runtime representation - file read + print - use types to design programs, undefined definition, partial - x Pattern matching + totality - x type classes for library writing - x or fail custom console by providing number/bool - x example: orchy, no dependencies, error handling - x skip naming on purpose - x advanced topics, type classes, monads etc - x composition / do notation / take vals from effect - x merge errors with the slides not to lose the context x - e.g. missing Effect with first code, missing Unit with second - x composition - x thomas honeyman Ideas: - CloPuReasonScriptElm - Lisp lang generating PureScript code (macros??) being compiled down to JS (defspec formatted (Array Int) (Array String)) (defn formatted [arr] (map toString arr) (where [toString #(String. %)])) ??? ```javascript // src/DOM.js exports.render = function (text) { return function () { document.body.innerHTML = text; } } ``` # Agenda