Vapour

John Coene

john@opifex.org

I’m a software engineer, founder of Opifex

john-coene.com/talks/shinyconf

Introduction

What it is

Vapour is a typed superset of the R programming language.

It’s very, very new, it’s buggy, and limited.

Story

I thought about this 4 to 5 years ago, lead to pre-processing R code, an ISC submission but no funding.

I’ve tried and failed to create this multiple times over the years.

This is the first “successful” attempt.

Inspiration

Vapour aims to be to R what Typescript is to Javascript.

A slightly different syntax that produces valid R code.

The syntax and type system are inspired from Go: Go’s methods == R S3.

What it looks like

A glimpse of vapour.

type person: object {
 age: int,
 name: char 
}

func create(name: char): person {
  return person(name = name)
}

let john: person = create("John")

Why?

Frustrations (1)

The language should warn me of such problems in my code, not the users of my applications or packages.

if(NA){
  print("this will error")
}

Frustrations (2)

Though the function below looks fine it really isn’t.

foo <- \(x) x + 1

foo("hello") # error

foo() # unhandled error

Frustrations (3)

bar <- function(x) {
  if(x > 1) return()
  x + 42
}
  • What is x?
  • No explicit return
  • Return different types

Frustrations (4)

Can you translate that structure to any other language?

list(
  x = 1,
  "hello",
  y = 2
)

Frustrations (Inf)

Methods and dots in identifier.

foo <- function(x) UseMethod("foo")
foo.data <- function(x) x
foo.data.frame <- function(x) x

foo.data <- function(x) UseMethod("foo.data")
foo.data.frame <- function(x) x

What even is this?

Preprocessing code

Dynamic languages Interpreted: R, Python, Javascript, etc.

Compiled languages Compiles to machine code: C, Go, Rust, …

Preprocessors

Babel, uglify, etc.

How it works

1. Lexer

Code to array of meaningful characters.

Based on Go’s lexer for templating language, see Rob Pike’s presentation.

  • Character by character
  • States vs. switch
func (l *Lexer) Lex() {
    for state := lexDefault; state != nil; {
        state = state(l)
    }
}

func lexComment(l *Lexer) stateFn {
    r := l.peek(1)
    for r != '\n' && r != token.EOF {
        l.next()
        r = l.peek(1)
    }
    l.emit(token.ItemComment)
    return lexDefault
}

2. Parser

Build the abstract syntax tree.

Pratt parser based on Thorsten’s Writing An Interpreter In Go

Based on operator precedence: 1 + 2 * 5

const (
    _ int = iota
    LOWEST
    EQUALS      // ==
    LESSGREATER // > or <
    SUM         // +
    PRODUCT     // *
    PREFIX      // -X or !X
    CALL        // call(X)
)

3. Type checker

Traverses the tree to implements numerous checks:

  • Types
  • Declarations
  • Use
  • Missing check
  • more …

4. Transpiler

Traverses the tree to convert vapour AST to valid R code.

func addOne(x: int = 41): int {
  return x + 1
}

addOne(1)
addOne <- function(x = 41) {
  return(x + 1)
}

addOne(1)

How to use

Install vapour, setup editor.

  1. Write code in .vp file
  2. Run vapour -infile=file.vp

(Quick) Demo