Slide 1

Slide 1 text

Ur/Web Vlad Ki @darkproger lightning fast prototyping for the web

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Stuff you have to do sometimes: web apps

Slide 4

Slide 4 text

Stuff you sometimes have to do in web apps: tons of forms and validation

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Runtime errors on typos

Slide 8

Slide 8 text

Rather have ugly type errors early than fancy runtime errors late

Slide 9

Slide 9 text

Want types out of the box haskell-servant/servant

Slide 10

Slide 10 text

protobuf.load("awesome.proto", function(err, root) { if (err) throw err; // Obtain a message type var AwesomeMessage = root.lookupType("awesomepackage.AwesomeMessage"); // Exemplary payload var payload = { awesomeField: "AwesomeString" }; // Verify the payload if necessary (i.e. when possibly incomplete or invalid) var errMsg = AwesomeMessage.verify(payload); if (errMsg) throw Error(errMsg); // Create a new message var message = AwesomeMessage.create(payload); // or use .fromObject if conversion is necessary // Encode a message to an Uint8Array (browser) or Buffer (node) var buffer = AwesomeMessage.encode(message).finish(); // ... do something with buffer // Decode an Uint8Array (browser) or Buffer (node) to a message var message = AwesomeMessage.decode(buffer); // ... do something with message // If the application uses length-delimited buffers, there is also encodeDelimited and decodeDelimited. // Maybe convert the message back to a plain object var object = AwesomeMessage.toObject(message, { longs: String, enums: String, bytes: String, // see ConversionOptions }); });

Slide 11

Slide 11 text

protobuf.load("awesome.proto", function(err, root) { if (err) throw err; // Obtain a message type var AwesomeMessage = root.lookupType("awesomepackage.AwesomeMessage"); // Exemplary payload var payload = { awesomeField: "AwesomeString" }; // Verify the payload if necessary (i.e. when possibly incomplete or invalid) var errMsg = AwesomeMessage.verify(payload); if (errMsg) throw Error(errMsg); // Create a new message var message = AwesomeMessage.create(payload); // or use .fromObject if conversion is necessary // Encode a message to an Uint8Array (browser) or Buffer (node) var buffer = AwesomeMessage.encode(message).finish(); // ... do something with buffer // Decode an Uint8Array (browser) or Buffer (node) to a message var message = AwesomeMessage.decode(buffer); // ... do something with message // If the application uses length-delimited buffers, there is also encodeDelimited and decodeDelimited. // Maybe convert the message back to a plain object var object = AwesomeMessage.toObject(message, { longs: String, enums: String, bytes: String, // see ConversionOptions }); }); Want to avoid encoding/decoding values all over the place google/protobuf

Slide 12

Slide 12 text

{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-} module Db where import Elm import GHC.Generics data Person = Person { id :: Int , name :: Maybe String } deriving (Show, Eq, Generic, ElmType) import Data.Proxy import Db import Elm spec :: Spec spec = Spec ["Db", "Types"] [ "import Json.Decode exposing (..)" , "import Json.Decode.Pipeline exposing (..)" , toElmTypeSource (Proxy :: Proxy Person) , toElmDecoderSource (Proxy :: Proxy Person) ] main :: IO () main = specsToDir [spec] "some/where/output" Or write typing glue every time krisajenkins/elm-export

Slide 13

Slide 13 text



var roomName = {{ room_name_json }}; var chatSocket = new WebSocket( 'ws://' + window.location.host + '/ws/chat/' + roomName + '/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); var message = data['message']; document.querySelector('#chat-log').value += (message + '\n'); }; chatSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); };

Slide 14

Slide 14 text

var roomName = {{ room_name_json }}; var chatSocket = new WebSocket( 'ws://' + window.location.host + '/ws/chat/' + roomName + '/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); document.querySelector( '#chat-log' ).value += (data['message'] + '\n'); }; chatSocket.onclose = function(e) { console.error('oops'); };

Slide 15

Slide 15 text



var roomName = {{ room_name_json }}; var chatSocket = new WebSocket( 'ws://' + window.location.host + '/ws/chat/' + roomName + '/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); var message = data['message']; document.querySelector('#chat-log').value += (message + '\n'); }; chatSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); }; Stuff you hate doing in a web app: serialization, APIs and URLs

Slide 16

Slide 16 text

Let's try something else

Slide 17

Slide 17 text

N2O: a Nitrogen fork that lets you avoid JavaScript

Slide 18

Slide 18 text

Pretty much everything is Erlang process-like and records are everywhere

Slide 19

Slide 19 text

But you still need a full-time frontend hacker synrc/n2o

Slide 20

Slide 20 text

Shout out to Pyjamas

Slide 21

Slide 21 text

Not Contenders Elm / PureScript / Fable Python in the Browser Everything that "shares code"

Slide 22

Slide 22 text

Contenders meteor.js Ocsigen / Eliom Ur/Web

Slide 23

Slide 23 text

Ur/Web

Slide 24

Slide 24 text

The language part http:/ /www.impredicative.com/ur/tutorial/

Slide 25

Slide 25 text

The Language Almost like SML Fun with declaration ordering and mutual recursion ML modules and functors “header” files Compiler generates JS for you as well C and JS FFI, SQL and XML is sugar No REPL :D

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

fun id [a] (x : a) : a = x fun compose [a] [b] [c] (f : b -> c) (g : a -> b) (x : a) : c = f (g x) “-XUndecidableInstances”: type inference is undecidable by default so watch out

Slide 28

Slide 28 text

fun mp [a] [b] (f : a -> b) : list a -> list b = let fun loop (ls : list a) = case ls of [] => [] | x :: ls' => f x :: loop ls' in loop end

Slide 29

Slide 29 text

datatype tree a = Leaf of a | Node of tree a * tree a fun size [a] (t : tree a) : int = case t of Leaf _ => 1 | Node (t1, t2) => size t1 + size t2

Slide 30

Slide 30 text

signature S = sig val client_id : string val client_secret : string val https : bool val onLogin : profile -> transaction page end Module signatures

Slide 31

Slide 31 text

functor Make(M : S) : sig val authorize : transaction page val whoami : transaction (option string) val loadProfile : string -> transaction profile val logout : transaction unit end Functors: parametrized modules

Slide 32

Slide 32 text

val x = { A = 0, B = True } x.A type somerec = { A : int, B : bool } fun getA (r : somerec) = r.A getA (x -- #A ++ {A = 4}) Row types

Slide 33

Slide 33 text

val doStuff : transaction page = error oops IO = transaction

Slide 34

Slide 34 text

Web

Slide 35

Slide 35 text

#war-room bot: jenkins is down dev: phew, no more tyre fires dev: can finally have a weekend off dev: make sure nobody brings it back up ¯\_(ツ)_/¯

Slide 36

Slide 36 text

code: proger/urc

Slide 37

Slide 37 text

fun main () : transaction page = {User = user, Header = hdr} <- userHeader; textId <- fresh; text <- source ""; init <- prefetch (); ch <- subscribe; rest <- source ; received <- source []; let (* ... *) in return (Def.page "Urc" hdr (onload ())

chatroom

submit ()} />
{init} ) end transaction generates both client and server code

Slide 38

Slide 38 text

fun main () : transaction page = {User = user, Header = hdr} <- userHeader; textId <- fresh; text <- source ""; init <- prefetch (); ch <- subscribe; rest <- source ; received <- source []; let (* ... *) in return (Def.page "Urc" hdr (onload ())

chatroom

submit ()} />
{init} ) end

Slide 39

Slide 39 text

fun listen () = s <- recv ch; xs <- get received; set received (s :: xs); set rest (List.foldr join (List.mp format (s :: xs))); listen () and onload () = _ <- reset (); listen () and submit () : transaction unit = message <- get text; time <- now; rpc (publish' {Author = user, Timestamp = time, Message = message}); _ <- reset (); return ()

Slide 40

Slide 40 text

transaction generates both client and server code No POST, No GET No URLs No HTTP status codes (200 or 500) Client-to-server (rpc) Server-to-client (channel, signal + source + dyn)

Slide 41

Slide 41 text

Limited HTTP access Read-write Cookies Read-write Headers

Slide 42

Slide 42 text

Relax!

Slide 43

Slide 43 text

Sugar

Slide 44

Slide 44 text

% cat test.ur table users : { Bobby: bool, Id: int }

Slide 45

Slide 45 text

% urweb -dumpSource -timing test parseJob: 3.14E~4 parse: 1.87E~4 elaborate: 0.601906 unnest: 0.002338 explify: 0.002041 corify: 0.002383 core_untangle: 2.81E~4 shake1: 2.23E~4 especialize1': 1.8E~5 shake1': 3E~6 rpcify: 1.9E~5 core_untangle2: 0 shake2: 2E~6 especialize1: 1E~6 core_untangle3: 0 shake3: 1E~6 tag: 3.3E~5 reduce: 4.4E~5 shakey: 1E~6 unpoly: 1.7E~5 specialize: 1.7E~5 shake4: 1E~6 especialize2: 1E~6 shake4': 1E~6 unpoly2: 1E~6 specialize2: 0 shake4': 0

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

val spinner : xbody =

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

ok <- oneRowE1 (SELECT COUNT( * ) > 0 FROM secrets WHERE secrets.Login = {[r.Login]} AND secrets.Secret = {[r.Secret]});

Slide 51

Slide 51 text

urweb -dumpSource -dumpTypes -stop elaborate test

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

% urweb -dumpSource -dumpTypes -stop tag test con int :: Type = FFI(Basis.int) con bool :: Type = FFI(Basis.bool) table users as Test_users : [#Bobby = bool, #Id = int] keys FFI(Basis.no_primary_key) [[#Bobby = bool, #Id = int]] constraints FFI(Basis.no_constraint) [[#Bobby = bool, #Id = int]]

Slide 54

Slide 54 text

Metaprogramming

Slide 55

Slide 55 text

x metaclasses x generating AST v type-safe functors!

Slide 56

Slide 56 text

structure Admin = Crud.Make(struct con key = #Login val tab = GH.users val title = "Authenticated Users" val everything = { Login = Crud.string "Login", AvatarUrl = Crud.string "Avatar URL", Nam = Crud.option_string "Name", (* ... *) } type task_type = {} val runTask = fn _ username => redirect (bless ("https://github.com/" ^ username)) val taskTitle = fn _ => "[Visit]" val allTasks = () :: [] val header = { User = _, Header = hdr } <- userHeader; return hdr end)

Slide 57

Slide 57 text

FFI

Slide 58

Slide 58 text

uw_Basis_string uw_WorldFfi_post(uw_context ctx, uw_Basis_string url, uw_Basis_string body) { CURL *c = curl(ctx); curl_easy_reset(c); curl_easy_setopt(c, CURLOPT_POSTFIELDS, body); uw_Basis_string ret = doweb(ctx, c, url, 1); return ret; }

Slide 59

Slide 59 text

val post : url -> string -> transaction string val get : url -> option string -> transaction string

Slide 60

Slide 60 text

Packaging and Deployment

Slide 61

Slide 61 text

Compiler generates C code Outputs a single binary Configuration is usually compiled in

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

{ stdenv, fetchgit, autoreconfHook, curl, urweb }: stdenv.mkDerivation { name = "urweb-curl"; buildInputs = [ autoreconfHook urweb ]; propagatedBuildInputs = [ curl ]; preConfigure = '' export CFLAGS="-I${urweb}/include/urweb" ''; src = fetchgit { inherit (builtins.fromJSON (builtins.readFile ./urweb-curl.json) ) url rev sha256; }; }

Slide 64

Slide 64 text

Getting Information

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

http:/ /www.impredicative.com/ur/demo/

Slide 67

Slide 67 text

Slide 68

Slide 68 text

twitter.com/kievfprog kievfprog.net