Factor!

https://factorcode.org

  • Development started in 2003
  • Open source (BSD license)
  • Influenced by Forth, Lisp, and Smalltalk
  • Blurs the line between language and library
  • Interactive development
  • Upcoming release: 0.99, then... 0.100?

Concepts

  • Concatenative
  • Dynamic types
  • Extensible syntax
  • Fully-compiled
  • Cross-platform
  • Clickable
  • Useful?

Concatenative

1 2 + .
3 weeks ago noon .
"hello" rot13 .
URL" https://factorcode.org" http-get
10 [ "Hello, Factor" print ] times
{ 4 8 15 16 23 42 } [ sum ] [ length ] bi / .

Words

  • Defined at parse time
  • Parts: name, stack effect, definition
  • Composed of tokens separated by whitespace
: palindrome? ( string -- ? ) dup reverse = ;
  • Unit tests
{ f } [ "hello" palindrome? ] unit-test

{ t } [ "racecar" palindrome? ] unit-test

Quotations

  • Quotation: un-named blocks of code
[ "Hello, World" print ]
  • Combinators: words taking quotations
10 dup 0 < [ 1 - ] [ 1 + ] if .
{ -1 1 -2 0 3 } [ 0 max ] map .

Vocabularies

  • Vocabularies: named sets of words
    Vocabulary index
    USING: loads dependencies
  • Source, docs, tests in one place

Debugging Tools

  • Let's implement the fortune program
"/usr/local/share/games/fortunes/science"
ascii file-lines
{ "%" } split random
[ print ] each
  • Let's try it out!

Native Performance

wc -l factor.image

  • Counting lines is reasonably fast...
: simple-wc ( path -- n )
    binary <file-reader> [
        0 swap [
            [ CHAR: \n = ] count +
        ] each-stream-block-slice
    ] with-disposal ;
[ "factor.image" simple-wc ] time

Native Performance

  • But it can be even faster!
USE: tools.wc
[ "factor.image" wc ] time
  • Deploy as binary
"tools.wc" deploy

Visual Tools

"Bigger" { 12 18 24 72 } [
    font-size associate format nl
] with each
10 <iota> [
    "Hello, World!"
    swap 10 / 1 over - over 1 <rgba>
    background associate format nl
] each
USE: images.http
"https://factorcode.org/logo.png" http-image.
USE: lcd
<time-display> gadget.

Interactive Development

  • Programming is hard, let's play tetris
    tetris
  • Tetris is hard too... let's cheat
"tetris.tetromino" edit
  • Factor workflow: change code, F2, test, repeat

Parsing Words

  • Extensible syntax, DSLs
  • Most parsing words fall in one of two categories
  • First category: literal syntax for new data types
  • Second category: defining new types of words
  • Some parsing words are more complicated

Parsing Words - Pairs

  • Generic array syntax
{ 1 2 }
\ { see
  • Custom pair syntax
SYNTAX: => dup pop scan-object 2array suffix! ;
1 => 2

Parsing Words - Dice

dice

ROLL: 2d8+5

"You do %s points of damage!\n" printf
\ ROLL: see
[ ROLL: 2d8+5 ] .

Parsing Words - Regexp

regexp

  • Pre-compiles regexp at parse time
  • Implemented with library code
"ababbc" "[ab]+c" <regexp> matches? .
"ababbc" R/ [ab]+c/ matches? .

Parsing Words - XML

xml

  • Implemented with library code
  • Useful syntax forms
{ "three" "blind" "mice" }
[ [XML <li><-></li> XML] ] map
[XML <ul><-></ul> XML]
pprint-xml

Local Variables

  • Sometimes, there's no good stack solution to a problem
  • Or, you're porting existing code in a quick-and-dirty way
  • Combinator with 5 parameters!
:: branch ( a b neg zero pos -- )
    a b = zero [ a b < neg pos if ] if ; inline
  • Unwieldy with the stack

Local Variables

: check-voting-age ( age -- )
    18
    [ "You're underage, sorry..." print ]
    [ "Yay, register to vote!" print ]
    [ "Participate in democracy!" print ]
    branch ;
  • Locals are entirely implemented in Factor
  • Example of compile-time meta-programming
  • No performance penalty -vs- using the stack

Dynamic Variables

  • Implemented as a stack of hashtables
  • Useful words are get, set
  • Input, output, error streams are stored in dynamic variables
  • Read from a string...
"cat\ndog\nfish" [ readln ] with-string-reader
  • Read from a file...
"LICENSE.txt" utf8 [ readln ] with-file-reader

Destructors

  • Deterministic resource disposal
  • Any step can fail and we don't want to leak resources
  • We want to conditionally clean up sometimes
: do-stuff ( -- )
    [
        256 malloc &free
        256 malloc &free
        ... work goes here ...
    ] with-destructors ;

Profiling

: fib ( m -- n )
    dup 1 > [
        [ 1 - fib ] [ 2 - fib ] bi +
    ] when ;
[ 40 fib ] time
  • Very slow! Let's profile it...
[ 40 fib ] profile
  • Not tail recursive
  • Call tree is huge

Profiling - Typed

  • Type declarations with TYPED:
TYPED: fib ( m: fixnum -- n )
    dup 1 > [
        [ 1 - fib ] [ 2 - fib ] bi +
    ] when ;
  • A bit faster

Profiling - Memoize

  • Memoization using MEMO:
MEMO: fib ( m -- n )
    dup 1 > [
        [ 1 - fib ] [ 2 - fib ] bi +
    ] when ;
  • Much faster
10,000 fib number>string 
80 group [ print ] each

Macros

  • Expand at compile-time
  • Return a quotation to be compiled
  • Can express non-static stack effects
MACRO: ndup ( n -- quot )
    [ \ dup ] [ ] replicate-as ;
[ 5 ndup ] infer
[ 5 ndup ] expand-macros

PEG / EBNF

EBNF:: a complex parsing word

  • Implements a custom syntax for expressing parsers
  • Example: printf
"Factor"
2003 <year> ago duration>years

"%s is %d years old\n" printf
[ "%s monkeys" printf ] expand-macros

Objects

  • A tuple is a user-defined class which holds named values.
TUPLE: rectangle width height ;

TUPLE: circle radius ;

Objects

  • Constructing instances:
rectangle new
rectangle boa
  • Let's encapsulate:
: <rectangle> ( w h -- r ) rectangle boa ;

: <circle> ( r -- c ) circle boa ;

Single Dispatch

GENERIC: area ( shape -- n )
  • Two methods:
M: rectangle area
    [ width>> ] [ height>> ] bi * ;

M: circle area radius>> sq pi * ;
  • We can compute areas now.
100 20 <rectangle> area .
3 <circle> area .

Multiple Dispatch

SINGLETONS: rock paper scissors ;
  • Win conditions:
FROM: multi-methods => GENERIC: METHOD: ;

GENERIC: beats? ( obj1 obj2 -- ? )

METHOD: beats? { scissors paper } 2drop t ;
METHOD: beats? { rock  scissors } 2drop t ;
METHOD: beats? { paper     rock } 2drop t ;
METHOD: beats? { object  object } 2drop f ;

Multiple Dispatch

  • Let's play a game...
: play. ( obj -- )
    { rock paper scissors } random {
        { [ 2dup beats? ] [ "WIN" ] }
        { [ 2dup = ] [ "TIE" ] }
        [ "LOSE" ]
    } cond "%s vs. %s: %s\n" printf ;
  • With a simple interface:
: rock ( -- ) \ rock play. ;
: paper ( -- ) \ paper play. ;
: scissors ( -- ) \ scissors play. ;

Object System

  • Supports "duck typing"
  • Two tuples can have a slot with the same name
  • Code that uses accessors will work on both
  • Objects are not hashtables; slot access is very fast
  • Tuple slots can be reordered/redefined
  • Instances in memory will be updated

Object System

  • Predicate classes
PREDICATE: positive < integer 0 > ;
PREDICATE: negative < integer 0 < ;

GENERIC: abs ( m -- n )

M: positive abs ;
M: negative abs -1 * ;
M: integer abs ;

Object System

  • And lots more features...
  • Inheritance, type declarations, read-only slots, union, intersection, singleton classes, reflection
  • Object system is entirely implemented in Factor

Assembly

  • Access the Time Stamp Counter
HOOK: rdtsc cpu ( -- n )

M: x86.64 rdtsc
    longlong { } cdecl [
        RAX 0 MOV
        RDTSC
        RDX 32 SHL
        RAX RDX OR
    ] alien-assembly ;

FFI

NAME
     sqrt – square root function

SYNOPSIS
     #include <math.h>

     double
     sqrt(double x);
  • Let's use it!
FUNCTION: double sqrt ( double x )

Infix

  • Syntax experiments with infix
  • Infix word definitions:
INFIX:: foo ( x y -- z ) sqrt(x)+y**3 ;
  • Inline also:
[let "hello" :> seq
    [infix seq[::-1] infix]
]

Implementation

  • VM in C++ (12,000 lines of code)
  • VM features primitives, garbage collection, etc.
  • Lines of code: 300,000
  • Lines of tests: 80,000
  • Lines of docs: 70,000
  • One big repository, and we love contributions!

Project Infrastructure

https://factorcode.org
https://concatenative.org
https://docs.factorcode.org
https://planet.factorcode.org
https://paste.factorcode.org

  • Uses our HTTP server, SSL, DB, Atom libraries...

Project Infrastructure

  • Build farm, written in Factor
  • Multiple OS and architecture
  • Builds Factor and all libraries, runs tests, makes binaries
  • Saves us from the burden of making releases by hand
  • Maintains stability

Demo

  • It is hard to cover everything in a single talk
  • Factor has many cool things that I didn't talk about
"demos" run
  • Let's look at a real program!

Cool Things

USE: xkcd
XKCD: 138
USE: reddit
"programming" subreddit.

Cool Things

minesweeper
game-of-life
boids
pong

Cool Things

  • 8080 cpu emulator
"resource:roms" rom-root set-global

roms.space-invaders

Cool Things

bloom-filters
cuckoo-filters
persistent
trees
tuple-arrays
specialized-arrays

Cool Things

USE: text-to-speech
"hello" speak-text
USE: morse
"hello" play-as-morse
USE: flip-text
"hello" flip-text .
USE: emojify
"I :heart: Factor! :+1!" emojify .

Cool Things

USE: google.charts
"x = \\frac{-b \\pm \\sqrt {b^2-4ac}}{2a}"
<formula> 200 >>width 75 >>height chart.
100 [ 100 random ] replicate
100 [ 100 random ] replicate
zip <scatter> chart.
"/usr/share/dict/words" utf8 file-lines
[ >lower 1 head ] histogram-by
sort-keys <bar>
    COLOR: green >>foreground
    400 >>width
    10 >>bar-width
chart.

Cool Things

  • Tab completion
http
P" vocab:math
COLOR: 

Cool Things

./factor -run=file-server
./factor -run=file-monitor
./factor -run=tools.dns microsoft.com
./factor -run=tools.cal