Arc Forumnew | comments | leaders | submit | tokipin's commentslogin
6 points by tokipin 6090 days ago | link | parent | on: Arc's web server in Lua

i like how Lua is a language that "just works" (within reason of course.) i miss its awesometastic tables when using other languages. it's also rare in that it doesn't promote any particular paradigm. it lets you use what you feel is best. relatedly, its syntax is deceptively simple

anywho, i took a glance at the code. i know you're going for performance so maybe that affected the mechanisms you used, but here's some suggestions anyways:

* Lua's string concatenation operator is .., as in

  return "str1" .. "str2"
* in tight code, local variables should be used anywhere possible. global variables require a table lookup, but local variables are on the stack (or register... one 'o thems.) so for example, you might want to have something like

  local tinsert = table.insert
at the top or above the functions it's used in. it can't exactly JIT the table.insert because table lookup is dynamic (in both the tables you move around and the global environment)

* string.gsub might be faster than iterated replacement

* a key in a table can be anything, even functions and other tables, for the hex2int you can therefore have something like:

  { ["1"] = 1, ["2"] = 2, ... ["a"] = 10 }
i don't remember if the square brackets are necessary there

* as far as ipairs is concerned, a nil value signifies the end of a table, so if you have:

  { 1, 2, nil, 3, 4 }
ipairs will only iterate from 1 to 2. hence, depending on circumstances, you may be able to use this to make table splitting a bit faster

* table.concat might be useful somewheres

* a map-like function where on each iteration you return not just a value, but a key-value pair might be useful. eg pretending you don't care about performance, reverse could be written:

  function reverse( a )
      local len = #a
      return tblmap( a, function( k, v )
          return len-k+1, v
      end )
  end
it also makes mirroring tables easy. (again, ignoring performance. this is something you might use in the user code rather than in the library)

* one way i've set up bi-directional tables before is with __call. in that form, tbl[key] would access one way, and tbl(key) would access it in reverse. another option is something like tbl[1][key]/tbl[-1][key]. depending on how you're using the table, you can memoize in one of the directions and/or set up some sort of little database hidden behind that single table

* Lua doesn't have macros, but it does have first-class environments. among many other things this means it has anaphoric macro-like capabilities, except for the need to pass in expressions through closures or strings. take for example these lines from luarc.lua:

  local sep     = sep or ' '
  local pattern = string.format ('[^%s]+', sep)
  local f       = string.gmatch (s, pattern)
  local match   = f()
using a function seq:

  function seq( ... )
      local that = nil
      local original_env = getfenv( select( 1, ... ) )
  
      for i = 1, select( '#', ... ) do
          local fn = select( i, ... )
  
          local new_env = { ["that"] = that }
  
          -- have new environment inherit from old, otherwise
          -- any global variables used in the fns will be unavailabe
          setmetatable( new_env, { __index = original_env } )
  
          setfenv( fn, new_env )
  
          that = fn()
      end
  
      return that
  end
or a shorter version if we have another function "with":

  function seq( ... )
      local that = nil
  
      for i = 1, select( '#', ... ) do
          local fn = select( i, ... )
  
          that = with( { ["that"] = that }, fn )()
      end
  
      return that
  end
we can write:

  local match = seq(
      function() return sep or " " end,
      function() return string.format( "[^%s]+", that ) end,
      function() return string.gmatch( s, that ) end,
      function() return that() end )
this is an extremely questionable abstraction to say the least (note, none of this was tested,) but it's an example of some of the sorts of things you can do with first-class environments, which might help in betterizing the way the library is used. keep in mind that environments are tables, whose metatables may be set like any other tables, which need not be constrained to easy childish things like inheritance. eg:

  do
      local test_env = {}
  
      local i = 0
      setmetatable( test_env, { __index = function( tbl, var )
          if var == "var" then
              i = i + 1
              return i
          else
              return _G[var]
          end
      end } )
  
      setfenv( 0, test_env )
  end


  > print( var )
  1
  > print( var )
  2
  > print( var )
  3
* finally, the horse's mouth is usually the best teacher:

http://www.lua.org/manual/5.1/manual.html

* and why yes, i am bored, thank you for asking (actually, any excuse to play with Lua or Arc is welcome)

-----

3 points by bOR_ 6090 days ago | link

Heh. Lua is pretty nice. Wrote a few mods for warcraft in it. The prefix notation is growing on me though as it appears to be clearer to my mind what is happening when things are written in prefix than infix (took a bit of getting used to though).

-----

3 points by sacado 6089 days ago | link

waow, thanks for the information (and for reviewing my code). I'm not a very proficient Lua developer yet, so I still have to learn a lot. Your advice will help...

-----

1 point by almkglor 6090 days ago | link

> i miss its awesometastic tables when using other languages.

Would you mind expounding on how "awesometastic" they are? I gather that they have a nice implementation of vectors hidden under the table data structures (so conceptually a vector is just a table, but the runtime optimizes away vector-like usage into a true vector). Does it have more awesometastic properties?

-----

4 points by tokipin 6090 days ago | link

i think it's the fact that those concerns don't occur in the language to begin with. from what i glanced (i can't find the pdf anymore,) the interpreter switches between different representations depending on how the particular table is used, and can switch between the representations dynamically for the given table (i'm not sure if the switching is bidirectional)

but the user never sees any of that. if you want an array or tuple you type

  local blah = { 1, "two", function return 3 end }
if you want a hash table you type

  local blah = { bleh = "yep", bleagh = "whatever" }

  -- both of the following are valid
  print( blah["bleh"] )
  print( blah.bleh )
sparse array:

  local blah = { "my", "name", "is", [12] = "slim", [888] = "shady" }
hybrid:

  local blah = { "element 1", "element 2", name = "son", lastname = "goku" }
nesting:

  local blah = { { "star", "bucks" }, { "apple", "sauce" } }
etc:

  local blah = { [{ 1, 3, 5 }] = "odd", [{ 2, 4 }] = "even" }
basically, any sequency, key-valuey, or structured ish thingie, i can just write out without worrying about anything. i'm sure it's doin' some sort 'o magic under the hood, but to that end they could be sacrificing lawn gnomes for all i care

and keep in mind that Lua is likely the fastest interpreted language... or at least it was before the Javascript optimization race started. i think one of the Javascript engines has features inspired by the Lua VM

and then there's metatables, which are properties that can be set to control the behavior of values in different situations. for example, one of the properties is __index, which is referred to when a table doesn't have a value for a given key. this enables straightforward memoization, inheritance, hidden data management, infinite data structures, etc

other metatable properties determine the operation of the value under addition, comparison, etc

you could, for example, implement complex numbers as if they were part of the language, simply by creating a seed table called i. when an expression such as 1 + 2 * i is reached, the __mul property would perform the complex multiplication, then return a new Complex table which would in turn perform the addition. along each step it would be a normal table, so you could do, for example:

  print( (1 + 2 * i).polar_form )
keeping in mind that the table access is dynamic, hence polar_form can be calculated each time it's accessed, no need for getting/setting. also, this:

  print( 8 * i^5 + 2 * i )
would work as it should because there's a __tostring property

i've been thinking of making an APL-like DSL (would be nice to be able to implicitly map and whatnot) in this sort of manner. i haven't looked deep into Ruby but i believe it has things of this nature

here's a description of metatables:

http://www.lua.org/manual/5.1/manual.html#2.8

the fact that tables are used everywhere is unoverstatable. environments are tables, therefore you can do powerful things with environments. like writing your own import/include function, reading and writing global state in a file, anaphora and implicits... pretty much a bunch of crazy shit

along with this some very well-chosen features such as coroutines, closures, the implementation of most language features as modules (eg file.open, coroutine.wrap) to keep the syntax clean, etc

the tables by themselves go a long way, especially with their It Just Works® all-purpose syntax. but the way this flexibility is encouraged through the rest of the language makes it a wonderful unified package

-----

1 point by tokipin 6090 days ago | link | parent | on: Improve this function 2 =)

version using accum and aif

  (def observe (row col range)
      (accum append
        (for i (- range) range
        (for j (- range) range
            (aif (showpos (+ row j) (+ col i))
                 (append it))))))
i tried making a showpos using aand like so:

  (def showpos (row col)
      (aand (world row) (it col)))
but it errors on out-of-bounds <_< i was hoping the errsafe was for using nil as a function

-----

1 point by bOR_ 6090 days ago | link

thanks for the (- somevariable)! I was using there (and in other places) still ( -1 somevariable)..

-.variable = (* -1 variable) work just fine in arc.

errsafe was a trick I learned from my previous 'improve this function' post :).

-----

2 points by tokipin 6109 days ago | link | parent | on: Things to know before learning Arc?

jump right in. in fact i think Arc makes a great first language because it has the syntactic simplicity of lisp with an eye for aesthetics

-----

2 points by prakash 6109 days ago | link

I will probably try a combination of all the above that works for me.

-----

2 points by tokipin 6113 days ago | link | parent | on: Show and Tell: elliottslaughter.net

'to-nearest might help:

http://arcfn.com/doc/math.html#to-nearest

-----

1 point by eds 6113 days ago | link

  arc> (to-nearest 5.123 .1)
  5.1000000000000005
Um...

-----

7 points by tokipin 6121 days ago | link | parent | on: Mutual recursive lambdas in a with?

"withs" is the recursive binding thingie but in this case let is nicer:

  (let (f1 f2) nil
    (def f1 () (prn "bob") (f2))
    (def f2 () (prn "bib") (f1))
  
    (f1))
using withs, i couldn't bind a variable twice in the same declaration so it ended up being ugly:

  (withs (f2 nil
          f1 (fn () (prn "bob") (f2)))

         (def f2 () (prn "bib") (f1)) ; f2 can't be bound again in the withs part, maybe a bug
  
      (f1))

-----

2 points by skenney26 6121 days ago | link

(With very minimal testing)

  (mac mrec (fns . body)
   `(let (,@(map1 car fns)) nil
      ,@(map1 (fn (f) `(def ,@f))
              fns)
      ,@body))

  arc> (mrec ((f1 () (prn "bob") (f2))
              (f2 () (prn "bib") (f1)))
         (f1))
  bob
  bib
  bob
  bib
  ...

-----

3 points by rntz 6119 days ago | link

This won't work if you want to define a function called 'o. Reason: (let var val ...) becomes ((fn (var) ...) val), and if var is (o ...), it becomes ((fn ((o ...)) ...) val). (o ...) is interpreted as an optional parameter, which disables destructuring.

Destructuring in lets is a nice idea, and using this with nil is a cool hack, but until and unless this behavior is changed, don't rely on it in macros - it can cause unexpected code breakage. A better way to do it would be the following (my 'fwithr is your 'mrec).

    (mac withr (bindings . body)
      (let bindings (pair bindings)
        `(with ,(mappend [list car._ nil] bindings)
           ,@(mapeach b bindings
               `(set ,@b))
           ,@body)))

    (mac fwithr (fns . body)
      `(withr
         ,(mappend [list car._ `(fn ,@cdr._)] fns)
         ,@body))
Edit: It seems almkglor has already done something like this, but his also has the "let doesn't always destructure" bug: http://arclanguage.org/item?id=7387

-----

1 point by ambition 6121 days ago | link

Oh, I see. I didn't do the 'f2 nil' line when trying withs so I got errors when f1 couldn't find f2. Also I didn't know you could do that with let!

Great help, thanks.

-----

2 points by drcode 6121 days ago | link

clever hack.

-----

2 points by tokipin 6121 days ago | link

just to make clear, i didn't come up with the autodestructured nil thing, despite having the good looks and intellect for it. credit goes to rkts:

http://news.ycombinator.com/item?id=265111

http://arclanguage.com/user?id=rkts

-----

1 point by tokipin 6123 days ago | link | parent | on: Improve this function?

flat should be useful here. eg:

  (def avg2 (lst)
      (avg (flat lst)))
though i'm away from the lab and can't verify it right now

-----

2 points by fallintothis 6123 days ago | link

I thought at at first, but that won't produce the same answers. Take his test case:

  '(1 2 3 (3 4 5) (3 4 (1 2 3)))
Using the desired method, it's:

  (/ (+ 1
        2
        3
        (/ (+ 3 4 5) 3)
        (/ (+ 3 4 (/ (+ 1 2 3) 3)) 3))
     5) ;= 13/5
But by flattening the list, we get:

  (/ (+ 1 2 3 3 4 5 3 4 1 2 3) 11) ;= 31/11

-----

1 point by tokipin 6123 days ago | link

ah, didn't see the 'resolved' thingie

-----

2 points by offby1 6123 days ago | link

topkin's thingy doesn't give the same result. Try it and see.

-----

3 points by tokipin 6127 days ago | link | parent | on: Help needed with macro

here's my take

  (def wrand args
      (withs (weights (sort (compare > car) (pair args))
              unit   (/ 1.0 (reduce + (map car weights))))
          
         (ccc (fn (return)
             (with (acc 0 r (rand))
  
                 (each (weight val) weights
                     (= acc (+ acc weight))
                     (if (< r (* acc unit))
                         (return val))))))))
i think the sort might be unnecessary but my mind isn't functioning atm

-----

1 point by skenney26 6127 days ago | link

Shouldn't there be a simple, elegant solution to this problem that doesn't necessitate using ccc and return? Maybe I'm resistant to using continuations because I'm still coming to grips with understanding them... but there's alot of code in arc.arc and only one call to ccc.

-----

1 point by rkts 6126 days ago | link

You can write a tail-recursive version of tokipin's code pretty easily. Personally though I think this could be an addition to the "examples of LOOP" thread from a while back.

  (defun wrandf (xs weights)
    (loop with r = (random (apply #'+ weights))
          for x in xs
          for w in weights
          for cw = w then (+ cw w)
          when (> cw r) return x))

  ; assumes pair
  (defmacro wrand (&rest args)
    `(funcall
       (wrandf
         (list ,@(mapcar (lambda (x) `(lambda () ,(cadr x))) (pair args)))
         (list ,@(mapcar #'car (pair args))))))

-----

1 point by almkglor 6127 days ago | link

The sort is indeed unnecessary, and the solution above is indeed the classic solution to the weighted random choice

-----

1 point by tokipin 6127 days ago | link | parent | on: Help needed with macro

you can multiply by 10^n to get n-digit decimal precision

-----

1 point by tokipin 6150 days ago | link | parent | on: Destructive list operations

i can't speak about any standards, but the following syntax might not be bad:

  (d!car '(1 2 3))
the setup is:

  (def d (op)
      (eval op))

-----

2 points by tokipin 6154 days ago | link | parent | on: Help w/ continuations

i'm currently writing a blog post featuring Arc's ccc, and all signs point to it being a 100% implementation. this shouldn't be too surprising since (i believe) the compiler transforms the Arc code to CPS

i don't know what the OP is asking exactly, but the answer is likely yes. i implemented coroutines, so these more straightforward things should be no problem

i talk about it a bit on the blog post, but the problem with continuations is the freaken name. it sounds like some sophisticated thing, when in fact all it is is marking the next instruction. for example:

  (ccc func)
obviously you're using ccc here because you want to go back to it later. perhaps you want to go back to it from within 'func'. in other words, so that 'func' effectively has the ability to arbitrarily return:

  (ccc [do (stuff...)
        (if blah (_ bleh)) ; early return
        (do more stuff)])
but what if control returned right back precisely to ccc? then the whole form would be executed again. see how that would be a problem? so ccc doesn't mark the "current" spot. it marks the next spot so you don't get stuck in an infinite loop

and because it's the next spot, and because you can call _ as a function, they refer to it with the holistic name "continuation," instead of "the spot after the ccc"

they could have just as easily made the primitive be mark-the-current-spot, a la:

  (ccc [= spot _])
but while making mark-the-current-spot with ccc is easy, the converse isn't, nor would mark-the-current-spot necessarily be the most common use form (the impetus for call/cc might have been to give scheme the ability to return)

-----

1 point by absz 6154 days ago | link

Actually, arc just compiles everything down to Scheme; Scheme, of course, has full continuation support, and thus so does Arc.

That's a nice explanation of continuations, but one comment: I think (if I remember what I read correctly) that writing ccc in terms of mark-the-current-spot (or perhaps get/cc, or gcc :P) is actually impossible, but I could be wrong. The other advantage of ccc is that every flow control construct can be simulated using it (including, but not limited to, C's return).

-----

2 points by tokipin 6154 days ago | link

> I think (if I remember what I read correctly) that writing ccc in terms of mark-the-current-spot (or perhaps get/cc, or gcc :P) is actually impossible, but I could be wrong.

that's the short-term conclusion i came to when i was fidding with it. i didn't exactly prove it was the case though so i was being safe

-----

1 point by almkglor 6154 days ago | link

You might also want to mention continuation guards and dynamic-wind (which is partially exposed as 'protect in Arc).

-----

More