Changeset 8700 in project for wiki/chicken-for-ruby-programmers

02/23/08 18:23:02 (12 years ago)

Add an initial tutorial for Ruby programmers

1 edited


  • wiki/chicken-for-ruby-programmers

    r8584 r8700  
    1 == Overview
    2 === Scheme and Ruby are similar
    3 * Ruby shares history with Scheme
    4 ** Names: map, Proc(cedures), nil
    5 ** bang! methods/procedures
    6 ** Lexical scope => closures a.k.a. blocks
    7 ** continuations
    8 ** runtime evaluation
    9 * garbage collected
    10 * interpreted
    11 * Purity of design
    12 === Scheme and Ruby are different
    13 * Syntax
    14 * Can be compiled as well
    15 * Not object oriented (by default)
    17 == Scheme Basics
    18 * Scheme has very simple syntax: (procedure arg1 arg2 arg3 ...)
    19 * There are a few basic special forms to which everything else can be reduced:
    20 ** (if predicate consequent alternate) - basic conditional
    21 ** (lambda var-list expression...) - basic function
    22 ** (set! var value) - assignment
    23 ** (quote anything) - symbol or list constants
    24 ** (define var value) - define a variable
    25 ** (define (name var-list) expression...) - define a variable whose value is a function
    26 ** (define-macro var expression ...) - define a metaprogram
    28 (let ...)
    29 (letrec ...)
    31 Note: define is technically not primitive in Chicken, because you can set! any variable in Chicken whether it's been defined or not.  However, it is best practice to define all variables, and some Schemes enforce this rule.
    32 == Equivalents from Ruby
    33 * Array => List/Vector
    35 Also multi-dimensional via SRFI-25, SRFI-47, & SRFI-63 (47 & 63 covered by the 'array-lib" egg).
    37 * Hash => Hash Table
    38 * Regex => Regex
    39 * Blocks => Procedures
    40 * Macros (e.g. acts_as_foo) => Macros
    42 Note that Chicken has only '(define-macro ...)' built-in. The "syntax-rules" system is an egg - the "syntax-case" egg.
    44 * Objects => Numerous object systems
    46 TinyCLOS is probably the best supported by Chicken.
    47 == Installation & Libraries
    48 * Basic install guidelines
    49 * Gems => Eggs
    51 == Getting things Done
    52 * Shell scripts
    53 * Web programming
    54 * Databases
    55 * GUI Apps
    56 * Compiling
    57 * FFI
    59 == Pick Your Style
    60 * Imperative
    61 * OOP
    62 * Functional
    63 * Stack
    64 * Actor
    65 * DSLs and Interpreters
    66 ** Metaprogramming
     3== Introduction
     5If you don't know much about chicken yet, please take a moment to read
     6the introductory part of [[The User's Manual]].  You're back?  Good!
     8=== Paradigm independence
     10The most important design feature of Ruby is that it is purely
     11object-oriented; everything is an object.  Scheme is ''not'' an
     12object-oriented language.  In fact, it does not commit to any
     13particular programming paradigm -- it offers ''complete and total
     14freedom to the programmer''.  If you decide (a part of) a program is
     15best implemented in an object-oriented fashion, you can choose to use
     16one of the many object systems.  Have a quick glance at [[Eggs
     17Unlimited 3#object-oriented-programming]] to get an impression of the
     18diversity of styles of object oriented programming you can use with
     19Chicken.  By the way, the list on that page shows all the available
     20''eggs'' for Chicken.  We'll explain all about these [[Eggs|later]].
     22Besides object-oriented programming, you can also program in a
     23procedural fashion (like you would in Pascal, for example) or in a
     24functional style (a bit like Haskell or ML) and you can even
     25experiment with message-passing like in Erlang or logic programming
     26like you would with Prolog.
     28=== Origins
     30Ruby's origins are firmly rooted in Lisp.  It takes many things and
     31ideas from Lisp (symbols, lambdas, eval, metaprogramming, DSLs etc).
     32What it doesn't take from Lisp it takes from Smalltalk, which was
     33itself inspired by Lisp's clean syntax.  All this means that once
     34you're over the initial hump of grokking the syntax, you'll find
     35yourself in pretty familiar territory.
     37Originally, Ruby started out as an implementation-defined language.
     38That is, there was only one implementation (Matz's) and whatever that
     39implementation did '''was''' the language spec.  Nowadays, new
     40implementations of Ruby are being developed, but as of this writing
     41there is still no official specification on Ruby's syntax and
     42semantics, by my knowledge.
     44Scheme, on the other hand, is a specification-defined language.  There
     45is one official language specification which says what Scheme is and
     46how it works.  Chicken is simply an implementation of that
     47specification.  There is one thing that is important to know right
     48now: The Scheme specification is ''extremely'' minimal in design.  It
     49tries to define as few language constructs as possible, but these few
     50should be so powerful that you will not need any more to make
     51programs.  This results in an extremely small spec and a very elegant
     52and clean language with very few rules to remember, which makes it
     53easy to learn.
     55However, in the real world you will need much more than just
     56programming constructs, you need things to interact with the operating
     57system, networking libraries etcetera.  That's where the difference
     58between Scheme and other languages comes in: Every implementation of
     59Scheme defines the things it thinks are necessary to build real
     60programs with.  Unfortunately, this means most Scheme programs are
     61inherently unportable, but it also means that Scheme implementations
     62are free to experiment how they want and explore new language
     63territory.  This gives each Scheme implementation its uniqueness.
     65Chicken's power is in how it extends the Scheme standard.  It has a
     66very comfortable interface to C that does not require you to touch a
     67single line of C code in order to create bindings to existing C
     68libraries, but it also gives you the freedom to embed Chicken in C
     69code or C in Chicken code as you want.  It offers a TCP/IP networking
     70layer, it has great POSIX interoperability so you can interact with
     71the OS. And most importantly: It can compile Scheme code to very
     72efficient C code which can itself be compiled to machine code, giving
     73you the best of both worlds: a dynamic language which allows you to
     74program in the flexible way you are used to with Ruby, but this can be
     75compiled for maximum efficiency.
     77== Syntax
     79=== The basics
     81The one thing that is most strikingly different between Ruby and
     82Scheme is of course the syntax.  Ruby has an extremely baroque syntax
     83that allows you many freedoms in how you would like to write down
     84things. Scheme, on the other hand, has only one way in which to write
     85a given expression.  Let's start by looking at an example.  First we
     86start by firing up an {{irb}} session and typing a little program:
     88  irb(main):001:0> # My first program
     89  irb(main):002:0* [1, 2, 3, 4].map{|x| x + 1}
     90  => [2, 3, 4, 5]
     91  irb(main):003:0>
     93Now, we fire up a {{csi}} (chicken scheme interpreter) session:
     95  #;1> ; My first program
     96  (map add1 (list 1 2 3 4))
     97  (2 3 4 5)
     98  #;2>
     100In Scheme, lists are delimited with parentheses with its elements
     101separated by spaces. As we can see, everything in Scheme is a list,
     102even the expressions that you use to tell it what to do!  An
     103expression in Scheme is always a list with the operator on its first
     104position and the operands following it.  Procedures that accept no
     105arguments are simply a list with only the procedure in it, for example
     106{{(newline)}}, which simply displays a newline character.
     108This simple rule also means that ''every parenthesis has a meaning''.
     109You can't add more parentheses or leave off parentheses like you can
     110in most Ruby expressions.  Adding extra parentheses simply applies
     111the resulting expression as if it were a procedure:
     113  #;2> ((newline))
     115  Error: call of non-procedure: #<unspecified>
     117          Call history:
     119          <syntax>                ((newline))
     120          <syntax>                (newline)
     121          <eval>          ((newline))
     122          <eval>          (newline)       <--
     124If {{(newline)}} returned a procedure, it would be called.  But as it
     125happens, {{newline}} simply returns an unspecified value which is not
     126a procedure and thus can't be applied.  We can also use the result of
     127calling a procedure in the call to another procedure:
     129  #;3> (add1 2)
     130  3
     131  #;4> (- 10 (add1 2) 1)
     132  6
     134We see that arithmetic is a little different from Ruby, as a result of
     135the simple rules Scheme has for its syntax.  This may be a little
     136awkward, especially with complex calculations:
     138  #;5> (* (+ 1 (- 6 2) 2) (- 10 5))
     139  35
     141In Ruby (and other languages with algebraic syntax) this would have
     144  irb(main):002:0> (1 + (6 - 2) + 2) * (10 - 5)
     145  => 35
     146  irb(main):003:0> # Alternatively:
     147  irb(main):004:0* (1 + 6 - 2 + 2) * (10 - 5)
     148  irb(main):005:0> # or even:
     149  irb(main):006:0* (((1) + ((((6 - 2)))) + 2) * (((10) - ((5)))))
     150  => 35
     152Both types of syntax have their advantages and disadvantages: The
     153Ruby-like syntax is more natural, but you have to think about operator
     154precedence rules.  Chicken does not need operator precedence rules
     155because the precedence can be determined from the way it's nested, but
     156it's less natural for most people (though you get used to it very
     159Actually, right now you know almost all there is to know
     160about Scheme's syntax!  Chicken has a couple of extensions to the
     161basic Scheme syntax, but we won't go into detail here.  Later you'll
     162see a couple of handy shorcuts, but this is basically it.
     164=== Variables
     166Variables are names for things.  Chicken has vary lax rules for
     167naming variables.  Actually, ''any string'' is a valid identifier
     168as long as you quote it correctly.
     172  #;1> x = 10
     173  => 10
     174  #;2> x
     175  => 10
     176  #;3> x-y-z = 10
     177  NameError: undefined local variable or method `x' for main:Object
     178        from (irb):2
     182  #;1> (define x 10)
     183  #;2> x
     184  10
     185  #;3> (define x-y-z 10)
     186  #;4> x-y-z
     187  10
     188  #;5> (define %x% 1)
     189  #;6> %x%
     190  1
     191  #;7> (define |a b c| 5)
     192  #;8> |a b c|
     193  5
     195As you can see, because of Scheme's operator rules, symbols that would
     196normally be separate tokens designating operators have no special
     197meaning so we can use it in the middle of a name.  The convention in
     198Scheme is to use the minus sign as word separator (in Ruby, you would
     199use an underscore for separating words).  The final example shows how
     200any string is allowed as a variable name: if the string contains
     201syntax that would mean something else to Scheme you can enclose the
     202variable name in pipe symbols.  The pipe symbol itself can be escaped
     203with a backslash, if you need it to be part of a variable.
     205=== Procedures
     207Of course using simple expressions like this is not enough.  You'll
     208need procedures too.  In Ruby, named procedures are actually methods
     209on objects, but we can forget about that little detail for now:
     213<enscript highlight=ruby>
     214  def pythagoras(a, b)
     215    Math.sqrt(a**2 + b**2)
     216  end
     221<enscript highlight=scheme>
     222  (define pythagoras
     223    (lambda (a b)
     224      (sqrt (* a a) (* b b))))
     227Now that's interesting!  Procedures are just regular variables in
     228Scheme (a bit like functions in Javascript).  We assign a lambda to it.
     229We can do that in Ruby too, but it's not pretty:
     232  some_class.send(:define_method, :pythagoras) {|a, b| Math.sqrt(a**2 + b**2) }
     234Just like in Ruby the {{def foo}} is shorter than the above, in Scheme
     235we have a convenient shorthand for defining procedures too:
     237<enscript highlight=scheme>
     238  (define (pythagoras a b)
     239    (sqrt (* a a) (* b b)))
     242To assign to a pre-existing variable we can also use {{set!}}:
     244  #;1> (define x 10)
     245  #;2> x
     246  10
     247  #;3> (set! x 20)
     248  #;4> x
     249  20
     251Top-level variables can also be overwritten by simply redefining them,
     252but in some cases you need {{set!}}.  However, set! is a typical
     253artifact of an imperative programming style and in clean code you
     254want to avoid using it.
     256== Data types
     258Now we have a basic grasp of Scheme's syntax, we can have a look at the
     259different data types Chicken has to offer.  We will do this from a Ruby
     262=== Strings
     264Strings are simple.  Just like in Ruby, we have strings enclosed by
     265double quotes: {{"foo"}} works the same in Ruby as it does in Chicken.
     266Chicken's double quoted strings work more like Ruby's single-quoted
     267strings, though.  There is no string interpolation and other things;
     268a string is just a string.
     272  x = 10
     273  y = "x contains #{x}"
     274  z = "x contains " + x.to_s
     278  (define x 10)
     279  (define y (sprintf "x contains ~A" x))
     280  (define z (conc "x contains " x))
     281  ; Or:
     282  (define z (string-append "x contains " (->string x)))
     284Note that {{->string}} is simply the name of a procedure, including
     285the arrow.
     287=== Arrays
     289In Ruby we use arrays for storing lists of things.  The obvious Scheme
     290equivalent type is the list, you'd think.  This is sort of true:
     294  x = [1, 2, 3, 4]
     295{|y| x + 10 }
     296  x.each{|y| puts y }
     300  (define x '(1 2 3))
     301  (map x (lambda (x) (+ x 10)))
     302  (for-each (lambda (x) (display x) (newline)) x)
     304Note that Scheme does not have the block scoping bug.  Another thing
     305that we should note is the first line.  We create a list by
     306''quoting'' it.  This allows us to enter the list in such a way that
     307Chicken knows the list is just that; a list, and not a procedure
     308application of the procedure called {{1}} on the arguments {{2}} and
     309{{3}}.  The apostrophe takes care of that.
     311However, we must always remember that the Scheme list is more like a
     312linked list.  This means that it is very flexible in how we can add
     313things to it and alter it, but it also means that traversing it takes
     314more time as more items are added to it.  Accessing an element is an
     315O(n) operation, where n is the position of the element.
     317If we want O(1) operations on our lists, we can use a ''vector'':
     319  #;1> (define x (vector 1 2 3 4))
     320  #;2> (vector-ref x 2)
     321  3
     322  #;3> (define y (list 1 2 3 4))
     323  #;4> (list-ref y 2)
     324  3
     326Adding new elements to a vector requires resizing or even copying the
     327vector, just like it would in Ruby.  So whenever you're contemplating
     328using a list type, think about the properties you want the list type
     329to have.  This may sound odd, but in fact this gives you much more
     330flexibility than Ruby, where you have the choice of using an Array,
     331or... using an Array.  However, as Knuth famously said: "Premature
     332optimization is the root of all evil", and you should probably take
     333the list solution until it's proven that you ''need'' vectors.  Also,
     334because Lisp was built on lists, it is very good at manipulating them,
     335so they're most likely the most convenient datatype.
     337Chicken also offers you several other types of array-like types, each
     338with their own unique time and space properties.  Which you'll use
     339depends on the task at hand and the situations your system will be
     340used under.
     343=== Symbols
     345Luckily, you are a Ruby programmer, so we will not have to go through
     346the whole "explaining what symbols exactly are" again :)
     347Actually, Ruby borrowed symbols from Lisp.
     351  "foo".to_sym
     355  (string->symbol "foo")
     357To enter them literally, we can use this syntax:
     361  :foo
     365  'foo
     367As we can see, a symbol is only a quoted variable name!  This is the
     368origin of symbols and also the reason you can {{send}} symbols
     369representing method names to objects in Ruby.  Symbols have all the
     370same semantics as Ruby's symbols: they can be compared in constant
     371time and they take up very little memory space.
     373== Examples
     375Let's look at a few larger programs to better appreciate how one would
     376program in Chicken.
     378  TODO
     380== Meta programming
     382A very hot topic in the Ruby community is meta programming and DSLs
     383(Domain specific languages).  These ideas originated from Lisp, which
     384means you can just keep on trucking in Chicken!
     386=== Data is code and code is data
     388The most fundamental concept of Lisp is that code is data.  As we've
     389seen, procedure calls look like lists.  We've also seen that we can
     390quote lists to "block" Scheme from interpreting a list as a procedure
     391call.  We can also turn this around on its head and force a list to
     392be evaluated as code:
     394  #;1> (define calculate '(+ 1 2 3 4))
     395  #;2> calculate
     396  (+ 1 2 3 4)
     397  #;3> (eval calculate)
     398  10
     399  #;4>
     401"Big deal", you might say, "Ruby also has eval".  But the difference
     402is what matters: In Ruby you have to construct strings to be evaluated,
     403which means you need to be able to parse strings if you want to change
     404a Ruby-program-stored-as-string.  In Scheme we can simply hack the
     405list.  ''The program is stored in a parsed state'', so to speak.
     407If we would want to change the operator from + to *, we can simply
     408do that:
     410  #;1> (eval (cons '* (cdr calculate)))
     411  24
     413This is much more robust than any regexp hacking or ad-hoc parsing on
     414strings you want to evaluate.
     416=== Macros
     418One of the coolest concepts, but also the one most easily abused is
     419''macros''.  Because Scheme stores code as data, you can change the
     420code on-the-fly as described above.  You can do that at run-time on
     421random data through eval, but you can also do it at compile-time on
     422your program, which is usually the best time to do it.
     424  TODO
     426== Chicken and the Real World
     428Programming is about more than having a pretty language, so let's look
     429at what Chicken has to offer for real construction work.
     431=== Eggs
     433Eggs are to chicken what ''gems'' are to Ruby: installable extensions
     434like libraries and programs.  The list of [[eggs]] is where you should
     435look first when you are going to implement something big. You can
     436install an egg almost like you install a gem, as follows:
     438  $ chicken-setup runcmd
     440This downloads and installs the egg with the name "runcmd".  This
     441egg has no dependencies, but if it did it would have downloaded and
     442installed them as well.
Note: See TracChangeset for help on using the changeset viewer.