Feed on
Posts
Comments

I’ve been playing with Factor for a couple of weeks. I’m finding that it takes me quite a bit longer to write stuff with factor than with other languages, but the process is enjoyable and I get the feeling that I’m learning something useful each time. The question is: will programming speed improve with experience. And more to the point, eventually, will I be able to write code faster in factor than in gambit scheme?

Here’s a task I’ve been trying to accomplish the past couple of evenings: converting tabular data into triples. I’ve written this functionality before in both python and scheme before so it’s a good exercise for developing factor experience.

First, here’s the code in python:


def row_to_triples(i,cols,row):
    return [ (i,col,cell) for col,cell in zip(cols,row) ]

def tabular_to_triples(startid,cols,rows):
    triples = []
    for i,row in zip(range(startid,startid+len(rows)),rows):
        triples += row_to_triples(i, cols, row)
    return triples

Which works like this:


>> print tabular_to_triples(0,
                ["col1","col2","col3"],
                [["a","b","c"],["e","f","g"]])
[(0, 'col1', 'a'), (0, 'col2', 'b'), (0, 'col3', 'c'), (1, 'col1', 'e'), (1, 'col2', 'f'), (1, 'col3', 'g')]

Here’s the equivalent in gambit scheme using srfi-42 eager comprehensions:


(include "srfi-42.scm")

(define (tabular->triples startid rows cols)
  (define (row->triples id cols row)
    (list-ec (:parallel (:list i cols) (:list j row))
             (list id i j)))

  (append-ec (:list row (index i) rows)
             (row->triples (+ i startid) cols row)))

Which yields:


>> (tabular->triples 0
                     '((a b c)(d e f))
                     '(col1 col2 col3))

((0 col1 a) (0 col2 b) (0 col3 c) (1 col1 d) (1 col2 e) (1 col3 f))

ASIDE: If you’re evaluating gambit scheme (or scheme in general) be sure to check out srfi-42 and also Alex Shinn’s ml-style pattern matching module from http://synthcode.com/scheme/ . Gambit scheme out-of-the-box is like abstraction assembly language - you need tools layered over the top for real world use.

Ok, so on to factor. My first attempt was:


: row>triples ( cols row rowid -- triples )
  [ -rot 3array ] curry 2map ;

: process-row ( rowid cols row -- rowid cols triples )
  rot 1 + swapd                           ! inc rownumber
  [ swapd row>triples ] 2keep swap rot ;

: tabular>triples ( start-rowid cols rows -- triples )
  [ process-row ] map concat 2nip ;

Which works like this:


>> { "c1" "c2" "c3" } { { "a" "b" "c" } { "d" "e" "f" } } 0 tabular>triples
>> .
{
    { 1 "c1" "a" }
    { 1 "c2" "b" }
    { 1 "c3" "c" }
    { 2 "c1" "d" }
    { 2 "c2" "e" }
    { 2 "c3" "f" }
}

This is reasonably compact, but looks horrible to me and is pretty difficult to follow due to all the stack shuffling. I’m starting to learn that the way to eliminate stack shuffling is to use combinators, and try to factor out the common code.

I did a bit of functional de-composition with the hope that creating words for the pieces would yield clearer code. The pieces of functionality needed were:

- creating a triple from 3 elements on the stack. Handled by 3array
- enumerating a variable whilst mapping through a sequence
- holding a variable whilst mapping through a sequence. Handled by ‘curry map’
- mapping two sequences in parallel (columns and rows). Handled by 2map
- concatenating sequences (rows) of triples together: concat

Actually once I’d worked out the which words I wanted I found that a most of them already existed. In fact all of them except ‘enumerating a variable whilst mapping through a sequence’, so I wrote ‘map-with-counter’ to provide this. It prepends enumerating code to the map quotation before calling map, then cleans up the index variable at the end:


: map-with-counter ( start seq quot -- newseq )
 [ [ dup 1+ swap ] dip ] swap compose map nip ;

And so few attempts later and I’ve got:


: row>triples ( rowid cols row -- triples )
  [ 3array ] curry** 2map ;

: tabular>triples ( start-rowid cols rows -- triples )
  [ row>triples ] curry* map-with-counter concat ;
 

Which I’m quite pleased with. However in addition to map-with-counter I’ve had to create a new partial application combinator: curry**, which is general but doesn’t exist in the standard library. A sure sign that I’m doing something wrong, or at least differently to other factor developers.
And while I was writing curry** I ended up creating same new stack shufflers:


: rotd ( a b c d -- b c a d )  >r rot r> ;
: -rotd ( a b c d -- c a b d ) >r -rot r> ;
: dupdd ( a b c -- a a b c ) >r dupd r> ;

! partial application of quot based on 3rd item of stack
! see curry, curry*
: curry** ( param obj obj quot -- obj obj curry )
  rotd [ -rotd call ] 2curry ; inline

The underlying theme to these extra words is dealing with the 3rd element in the stack and below. So does that mean that seasoned factor developers tend to switch to some other mechanism when there’s more than 3 stack variables in play? Sounds like a question for the mailing list…

Viewing 6 Comments

    • ^
    • v
    Just for a laugh, here it is in Haskell. You can type this at the interpreter.

    let insertList x = map (insert x) where insert x (y,z) = (x,y,z)

    let cols = ["col1","col2","col3"]

    let rows = [["a","b","c"],["e","f","g"]]

    concat $ zipWith insertList [0..] $ map (zip cols) rows

    This prints [(0,"col1","a"),(0,"col2","b"),(0,"col3","c"),(1,"col1","e"),(1,"col2","f"),(1,"col3","g")]

    No preview available so I don't know if your blog software is going to mangle that!
    • ^
    • v
    I thought from the input format and the output that a Python list comprehension would be the way I'd solve it and came up with this:

    >>> from pprint import pprint as pp
    >>> tabular = [["col1","col2","col3"],[["a","b","c"],["e","f","g"]]]
    >>> triples = [(row[0], col[1], row[1][col[0]])
    for row in enumerate(tabular[1])
    for col in enumerate(tabular[0])]
    >>> pp(triples)
    [(0, 'col1', 'a'),
    (0, 'col2', 'b'),
    (0, 'col3', 'c'),
    (1, 'col1', 'e'),
    (1, 'col2', 'f'),
    (1, 'col3', 'g')]
    >>>


    (I added a couple of newlines to the list comprehension to get it into the comment box).

    If I were using it then I might wrap it in a function and add a docstring to test/explain it.

    - Paddy.
    • ^
    • v
    Hi Phil,

    Small typo in the first sentence: "I’ve been playing with for a couple of weeks.", I believe you meant "factor" instead of "for".

    If you are interested in concatenative languages and the semantic web have you seen Ripple? http://ripple.fortytwo.net/

    There is also my own relatively immature language Cat (http://www.cat-language.com), which will be stable "real soon now".

    Cheers,
    Christopher Diggins
    • ^
    • v
    Thanks Christopher - it was a missing close brace in the factor link preventing it from being displayed.
    I'll definitely check out both Cat and ripple - thanks for the links
    • ^
    • v
    Hi,

    I had a go at implementing this. The key, as you've noticed, is to decompose the problem into steps where you're working with no more than a handful of values at once.

    Don'write a big loop, instead break it up into multiple iterations over the data and use library words where possible.

    USE: math.ranges

    : (column-names) ( cols row -- pairs )
    2array flip ;

    : column-names ( cols rows -- seqs-of-pairs )
    [ (column-names) ] curry* map ;

    : (number-rows) ( pairs n -- triples )
    [ add* ] curry map ;

    : number-rows ( seqs-of-pairs -- triples )
    tuck length [a,b] [ (number-rows) ] 2map concat ;

    : tabular>triples ( start-rowid cols rows -- triples )
    column-names number-rows ;
    • ^
    • v
    I prefer the Haskell list comprehension version:

    [(i,col,cell)|(i,row)<-zip [startid..] rows, (col,cell) <- zip cols row]

    Now I wonder how this will get formatted? Need a preview button.
close Reblog this comment
blog comments powered by Disqus

generic acomplia purchase cialis overnight delivery cheap acomplia online buy generic clomid buy cialis low price viagra without prescription where to buy cialis lowest price levitra where to buy propecia cheap cialis from canada lasix no prescription viagra without rx cheap accutane tablets viagra online without prescription viagra no rx buying cialis online zithromax viagra in uk free cialis cialis us where to buy acomplia find cialis online buy viagra lowest price accutane prescription buy cheap accutane online cialis buy buy generic cialis online acomplia order propecia online lowest price synthroid synthroid without a prescription synthroid online buy propecia online cheap levitra online where to buy levitra cialis online review synthroid prices cialis generic cialis buy drug buy viagra on line viagra pharmacy cialis for order price of levitra zithromax online where to buy synthroid soma generic generic clomid propecia online stores viagra cheap drug cheap generic soma cialis cheap zithromax online cheap order accutane online purchase zithromax online purchase viagra online buy cheap clomid cheap generic propecia zithromax pharmacy online pharmacy cialis cheapest acomplia cost of cialis no prescription viagra free viagra purchase lasix online cialis from india viagra from india order discount cialis soma online stores find no rx cialis cialis no rx required find viagra without prescription approved cialis pharmacy lasix discount