newLisp without collateral effects

Started by ale870, December 16, 2010, 01:46:06 AM

Previous topic - Next topic

ale870

Hello,



after some time I restarted to use newLisp! I love this language.



Now I wanted to try to use it implementing code to avoid the "famous" collateral effects. Since newLisp supports dynamic scope, it introduces them, but I can use some techniques to avoid it.



For example:


> (define (myFunc b)(println "SUM:" (+ a b)))
(lambda (b) (println "SUM:" (+ a b)))

> (setq a 10)
10

> (myFunc 5)
SUM:15
15
>


As you can see I introduced an external variable, so I "introduced" a collateral effect. I can simply avoid this by making the program in this way:


> (define (myFunc a b)(println "SUM:" (+ a b)))
(lambda (a b) (println "SUM:" (+ a b)))

> (myFunc 1 5)
SUM:6
6
>


Well, now see the following code:


> (setq counter 0)
(map
  (fn(x)
    (inc counter)
    (println "Value " counter " --> " x) )

  '("A" "B" "C") )

Value 1 --> A
Value 2 --> B
Value 3 --> C
("" "" "")
>


The problem is the variable "counter" introduces a collateral effect, but I cannot find a way to avoid it. Can you help please?

I'm very interested to solve this class of problems!



Thank you!
--

ale870

#1
I found this solution using CONTEXT, but I wanted to try something without using them and using "pure" Lisp code:


(define (CALC:myFunc x)
    (inc CALC:counter)
    (println "Value " CALC:counter " --> " x) )

(map CALC:myFunc '("A" "B" "C"))


I "moved" the counter inside a context, so I can say that I minimize the collateral effect since the functions and values inside the context are "controlled", but it is more "object-oriented" approach, and does not eliminate the collateral-effect.



So... ???
--

ale870

#2
Another question: can I create a lambda function inside a context, in order to use context-insulation?
--

tomtoo

#3
"silent" suppresses console output, or you could run your code from a script.

ale870

#4
Sorry but I don't understand your answer on my question.

Do you know what are "collateral effects" in a programming language? My problem is not text in output, but the capability for a programming language to run functions that take values from outside the function self (based on the variables scoping).
--

tomtoo

#5
Ok, sorry, disregard.  I thought I'd take a stab at it.

m i c h a e l

#6
Hi Alessandro!



About your solution using a context, you say:


Quote from: "Alessandro". . . does not eliminate the collateral-effect.


Why not? Perhaps I'm not sure what you mean by collateral-effect. You started by saying:


Quote from: "Alessandro"I found this solution using CONTEXT, but I wanted to try something without using them and using "pure" Lisp code


So it sounds like you are referring to pure functional programming (without side effects), but later you say:


Quote from: "Alessandro"My problem is not text in output, but the capability for a programming language to run functions that take values from outside the function self (based on the variables scoping).


So I'm not sure. Do you mean how to pass a variable by reference rather than by value?



m i c h a e l

ale870

#7
Sorry I made a mistake in the naming: I was talking about "side-effects" not "collateral-effects" :-)



In fact I wanted to use MAP function without using an external variable, which introduce the side-effects.



Even contexts, do not eliminate side-effects, just like object-oriented programming (even OO has side-effects, since some variables are inside a class but outside the methods contained in the class self).

Contexts are "similar" to OO class: they minimize side effects but not eliminate them.



I wish to find a method to eliminate side-effects using MAP.



Thank you for your help!
--

johu

#8
If my understanding to this discussion might be wrong, I am sorry.

It means the following ?
> (setq counter 0)
0
> (map (fn(x) (inc counter) (println "Value " counter " --> " x)) '("A" "B" "C"))
Value 1 --> A
Value 2 --> B
Value 3 --> C
("A" "B" "C")
> counter
3
> (setq counter 0)
0
> (let (counter 0) (map (fn(x) (inc counter) (println "Value " counter " --> " x)) '("A" "B" "C")))
Value 1 --> A
Value 2 --> B
Value 3 --> C
("A" "B" "C")
> counter
0
> (define (myFunc x) (inc counter) (println "Value " counter " --> " x))
(lambda (x) (inc counter) (println "Value " counter " --> " x))
> (setq counter 0)
0
> (map myFunc '("A" "B" "C"))
Value 1 --> A
Value 2 --> B
Value 3 --> C
("A" "B" "C")
> counter
3
> (setq counter 0)
0
> (let (counter 0) (map myFunc '("A" "B" "C")))
Value 1 --> A
Value 2 --> B
Value 3 --> C
("A" "B" "C")
> counter
0
>

ale870

#9
As per my knowledge, every example you made "suffers" the side-effects.

In order to eliminate a side-effect, one must only call a function passing all values as parameters, and get the function results. No global variable should be used.



For example, this is the wrong approach ("counter" introduces the side-effects):

(setq counter 0)

(define (myFunc)
(dotimes (i 3)
  (inc counter)
  (println "VALUE [" i "]: " counter) ) )

(myFunc)


This is the correct approach (no side-effect):


(define (myFunc)
  (setq counter 0)
(dotimes (i 3)
  (inc counter)
  (println "VALUE [" i "]: " counter) ) )

(myFunc)


As you can see, in order to eliminate the side-effects, one must eliminate every reference, inside functions, to external variables (global variables).

This approach is very useful in order to avoid that, another function (XYZ) may change the value of the global variable, by introducing unexpected results (and a big BUG!) inside (myFunc).

In fact, if one function uses global variables (or variables external to the function self), this case may happen:


(setq counter 0)

(define (anotherFunc)
  (dec counter 5) )

(define (myFunc)
(dotimes (i 3)
  (inc counter)
(anotherFunc)
  (println "VALUE [" i "]: " counter) ) )

(myFunc)


As you can see, (myFunc) makes a job, but there is the function (anotherFunc) that modify the value of counter, so I introduced a bug (difficult to discover in a complex application), and I get unexpected results.



Another correct approach for this problem could be the following code:
(define (myFunc myI myCounter)
(inc myCounter)
(println "VALUE [" myI "]: " myCounter)
myCounter)

(setq counter 0)

(dotimes (i 3)
(setq counter (myFunc i counter)) )

(myFunc)


As you can see, the function (myFunc) never works on "counter" directly, but over a controlled copy.



The concept is: a function must get some values, can create local (controlled) variables, then it will return the results. A function should never work on variables external to its scoping.
--

johu

#10
I see.

My understanding to this discussion is wrong.

I'm sorry.



By the way,
Quote
In fact I wanted to use MAP function without using an external variable, which introduce the side-effects.


In newLISP, the internal system variable $idx can be used within map.

If myFunc will be used only within map,



> (define (myFunc x) (println "Value " $idx " --> " x))
(lambda (x) (println "Value " $idx " --> " x))
> (map myFunc '("A" "B" "C"))
Value 0 --> A
Value 1 --> B
Value 2 --> C
("A" "B" "C")
>  


Thank you for the explanation.

ale870

#11
That variable is an internal loop counter, and its usage is really limited. In our example it works, but I don't think that is a good generic solution.

That is a tricky workaround however!
--

ale870

#12
I found a workaround to limit the "damages" that a global function can do.

See the following code:


(setq counter 0)

(define (bad-boy)
(setq counter 2000) )

(map
(fn (x)
(let ( (myCounter counter) )
(inc myCounter)
(bad-boy)
(println "Value: " myCounter " --> " (upper-case x))
(setq counter myCounter) ) )
'("a" "b" "c") )


I use a trick: I use a global variable "counter" to maintain the counter, but when the lambda function starts, I save counter value in a local variable, so in the function I work with the local value. It means that, even if the function (bad-boy) change the value of the global counter, my function still works correctly.

It is not a good procedure, since if (bad-boy) changes the value means there is a reason to do that, but in this way the side-effects are under control.



I'm still looking for the definitive solution.

We need a way to create a variable inside the lambda function, but that value must exist across different function calls.



Another possible solution is using a mix of contexts (dynamically generated inside the function self) and the variable $idx ;-)


(map
(fn (x)
(if (= $idx 0) (setq MY_CONTEXT:myCounter 1) (inc MY_CONTEXT:myCounter) )
(println $idx " - " "Value: "  MY_CONTEXT:myCounter " --> " (upper-case x))  )
'("a" "b" "c") )


Practically speaking, I use $idx=0 to inizialize a variable inside a context to manage the counter. Even if that variable is potentially accessible from externally of the function, we can say that symbol is "private" to the function self (since that context is not referenced anywhere outside the function self).

Then after the context variable was initialized, I use it as usual.



Even this approach is not fully side-effect free, but I really cannot find other ways to avoid side-effects to solve this algorithm using the (map) function.

the only definitive solution seems eliminating (map) and using a more "classic" LOOP (e.g. a (dotimes)).



This problem is interesting since MAP is a key function in LISP and it seems it goes against the logical approach of the language self: that function does not allow to allow us to make an algorithm side-effect free!
--

ale870

#13
Ok, I have another solution. Even this one is not so clean, but I like it:


(define (bad-boy)
(inc counter 100) )

(let ( (counter 0) )
(map
(fn (x)
(inc counter)
(bad-boy)
(println $idx " - " "Value: " counter " --> " (upper-case x))  )
'("a" "b" "c") ) )


I used a (let) function to create a local variable "counter". As you can see an external function, caused by dynamic binding, can change a "locally defined variable, but there is one point: who can make an external function (bad-boy) which works on a local defined variable?!
--

cormullion

#14
not sure what the issue is here, but you could do this:


(map
      (fn (x)
         (inc (copy counter))  ; -<<<<<<
         (bad-boy)
         (println $idx " - " "Value: " counter " --> " (upper-case x))  )
      '("a" "b" "c") )