Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Making a simple Purescript Application

Making a simple Purescript Application

Small talk for a weekly live/web programming meetup at Futurice in Helsinki (and Tampere, London, Berlin)

On Google Docs: https://docs.google.com/presentation/d/1n-I4IlBgUzRyJfVMaOJh-wz1j1Le7MyFwgwr_meX0Uo/edit?usp=sharing

Justin Woo

May 20, 2016
Tweet

More Decks by Justin Woo

Other Decks in Programming

Transcript

  1. Making a simple Purescript
    Application
    Going from Node + Elm 0.16 to Purescript + Node FFI
    to write a torrent scraper

    View Slide

  2. Why a torrent scraper?
    Don’t trust any “solutions”
    Want to run this on a VPS (with just Node)
    Just need magnet links downloaded
    Don’t want to do this manually
    I’m forgetful (want to run on CRON, etc)

    View Slide

  3. Callback JS soup version (Worst of all worlds)
    RxJS version, JS still sucks
    Node + Elm version
    Elm for logic, but mechanics still separate and Rx powered
    Still requires startup of Elm runtime
    Ports interface still not so great
    Written in 0.16, 0.17 removed Signals, upgrade woes
    Previous versions?

    View Slide

  4. Why Purescript?
    Want more compiler-checked code
    Want real FFI
    Want main program mechanics to not be in JS
    Signals (FRP library in Elm) removed in 0.17, making
    interfacing and writing programs more annoying
    Want to remove more of my own code as I can
    Purescript is way cooler

    View Slide

  5. Quick introduction to PS
    FP lang inspired by Haskell
    Some big usability improvements
    Compiles to legible, real JS with no runtime
    Gives you real FFI
    More granular “Effects” system compared to Haskell’s “IO”
    main :: forall e. Eff (err :: EXCEPTION, get :: HTTP, post :: HTTP, console :: CONSOLE | e) Unit

    View Slide

  6. Porting Elm to PS
    Most Elm code stays the same (other than :/::, Elm List ->
    JS/PS Array), but gets suped-up
    Get to use real FFI, no awkward runtime/port interface
    Control through Aff (async effects, basically `ErrorT (ContT
    Unit (Eff e) a`)
    Typeclasses like Functor (aka Interfaces like IMappable)
    Introduce fine-grained Effects

    View Slide

  7. Real FFI
    --- Main.js ---
    exports.getTargetsPage = function (callback)
    {
    return function (url) {
    return function () {
    request(url, function () {
    [...]
    return callback(body)();
    [...]
    exports.scrapeHtml = function (selector) {
    return function (html) {
    var $ = cheerio.load(html);
    var targets = [];
    $(selector).each(function (i, e) { [...]
    return targets.reverse();
    --- Main.purs ---
    type Url = String
    type Selector = String
    type HtmlBody = String
    foreign import data HTTP :: !
    foreign import scrapeHtml :: Selector -> HtmlBody ->
    FetchedTargets
    foreign import getTargetsPage :: forall e.
    (HtmlBody -> Eff (http :: HTTP | e) Unit) -> Url
    -> Eff (http :: HTTP | e) Unit

    View Slide

  8. Control through Aff
    No need for juggling callbacks or whatever
    Aff (effects) Thing - “I am an asynchronous routine of (effects) returning (Thing)”
    getConfig :: forall e. Aff (fs :: FS | e) Config
    getDownloadedFiles :: forall e. Aff (fs :: FS | e) (Array FilePath)
    getFetchedTargets :: forall e. Url -> Selector -> Aff (http :: HTTP | e) FetchedTargets
    kickOffDownloads' :: forall e. DownloadTargets -> Aff (console :: CONSOLE | e) Unit
    main = launchAff $ do
    {url, selector, blacklist} downloadedFiles fetchedTargets kickOffDownloads' $ getDownloadTargets blacklist downloadedFiles fetchedTargets

    View Slide

  9. Typeclasses
    Programming to interfaces with generic types is awesome
    But can’t do non-built-in comparable types in Elm
    E.g. how do you make a Set with generic types?
    Elm:
    Instantiate collection w/ concrete ordering/compare function? https://github.
    com/eeue56/elm-all-dict
    Purescript:
    Use a collection using the Ord typeclass, then derive or implement Ord (like purescript-
    sets)
    OR Roll your own collection easily, define your own typeclass, have instances to back it

    View Slide

  10. Creating/Using Typeclasses
    foreign import data CrappyHashSet :: * -> *
    class CrappyHash a where
    crappyHash :: a -> String
    data Coords = Coords Int Int
    instance crappyHashCoords :: CrappyHash Coords where
    crappyHash (Coords x y) = "x:" ++ (show x) ++ "y:" ++ (show y)
    foreign import empty :: forall a. CrappyHashSet a
    foreign import insert :: forall a. (CrappyHash a) => a -> CrappyHashSet a -> CrappyHashSet a
    foreign import mapToArray :: forall a b. (a -> b) -> CrappyHashSet a -> Array b

    View Slide

  11. Functor - “Functors can be thought of as homomorphisms between categories.” -
    Wikipedia
    Homomorphisms - “Structure-preserving operation”, i.e. map
    Categories - “‘Arrow’”-linked ‘objects’”, e.g. array
    (infix map) - “I am a LOW priority operator for which I take the function
    on my left and map it to the thing on my right”
    Can be used with anything with an instance of Functor defined
    instance functorAff :: Functor (Aff e) where
    map f fa = [...]
    class Functor f where map :: (a -> b) -> f a -> f b

    View Slide

  12. Functor in Action
    foreign import parseConfigFile :: String -> Config
    foreign import configPath :: String
    getConfig :: forall e. Aff (fs :: FS | e) Config
    getConfig = parseConfigFile readTextFile UTF8 configPath
    foreign import scrapeHtml :: Selector -> HtmlBody -> FetchedTargets
    foreign import getTargetsPage :: forall e.
    (HtmlBody -> Eff (http :: HTTP | e) Unit) -> Url ->
    Eff (http :: HTTP | e) Unit
    getFetchedTargets :: forall e. Url -> Selector -> Aff (http :: HTTP | e) FetchedTargets
    getFetchedTargets url selector =
    scrapeHtml selector makeAff (\e s -> getTargetsPage s url)

    View Slide

  13. Fine-grained Effects
    “Side”-effects as part of the signature as extensible records
    -- Filesystem Effects: --
    getConfig :: forall e. Aff (fs :: FS | e) Config
    getDownloadedFiles :: forall e. Aff (fs :: FS | e) (Array FilePath)
    -- HTTP Effects: --
    getFetchedTargets :: forall e. Url -> Selector -> Aff (http :: HTTP | e) FetchedTargets
    -- Console Effects: --
    foreign import kickOffDownloads :: forall e. DownloadTargets -> Eff (console :: CONSOLE | e) Unit
    kickOffDownloads' :: forall e. DownloadTargets -> Aff (console :: CONSOLE | e) Unit
    -- Our Main using all of these effects: --
    main :: forall e. Eff (err :: EXCEPTION, fs :: FS, http :: HTTP, console :: CONSOLE | e) Unit

    View Slide

  14. Results
    ✓ Works when source compiles
    ✓ Real FFI, no more runtime/port interfaces
    ✓ Contains the main mechanics of the program
    ✓ Can write code to typeclasses and their instances
    ✓ JS code reduced/minimized
    ✓ Is cool

    View Slide

  15. Etc
    Repo: https://github.com/justinwoo/torscraper
    Purescript:
    http://www.purescript.org/
    https://leanpub.com/purescript/read
    https://github.com/purescript/purescript/wiki/Language-Guide
    Might consider: http://haskellbook.com/
    My spam channel: @jusrin00

    View Slide