536 lines
17 KiB
Factor
536 lines
17 KiB
Factor
! Copyright (C) 2009 Doug Coleman.
|
|
! See http://factorcode.org/license.txt for BSD license.
|
|
USING: assocs combinators constructors eval help.markup kernel
|
|
multiline namespaces parser sequences sequences.private slides
|
|
vocabs.refresh words fry ;
|
|
IN: talks.tc-lisp-talk
|
|
|
|
CONSTANT: tc-lisp-slides
|
|
{
|
|
{ $slide "Factor!"
|
|
{ $url "http://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"
|
|
}
|
|
{ $slide "First, some examples"
|
|
{ $code "3 weeks ago noon monday ." }
|
|
{ $code "USE: roman 2009 >roman ." }
|
|
{ $code ": average ( seq -- x )
|
|
[ sum ] [ length ] bi / ;" }
|
|
{ $code "1 miles [ km ] undo >float ." }
|
|
{ $code "[ readln eval>string print t ] loop" }
|
|
}
|
|
{ $slide "XML Literals"
|
|
{ $code
|
|
"USING: splitting xml.writer xml.syntax ;
|
|
{ \"one\" \"two\" \"three\" }
|
|
[ [XML <item><-></item> XML] ] map
|
|
<XML <doc><-></doc> XML> pprint-xml"
|
|
}
|
|
}
|
|
{ $slide "Differences between Factor and Lisp"
|
|
"Single-implementation language"
|
|
"Less nesting, shorter word length"
|
|
{ "Dynamic reloading of code from files with " { $link refresh-all } }
|
|
"More generic protocols -- sequences, assocs, streams"
|
|
"More cross-platform"
|
|
"No standard for the language"
|
|
"Evaluates left to right"
|
|
}
|
|
{ $slide "Terminology"
|
|
{ "Words - functions" }
|
|
{ "Vocabularies - collections of code in the same namespace" }
|
|
{ "Quotations - blocks of code" { $code "[ dup reverse append ]" } }
|
|
{ "Combinators - higher order functions" }
|
|
{ "Static stack effect - known stack effect at compile-time" }
|
|
}
|
|
{ $slide "Defining a word"
|
|
"Defined at parse time"
|
|
"Parts: name, stack effect, definition"
|
|
"Composed of tokens separated by whitespace"
|
|
{ $code ": palindrome? ( string -- ? ) dup reverse = ;" }
|
|
}
|
|
{ $slide "Non-static stack effect"
|
|
"Not a good practice, nor useful"
|
|
"Not compiled by the optimizing compiler"
|
|
{ $code "100 <iota> [ ] each" }
|
|
}
|
|
{ $slide "Module system"
|
|
"Code divided up into vocabulary roots"
|
|
"core/ -- just enough code to bootstrap Factor"
|
|
"basis/ -- optimizing compiler, the UI, tools, libraries"
|
|
"extra/ -- demos, unpolished code, experiments"
|
|
"work/ -- your works in progress"
|
|
}
|
|
{ $slide "Module system (part 2)"
|
|
"Each vocabulary corresponds to a directory on disk, with documentation and test files"
|
|
{ "Code for the " { $snippet "math" } " vocabulary: " { $snippet "~/factor/core/math/math.factor" } }
|
|
{ "Documentation for the " { $snippet "math" } " vocabulary: " { $snippet "~/factor/core/math/math-docs.factor" } }
|
|
{ "Unit tests for the " { $snippet "math" } " vocabulary: " { $snippet " ~/factor/core/math/math-tests.factor" } }
|
|
}
|
|
{ $slide "Using a library"
|
|
"Each file starts with a USING: list"
|
|
"To use a library, simply include it in this list"
|
|
"Refreshing code loads dependencies correctly"
|
|
}
|
|
{ $slide "Object system"
|
|
"Based on CLOS"
|
|
{ "We define generic words that operate on the top of the stack with " { $link POSTPONE: GENERIC: } " or on an implicit parameter with " { $link POSTPONE: HOOK: } }
|
|
}
|
|
{ $slide "Object system example: shape protocol"
|
|
"In ~/factor/work/shapes/shapes.factor"
|
|
{ $code "IN: shapes
|
|
|
|
GENERIC: area ( shape -- x )
|
|
GENERIC: perimeter ( shape -- x )"
|
|
}
|
|
}
|
|
{ $slide "Implementing the shape protocol: circles"
|
|
"In ~/factor/work/shapes/circle/circle.factor"
|
|
{ $code "USING: shapes constructors math
|
|
math.constants ;
|
|
IN: shapes.circle
|
|
|
|
TUPLE: circle radius ;
|
|
CONSTRUCTOR: <circle> circle ( radius -- obj ) ;
|
|
M: circle area radius>> sq pi * ;
|
|
M: circle perimeter radius>> pi * 2 * ;"
|
|
}
|
|
}
|
|
{ $slide "Dynamic variables"
|
|
"Implemented as a stack of hashtables"
|
|
{ "Useful words are " { $link get } ", " { $link set } }
|
|
"Input, output, error streams are stored in dynamic variables"
|
|
{ $code "\"Today is the first day of the rest of your life.\"
|
|
[
|
|
readln print
|
|
] with-string-reader"
|
|
}
|
|
}
|
|
{ $slide "The global namespace"
|
|
"The global namespace is just the namespace at the bottom of the namespace stack"
|
|
{ "Useful words are " { $link get-global } ", " { $link set-global } }
|
|
"Factor idiom for changing a particular namespace"
|
|
{ $code "SYMBOL: king
|
|
global [ \"Henry VIII\" king set ] with-variables"
|
|
}
|
|
{ $code "with-scope" }
|
|
{ $code "namestack" }
|
|
}
|
|
{ $slide "Hooks"
|
|
"Dispatch on a dynamic variable"
|
|
{ $code "HOOK: computer-name os ( -- string )
|
|
M: macosx computer-name uname first ;
|
|
macosx \ os set-global
|
|
computer-name"
|
|
}
|
|
}
|
|
{ $slide "Interpolate"
|
|
"Replaces variables in a string"
|
|
{ $code
|
|
"\"Dawg\" \"name\" set
|
|
\"rims\" \"noun\" set
|
|
\"bling\" \"verb1\" set
|
|
\"roll\" \"verb2\" set
|
|
[
|
|
\"Sup ${name}, we heard you liked ${noun}, so we put ${noun} on your car so you can ${verb1} while you ${verb2}.\"
|
|
interpolate
|
|
] with-string-writer print "
|
|
}
|
|
}
|
|
{ $slide "Sequence protocol"
|
|
"All sequences obey a protocol of generics"
|
|
{ "Is an object a " { $link sequence? } }
|
|
{ "Getting the " { $link length } }
|
|
{ "Accessing the " { $link nth } " element" }
|
|
{ "Setting an element - " { $link set-nth } }
|
|
}
|
|
{ $slide "Examples of sequences in Factor"
|
|
"Arrays are mutable"
|
|
"Vectors are mutable and growable"
|
|
{ "Arrays " { $code "{ \"abc\" \"def\" 50 }" } }
|
|
{ "Vectors " { $code "V{ \"abc\" \"def\" 50 }" } }
|
|
{ "Byte-arrays " { $code "B{ 1 2 3 }" } }
|
|
{ "Byte-vectors " { $code "BV{ 11 22 33 }" } }
|
|
}
|
|
{ $slide "Specialized arrays and vectors"
|
|
{ "Specialized int arrays " { $code "int-array{ -20 -30 40 }" } }
|
|
{ "Specialized uint arrays " { $code "uint-array{ 20 30 40 }" } }
|
|
{ "Specialized float vectors " { $code "float-vector{ 20 30 40 }" } }
|
|
"35 others C-type arrays"
|
|
}
|
|
{ $slide "Specialized arrays code"
|
|
"One line per array/vector"
|
|
{ "In ~/factor/basis/specialized-arrays/float/float.factor"
|
|
{ $code "<< \"float\" define-array >>" }
|
|
}
|
|
{ "In ~/factor/basis/specialized-vectors/float/float.factor"
|
|
{ $code "<< \"float\" define-vector >>" }
|
|
}
|
|
}
|
|
|
|
{ $slide "Specialized arrays are implemented using functors"
|
|
"Like C++ templates"
|
|
"Eliminate boilerplate in ways other abstractions don't"
|
|
"Contains a definition section and a functor body"
|
|
"Uses the interpolate vocabulary"
|
|
}
|
|
{ $slide "Functor for sorting"
|
|
{ $code
|
|
"<FUNCTOR: define-sorting ( NAME QUOT -- )
|
|
|
|
NAME<=> DEFINES ${NAME}<=>
|
|
NAME>=< DEFINES ${NAME}>=<
|
|
|
|
WHERE
|
|
|
|
: NAME<=> ( obj1 obj2 -- <=> ) QUOT compare ;
|
|
: NAME>=< ( obj1 obj2 -- >=< )
|
|
NAME<=> invert-comparison ;
|
|
|
|
;FUNCTOR>"
|
|
}
|
|
}
|
|
{ $slide "Example of sorting functor"
|
|
{ $code "USING: sorting.functor ;
|
|
<< \"length\" [ length ] define-sorting >>"
|
|
}
|
|
{ $code
|
|
"{ { 1 2 3 } { 1 2 } { 1 } }
|
|
[ length<=> ] sort"
|
|
}
|
|
}
|
|
{ $slide "Combinators"
|
|
"Used to implement higher order functions (dataflow and control flow)"
|
|
"Compiler optimizes away quotations completely"
|
|
"Optimized code is just tight loops in registers"
|
|
"Most loops can be expressed with combinators or tail-recursion"
|
|
}
|
|
{ $slide "Combinators that act on one value"
|
|
{ $link bi }
|
|
{ $code "10 [ 1 - ] [ 1 + ] bi" }
|
|
{ $link tri }
|
|
{ $code "10 [ 1 - ] [ 1 + ] [ 2 * ] tri" }
|
|
}
|
|
{ $slide "Combinators that act on two values"
|
|
{ $link 2bi }
|
|
{ $code "10 1 [ - ] [ + ] 2bi" }
|
|
{ $link bi* }
|
|
{ $code "10 20 [ 1 - ] [ 1 + ] bi*" }
|
|
{ $link bi@ }
|
|
{ $code "5 9 [ sq ] bi@" }
|
|
}
|
|
{ $slide "Sequence combinators"
|
|
|
|
{ $link each }
|
|
{ $code "{ 1 2 3 4 5 } [ sq . ] each" }
|
|
{ $link map }
|
|
{ $code "{ 1 2 3 4 5 } [ sq ] map" }
|
|
{ $link filter }
|
|
{ $code "{ 1 2 3 4 5 } [ even? ] filter" }
|
|
}
|
|
{ $slide "Multiple sequence combinators"
|
|
|
|
{ $link 2each }
|
|
{ $code "{ 1 2 3 } { 10 20 30 } [ + . ] 2each" }
|
|
{ $link 2map }
|
|
{ $code "{ 1 2 3 } { 10 20 30 } [ + ] 2map" }
|
|
}
|
|
{ $slide "Control flow: if"
|
|
{ $link if }
|
|
{ $code "10 random dup even? [ 2 / ] [ 1 - ] if" }
|
|
{ $link when }
|
|
{ $code "10 random dup even? [ 2 / ] when" }
|
|
{ $link unless }
|
|
{ $code "10 random dup even? [ 1 - ] unless" }
|
|
}
|
|
{ $slide "Control flow: case"
|
|
{ $link case }
|
|
{ $code "ERROR: not-possible obj ;
|
|
10 random 5 <=> {
|
|
{ +lt+ [ \"Less\" ] }
|
|
{ +gt+ [ \"More\" ] }
|
|
{ +eq+ [ \"Equal\" ] }
|
|
[ not-possible ]
|
|
} case"
|
|
}
|
|
}
|
|
{ $slide "Fry"
|
|
"Used to construct quotations"
|
|
{ "'Holes', represented by " { $snippet "_" } " are filled left to right" }
|
|
{ $code "10 4 '[ _ + ] call" }
|
|
{ $code "3 4 '[ _ sq _ + ] call" }
|
|
}
|
|
{ $slide "Locals"
|
|
"When data flow combinators and shuffle words are not enough"
|
|
"Name your input parameters"
|
|
"Used in about 1% of all words"
|
|
}
|
|
{ $slide "Locals example"
|
|
"Area of a triangle using Heron's formula"
|
|
{ $code
|
|
":: area ( a b c -- x )
|
|
a b c + + 2 / :> p
|
|
p
|
|
p a - *
|
|
p b - *
|
|
p c - * sqrt ;"
|
|
}
|
|
}
|
|
{ $slide "Previous example without locals"
|
|
"A bit unwieldy..."
|
|
{ $code
|
|
": area ( a b c -- x )
|
|
[ ] [ + + 2 / ] 3bi
|
|
[ '[ _ - ] tri@ ] [ neg ] bi
|
|
* * * sqrt ;" }
|
|
}
|
|
{ $slide "More idiomatic version"
|
|
"But there's a trick: put the lengths in an array"
|
|
{ $code ": v-n ( v n -- w ) '[ _ - ] map ;
|
|
|
|
: area ( seq -- x )
|
|
[ 0 suffix ] [ sum 2 / ] bi
|
|
v-n product sqrt ;" }
|
|
}
|
|
{ $slide "Implementing an abstraction"
|
|
{ "Suppose we want to get the price of the customer's first order, but any one of the steps along the way could be a nil value (" { $link f } " in Factor):" }
|
|
{ $code
|
|
"dup [ orders>> ] when"
|
|
"dup [ first ] when"
|
|
"dup [ price>> ] when"
|
|
}
|
|
}
|
|
{ $slide "This is hard with mainstream syntax!"
|
|
{ $code
|
|
"var customer = ...;
|
|
var orders = (customer == null ? null : customer.orders);
|
|
var order = (orders == null ? null : orders[0]);
|
|
var price = (order == null ? null : order.price);" }
|
|
}
|
|
{ $slide "An ad-hoc solution"
|
|
"Something like..."
|
|
{ $code "var price = customer.?orders.?[0].?price;" }
|
|
}
|
|
{ $slide "Macros in Factor"
|
|
"Expand at compile-time"
|
|
"Return a quotation to be compiled"
|
|
"Can express non-static stack effects"
|
|
"Not as widely used as combinators, 60 macros so far"
|
|
{ $code "{ 1 2 3 4 5 } 5 firstn" }
|
|
}
|
|
{ $slide "A macro solution"
|
|
"Returns a quotation to the compiler"
|
|
"Constructed using map, fry, and concat"
|
|
{ $code "MACRO: plox ( seq -- quot )
|
|
[
|
|
'[ dup _ when ]
|
|
] map [ ] concat-as ;"
|
|
}
|
|
}
|
|
{ $slide "Macro example"
|
|
"Return the caaar of a sequence"
|
|
{ "Return " { $snippet "f" } " on failure" }
|
|
{ $code ": caaar ( seq/f -- x/f )
|
|
{
|
|
[ first ]
|
|
[ first ]
|
|
[ first ]
|
|
} plox ;"
|
|
}
|
|
{ $code "{ { f } } caaar" }
|
|
{ $code "{ { { 1 2 3 } } } caaar" }
|
|
}
|
|
{ $slide "Smart combinators"
|
|
"Use stack checker to infer inputs and outputs"
|
|
"Even fewer uses than macros"
|
|
{ $code "{ 1 10 20 34 } sum" }
|
|
{ $code "[ 1 10 20 34 ] sum-outputs" }
|
|
{ $code "[ 2 2 [ even? ] both? ] [ + ] [ - ] smart-if" }
|
|
}
|
|
{ $slide "Fibonacci"
|
|
"Not tail recursive"
|
|
"Call tree is huge"
|
|
{ $code ": fib ( n -- x )
|
|
dup 1 <= [
|
|
[ 1 - fib ] [ 2 - fib ] bi +
|
|
] unless ;"
|
|
}
|
|
{ $code "36 <iota> [ fib ] map ." }
|
|
}
|
|
{ $slide "Memoized Fibonacci"
|
|
"Change one word and it's efficient"
|
|
{ $code "MEMO: fib ( n -- x )
|
|
dup 1 <= [
|
|
[ 1 - fib ] [ 2 - fib ] bi +
|
|
] unless ;"
|
|
}
|
|
{ $code "36 <iota> [ fib ] map ." }
|
|
}
|
|
{ $slide "Destructors"
|
|
"Deterministic resource disposal"
|
|
"Any step can fail and we don't want to leak resources"
|
|
"We want to conditionally clean up sometimes -- if everything succeeds, we might wish to retain the buffer"
|
|
}
|
|
|
|
{ $slide "Example in C"
|
|
{ $code
|
|
"void do_stuff()
|
|
{
|
|
void *obj1, *obj2;
|
|
if(!(*obj1 = malloc(256))) goto end;
|
|
if(!(*obj2 = malloc(256))) goto cleanup1;
|
|
... work goes here...
|
|
cleanup2: free(*obj2);
|
|
cleanup1: free(*obj1);
|
|
end: return;
|
|
}"
|
|
}
|
|
}
|
|
{ $slide "Example: allocating and disposing two buffers"
|
|
{ $code ": do-stuff ( -- )
|
|
[
|
|
256 malloc &free
|
|
256 malloc &free
|
|
... work goes here ...
|
|
] with-destructors ;"
|
|
}
|
|
}
|
|
{ $slide "Example: allocating two buffers for later"
|
|
{ $code ": do-stuff ( -- )
|
|
[
|
|
256 malloc |free
|
|
256 malloc |free
|
|
... work goes here ...
|
|
] with-destructors ;"
|
|
}
|
|
}
|
|
{ $slide "Example: disposing of an output port"
|
|
{ $code "M: output-port dispose*
|
|
[
|
|
{
|
|
[ handle>> &dispose drop ]
|
|
[ buffer>> &dispose drop ]
|
|
[ port-flush ]
|
|
[ handle>> shutdown ]
|
|
} cleave
|
|
] with-destructors ;"
|
|
}
|
|
}
|
|
{ $slide "Rapid application development"
|
|
"We lost the dice to Settlers of Catan: Cities and Knights"
|
|
"Two regular dice, one special die"
|
|
{ $vocab-link "dice" }
|
|
}
|
|
{ $slide "The essence of Factor"
|
|
"Nicely named words abstract away the stack, leaving readable code"
|
|
{ $code ": surround ( seq left right -- seq' )
|
|
swapd 3append ;"
|
|
}
|
|
{ $code ": glue ( left right middle -- seq' )
|
|
swap 3append ;"
|
|
}
|
|
{ $code HEREDOC: xyz
|
|
"a" "b" "c" 3append
|
|
"a" """""""" surround
|
|
"a" "b" ", " glue
|
|
xyz
|
|
}
|
|
}
|
|
{ $slide "C FFI demo"
|
|
"Easy to call C functions from Factor"
|
|
"Handles C structures, C types, callbacks"
|
|
"Used extensively in the Windows and Unix backends"
|
|
{ $code
|
|
"FUNCTION: double pow ( double x, double y ) ;
|
|
2 5.0 pow ."
|
|
}
|
|
}
|
|
{ $slide "Windows win32 example"
|
|
{ $code
|
|
"M: windows gmt-offset
|
|
( -- hours minutes seconds )
|
|
\"TIME_ZONE_INFORMATION\" <c-object>
|
|
dup GetTimeZoneInformation {
|
|
{ TIME_ZONE_ID_INVALID [
|
|
win32-error
|
|
] }
|
|
{ TIME_ZONE_ID_STANDARD [
|
|
TIME_ZONE_INFORMATION-Bias
|
|
] }
|
|
} case neg 60 /mod 0 ;"
|
|
}
|
|
}
|
|
{ $slide "Struct and function"
|
|
{ $code "C-STRUCT: TIME_ZONE_INFORMATION
|
|
{ \"LONG\" \"Bias\" }
|
|
{ { \"WCHAR\" 32 } \"StandardName\" }
|
|
{ \"SYSTEMTIME\" \"StandardDate\" }
|
|
{ \"LONG\" \"StandardBias\" }
|
|
{ { \"WCHAR\" 32 } \"DaylightName\" }
|
|
{ \"SYSTEMTIME\" \"DaylightDate\" }
|
|
{ \"LONG\" \"DaylightBias\" } ;"
|
|
}
|
|
{ $code "FUNCTION: DWORD GetTimeZoneInformation (
|
|
LPTIME_ZONE_INFORMATION
|
|
lpTimeZoneInformation
|
|
) ;"
|
|
}
|
|
|
|
}
|
|
{ $slide "Cocoa FFI"
|
|
{ $code "IMPORT: NSAlert [
|
|
NSAlert -> new
|
|
[ -> retain ] [
|
|
\"Raptor\" <CFString> &CFRelease
|
|
-> setMessageText:
|
|
] [
|
|
\"Look out!\" <CFString> &CFRelease
|
|
-> setInformativeText:
|
|
] tri -> runModal drop
|
|
] with-destructors"
|
|
}
|
|
}
|
|
{ $slide "Deployment demo"
|
|
"Vocabularies can be deployed"
|
|
"Standalone .app on Mac"
|
|
"An executable and dll on Windows"
|
|
{ $vocab-link "webkit-demo" }
|
|
}
|
|
{ $slide "Interesting programs"
|
|
{ $vocab-link "terrain" }
|
|
{ $vocab-link "gpu.demos.raytrace" }
|
|
{ $vocab-link "gpu.demos.bunny" }
|
|
}
|
|
{ $slide "Factor's source tree"
|
|
"Lines of code in core/: 9,500"
|
|
"Lines of code in basis/: 120,000"
|
|
"Lines of code in extra/: 51,000"
|
|
"Lines of tests: 44,000"
|
|
"Lines of documentation: 44,500"
|
|
}
|
|
{ $slide "VM trivia"
|
|
"Lines of C++ code: 12860"
|
|
"Generational garbage collection"
|
|
"Non-optimizing compiler"
|
|
"Loads an image file and runs it"
|
|
}
|
|
{ $slide "Why should I use Factor?"
|
|
"More abstractions over time"
|
|
"We fix reported bugs quickly"
|
|
"Stackable, fluent language"
|
|
"Supports extreme programming"
|
|
"Beer-friendly programming"
|
|
}
|
|
{ $slide "Questions?"
|
|
}
|
|
}
|
|
|
|
: tc-lisp-talk ( -- )
|
|
tc-lisp-slides "TC Lisp talk" slides-window ;
|
|
|
|
MAIN: tc-lisp-talk
|