Arc Forumnew | comments | leaders | submitlogin
Noob question on using lexical scoping/let/not using set
1 point by thaddeus 3939 days ago | 10 comments
Ok so I am pretty fresh to programming, as you will see from my question.

I know lisp uses lexical scoping and understand sequential statement thinking will not work. As a note: I have read a little bit about macros, but have not used them...haven't gotten to tail-recursive functions yet nor closures.... so far I'm just playing around with basic logic and syntax....

After playing for a bit I noticed my "set x" functions were clobbering each other...so I started converting all my function to use "let" statements. Most are working, but I have one function that I am struggling to make work. I understand why it doesn't work: because my lets don't know anything about each other under the lexical scope regime. I've re-written the function about 10 times re-working the logic, but so far I haven't got it working.

I'm guessing I need to use an anaphoric operation like "aif", but maybe not....

Simplified (thus don't ask why):

(def ugh (string) (set x 5.0) (for i 0 (- (len string) 1) (set v (/ 12 (+ i 1))) (if (is "A" (cut string i (+ i 1))) (set x (trunc (+ x v))) (set x (trunc (- x v)))i))x)

(ugh "ABABABABAB")

result = 10

how do I change this so that I am not using "sets" ?

Thanks in advance.



3 points by drcode 3939 days ago | link

> sequential statement thinking will not work

Actually, the best arc code IMO uses a fifty-fifty mix of functional and sequential thinking....

> my "set x" functions were clobbering each other

As a beginner you probably want to use '= instead- That's the more general command. Most arc lispers will only use 'set in rare cases.

> (def ugh (string) ...

Avoid naming the variable 'string: It will probably be OK, but since arc has functions and variables in the same namespace (google "lisp-1") you're clobbering the 'string function.

Here's how I'd rewrite this code (as far as I understand it, which isn't very far, I'm afraid so this is a very very rough approximation :-)

  (def ugh (str) 
   (let x 5
    (forlen i str 
       (let v (trunc:/ 12 (+ i 1))
         (if (is #\A str.i)
	     (++ x v)
	     (-- x v)))
    x))
The short of it is, you wouldn't necessarily get rid of the set (the '++ and '-- are just variants of 'set)

If you really, really wanted to get rid of the "sets" you could write something as the following, which is in pure functional style (you might not want to do this, though... there's better languages for experimenting with a pure FP style, like Haskell)

(Again, very rough, untested code)

  (def ugh (str) 
   (apply +
    5
    (map (fn (c i)
             (let x (trunc:/ 12 n)
                 (if (is #\A c)
                     x
                     (- 0 x))))
         str
         (range 1 len.str))))
If writing in this "pure" style really interest you, I'd suggest switching from arc to Haskell first. After 6 months of Haskell, writing code without "sets" will become second nature. (At that point, you'll also realize what a pain in the ass Haskell programming is and will return to arc :-)

Oh, and the posting trick is to surround your code by empty lines and have it indented two spaces.

-----

1 point by skenney26 3939 days ago | link

Another twist is to use the 'on interator which binds its first argument to each successive element and implicitly binds 'index to the index of that element.

  arc> (on c "arc" (prn index ":" c))
  0:a
  1:r
  2:c
  nil

  (def ugh (str)
    (let x 5
      (on c str
        (let v (trunc (/ 12 (+ index 1)))
          (if (is c #\A)
              (++ x v)
              (-- x v))))
      x))

-----

1 point by drcode 3939 days ago | link

ah... I should be using 'on more often....

-----

1 point by thaddeus 3938 days ago | link

I think my initial question didn't capture the intent.... and my attempt to simplify the function did exactly the opposite... so i will try again, this time not being lazy, and defining more :)

; Time is initialized at 30 min (x). ; Check each element in a string containing either "A"s or "B"s; ; if "A" add "v" minute(s) to x, if "B" subtract "v" minute(s) from x (let's call this value "xi"); ; Upon each check for A or B re-initialize x to be the current xi value multiplied by 2 (This way the next check ; can use the new x for it's increase/decrease)

; Use these 4 calls to validate function works correctly: ; (ugh "AAAA" 1) = 255 ; (ugh "BBBB" 1) = 225 ; (ugh "ABAB" 2) = 250

My original-like solution using "Sets" which work: * I changed to get rid of trunc, and make easier to read and align to the specs above.

    (def ugh (str v)
      (set x 15)
        (for i 0 (- (len str) 1) (set xi (* x 2))
          (if (is "A" (cut str i (+ i 1)))  
              (set x (+ xi v))
              (set x (- xi v))i))x)
Note: Although I set x to 15, notice the "set xi" was placed to bump that to 30 on the first pass.

As pointed out I tried unsuccessfully to remove the "sets". drcode and skenney26 both suggested ideas which do not work (not to their fault rather it was my laziness in asking the question properly). I re-worked the suggested solution to present the spirit of the intended function using the new specs above.

      (def ugh (str v)
        (let x 15
          (on c str
            (let xi (* x 2)
              (if (is c #\A)
                  (++ x (+ xi v))
                  (-- x (- xi v)))))
          x))
As stated this does not work. Sorry drcode... I didn't try the pure functional version - it looked more complicated than I wanted to go.

My solution after looking hacking with suggestions for ideas:

    (def ugh (str v)
       (let x 15
          (on c str 
             (++ x (sub-ugh x v str index)))x))

    (def sub-ugh (x v str i)
        (if (is "A" (cut str i (+ i 1)))
          (++ x v)
          (-- x v)))
Now, call me silly but does't this seem like overly complicated code compared to a relatively simple problem ? not just complicted from an amount of work perspective, but from a code readability perspective too ?

Why wouldn't the ARChitects of LISP or ARC make a set like function that is global within a function ? Doesn't this seem like a smart thing to do ? Is this against the spirit of lisp/arc/scheme ?

Thanks for stickin with me on this overly verbose newbie problem. T.

-----

1 point by thaddeus 3938 days ago | link

Sorry forgot my 4rth test that validates the code works:

arc> x Answer = Error: "reference to undefined identifier: __x"

-----

1 point by cchooper 3938 days ago | link

By the sound of things, the problem is that the variables created by one function are being modified by other functions. In that case, the solution is to use local variables instead of global variables. To do that, you just need to wrap the function body in a single let form:

  (def ugh (str v)
    (let x 15
      (for i 0 (- (len str) 1) (set xi (* x 2))
        (if (is "A" (cut str i (+ i 1)))  
            (set x (+ xi v))
            (set x (- xi v)) i)) x))
The sets will refer to the locally created variable, not a global variable.

  (= x 100000)
  => 100000
  (ugh "AAAA" 1)
  => 255
  x
  => 100000
The global x has not been modified, because the function created a local x variable and used that instead.

Here's a more concise version of that function:

  (def ugh (str v)
    (let x 15
      (on c str 
          (= x (* x 2))
          (if (is #\A c) (++ x v) 
              (-- x v)))
      x))
and here's a version that uses no sets at all:

  (def ugh (str v (o x 15))
    (let x2 (* 2 x)
      (if (is "" str) x
          (is #\A str.0) (ugh (cut str 1) v (+ x2 v))
          (ugh (cut str 1) v (- x2 v)))))

-----

1 point by thaddeus 3938 days ago | link

ah! I see. my rudimentary tests for (= x(...)) and probably having x set from previous iterations of code led me to believe (= x(...)) always changed the global x.

Thanks to all! T.

-----

2 points by thaddeus 3939 days ago | link

Thanks! I appreciate it.... I'm stepping through the examples now and I can see there are a few nice tricks for me to get used to using with the code examples both of you have provided. T.

-----

1 point by thaddeus 3939 days ago | link

Sorry about the poor formatting, i know there was a post with a trick to make indentation take, but can't find the post anymore....

-----

1 point by rntz 3939 days ago | link

Just put four spaces before every line of code and it'll be formatted as code rather than text.

-----