Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Make your likes visible on Facebook?

Connect your Facebook account to Prezi and let your likes appear on your timeline.
You can change this under Settings & Account at any time.

No, thanks

From JavaScript to PureScript

No description
by

David Koontz

on 25 May 2016

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of From JavaScript to PureScript

Implementing First Pass
Very rough time getting started

All the previous feasibility tests needed to work 100% now

Original plan got put on hold to ship 1.0 (2 months)

Lots of little problems were uncovered as we went

New plan, vertical slice using Pux!

New problems!
From JavaScript to PureScript
Floating the Idea
Validating Concerns
The Vote
Lessons learned
Moving Forward
Initially I didn't even present PureScript as an option

When other options all failed to do what we wanted, I brought it up

There was cautious enthusiasm for the idea amongst the team and strong support from our team lead
There were surprisingly few places that could be pulled out cleanly

Eff / Aff seems very intimidating at first but is easily understood from the JS side

Pick your battles with conversion, leave stuff in-tact and come back later

FFI is extremely powerful, we never feel backed into a corner, but
very easy to get wrong
Training is high on the priority list

Slow rollout, converting when it makes sense, but get the top level stuff converted first

As PureScript in the app becomes more a reality, concerns around it rises within team

Currently have 1 PureScript developer who can work solo, that number needs to be everyone
Interop

Performance

Training
Vote: TypeScript vs PureScript

Team picked PureScript by a fair margin (7 to 2)

People were voting yes but did they know what they were in for?

Had I oversold the benefits?
Git Kraken
Cross platform desktop app

Built using Electron

Built on React + Flux

Built on NodeGit (C++)

~64,000 non-whitespace lines of JavaScript
Needs
React rendering performance improvements from immutability (pure render)

Enforcing standards beyond linting

Refactoring with confidence beyond unit tests

Ability to control effects that touch native code
Options
Flow
PureScript
TypeScript
Redux
Immutable.js
Elm
Compiling PureScript within Electron

Calling between JavaScript and PureScript

Passing data between the two

Embedding PureScript arbitrarily in JavaScript

The cost of curried functions

Immutable data

Runtime overhead
Sat down and 1 on 1 went through the benefits of static typing

Started with TypeScript syntax and transitioned to PureScript as examples got more sophisticated

Demonstrated examples of Maybe, pattern matching, ADT's, newtypes
Original plan was to replace Flux stores
Standalone
Data oriented

New plan is to replace the action, view, and store of one part of the app, now needed React integration

This can't be done all in 1 go, requires integrating with existing actions/stores during transition
Adapter subscribes to flux emits

Converts relevant changes to a Pux message
Flux emit
Pux message
data Message
=
AppMessage AppStateHandler.Message
| Child1 Child1.Message
| Child2 Child2.Message
...
Top level component dispatches these messages to a separate handler
Top level component
Child1
Child2
Child3
AppStateHandler
ChildA
ChildB
ChildC
Internal
message
AppState Message
update :: Message ->
AppState
-> State -> State


view ::
AppState
-> State -> Html Message
AppState is passed into component's update and view
update :: Message -> State -> State


view :: State -> Html Message
Regular Pux
GitKraken with AppState
update :: AppMessage -> AppState -> AppState
Some nice wins along the way, locking NodeGit operations
return asyncAction(constants.Commit.actions.ACTION_NAME, initialState)
.
addWork
(actionThatDoesntNeedAnyLocking)
.
addWork
(
constants.Lock.INDEX
, (result, state) =>
actions.Foo.doSomethingThatNeedsToBeLocked(state.bar)
)
.
addWork
(
constants.Lock.INDEX
, (result, state) =>
dangerousThingThatNeedsToBeLocked(result, state.baz)
.then(() => nextStepInProcess())
.then(() => iSureHopeThisDoesntLockAlsoOrWeDeadlock())
)
Existing system locked on a per-promise level, if functions that are called inside a lock try to acquire the same lock, they will never be able to progress
Using effect types we can guarantee a lock is obtained
lockConfig :: forall eff a.
StateT Locks (Aff (configlock :: CONFIGLOCK | eff)) a
-> StateT Locks (Aff eff) a
NodeGit functions that need a lock are given an effect type of XYZLock
setPushUrl :: forall eff.
Repository
-> String
-> Url
-> Aff (configlock :: CONFIGLOCK | eff) Unit
The type of our top level component has a closed effect row that does NOT contain any of the lock types

So the effect must be consumed by a function, this is done similarly to catchException
The StateT Locks keeps track of what locks we've obtained
so far so any attempt to re-obtain the lock will be no-ops
A run function is then used to get the Aff out of the StateT Locks
runNodeGitAction :: forall eff a. StateT Locks (Aff eff) a -> Aff eff a
myAction :: forall eff a. Aff eff a
myAction =
runNodeGitAction do
lockIndex $ liftAff do
affThatRequiresIndexLock
affThatInternallyUsesLockIndex
liftAff regularAffThatDoesntNeedLocking
liftAff is needed to turn the Aff into a StateT Locks
Challenges
Type errors are extremely intimidating for newcomers

Error messages involving large records are difficult to read

Tooling is an issue generally and switching to "better tooling" via Emacs or Vim is not realistic for our team, Atom is what we use (it is improving rapidly though, thanks Nate!)

Some devs previously worked in C# and are used to very reliable intellisense, frustrating when this doesn't work, especially for local variables

No refactoring tooling

Non-standard project structure makes tooling even more problematic, psc-ide-server must be started with a custom directory, Pulp doesn't work without extra params, which necessitates custom build scripts

Huge
thanks to everyone who has helped me get this far
Gary Burgess
John De Goes
Phil Freeman
Harry Garrood
Dennis Gosnell
Joel Grus
Christoph Hegemann
Language transition from JS is quite rough
Lots of new concepts that seem strange

This is not a "read a book over the weekend" situation

Purity forces some structure that gets in the way of development
unsafeLog :: a -> b -> b
During dev, especially when dealing with FFI some practical "impurity" is very helpful
function unsafeLog (valueToLog) {
return function (valueToReturn) {
console.log(valueToLog);
return valueToReturn;
}
}
The ability to "see behind the curtain" in JavaScript is
very
helpful
PureScript syntax is much less scary
when you can see what it translates
to
Will Jones
Alex Mingoia
Matt Parsons
thimoteus
Nate Wolverson
And everyone else that I am forgetting
*Also see Debug.Trace
-- PureScript

module Utils (AwesomeThingType, doAwesomeThing) where

data AwesomeThingType
= Kittens
| Ponies
| FpConferences

howAwesomeIsThing :: AwesomeThingType -> Int
howAwesomeIsThing Kittens = 5
howAwesomeIsThing Ponies = 7
howAwesomeIsThing FpConferences = 11


// JavaScript

const Utils = require('utils');
log(Utils.howAwesomeIsThing(Utils.Kittens.Value));
-- PureScript code

module Foreign where

foreign import jsThing :: Int -> Int -> Int

doThingFromJs :: String -> Int -> Int
doThingFromJs a b = jsThing (length a) b


// JavaScript code

// module Foreign

function jsThing(param1) {
return function(param2) {
return param1 + param2
}
}

module.exports = { jsThing: jsThing }
module Main where

import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)

functionWith2Params :: Int -> Int -> Int
functionWith2Params a b = a + b

main :: forall e. Eff (console :: CONSOLE | e) Unit
main = do
log "Hello sailor!"
// Generated by psc version 0.8.5.0
"use strict";
var Prelude = require("../Prelude");
var Control_Monad_Eff =
require("../Control.Monad.Eff");
var Control_Monad_Eff_Console =
require("../Control.Monad.Eff.Console");
var main =
Control_Monad_Eff_Console.log("Hello sailor!");
var functionWith2Params = function (a) {
return function (b) {
return a + b | 0;
};
};

module.exports = {
main: main,
functionWith2Params: functionWith2Params
};
http://bit.ly/1TKPGJ1
Purescript is installed as a npm dev dependency

Run psc / psa from a script that invokes node_modules/.bin/psa

Currently done from within electron-compile
Full transcript