History variables: Difference between revisions
added autohotkey L |
Adds Clojure solution |
||
Line 278:
<pre>foobar <- foo <- 5
</pre>
=={{header|Clojure}}==
Clojure does not have history variables, but it can be accomplished via a watcher function that can track changes on a variable.
<lang clojure>
(def a (ref 0))
(def a-history (atom [@a])) ; define a history vector to act as a stack for changes on variable a
(add-watch a :hist (fn [key ref old new] (swap! a-history conj new)))
</lang>
{{out|Sample Output}}
<pre>user=> (dosync (ref-set a 1))
1
user=> (dosync (ref-set a 2))
2
user=> (dosync (ref-set a 3))
3
user=> @a-history
[0 1 2 3]
user=> (dotimes [n 3] (println (peek @a-history)) (swap! a-history pop))
3
2
1
nil
user=> @a-history
[0]
</pre>
|
Revision as of 03:37, 10 July 2014
![Task](http://static.miraheze.org/rosettacodewiki/thumb/b/ba/Rcode-button-task-crushed.png/64px-Rcode-button-task-crushed.png)
You are encouraged to solve this task according to the task description, using any language you may know.
Storing the history of objects in a program is a common task. Maintaining the history of an object in a program has traditionally required programmers either to write specific code for handling the historical data, or to use a library which supports history logging.
History variables are variables in a programming language which store not only their current value, but also the values they have contained in the past. Some existing languages do provide support for history variables. However these languages typically have many limits and restrictions on use of history variables.
Concept also discussed on LtU and Patents.com.
- Task
Demonstrate History variable support:
- enable history variable support (if needed)
- define a history variable
- assign three values
- non-destructively display the history
- recall the three values.
For extra points, if the language of choice does not support history variables, demonstrate how this might be implemented.
Ada
Ada does not natively support history variables -- we have to implement them.
Furthermore, Ada is a strongly typed language -- that means, we would need to write a history variable type for every basic item type. Instead, we write a single generic package "History_Variables" that works for any item type.
Generic Package "History_Variables"
Specification:
<lang Ada>private with Ada.Containers.Indefinite_Vectors; generic
type Item_Type (<>) is private;
package History_Variables is
type Variable is tagged limited private;
-- set and get current value procedure Set(V: in out Variable; Item: Item_Type); function Get(V: Variable) return Item_Type;
-- number of items in history (including the current one) function Defined(V: Variable) return Natural;
-- non-destructively search for old values function Peek(V: Variable; Generation: Natural := 1) return Item_Type; -- V.Peek(0) returns current value; V.Peek(1) the previous value, ect. -- when calling V.Peek(i), i must be in 0 .. V.Defined-1, else Constraint_Error is raised
-- destructively restore previous value procedure Undo(V: in out Variable); -- old V.Peek(0) is forgotten, old V.Peek(i) is new V.Peek(i-1), ect. -- accordingly, V.Defined decrements by 1 -- special case: if V.Defined=0 then V.Undo does not change V
private
package Vectors is new Ada.Containers.Indefinite_Vectors (Index_Type => Positive, Element_Type => Item_Type);
type Variable is tagged limited record History: Vectors.Vector; end record;
end History_Variables;</lang>
The implementation of "History_Variables":
<lang Ada>package body History_Variables is
-- set and get procedure Set(V: in out Variable; Item: Item_Type) is begin V.History.Prepend(Item); end Set;
function Get(V: Variable) return Item_Type is begin return V.History.First_Element; end Get;
-- number of items in history (including the current one) function Defined(V: Variable) return Natural is begin return (1 + V.History.Last_Index) - V.History.First_Index; end Defined;
-- non-destructively search function Peek(V: Variable; Generation: Natural := 1) return Item_Type is Index: Positive := V.History.First_Index + Generation; begin if Index > V.History.Last_Index then raise Constraint_Error; end if; return V.History.Element(Index); end Peek;
procedure Undo(V: in out Variable) is begin V.History.Delete_First; end Undo;
end History_Variables;</lang>
Sample 1: The History of an Integer Variable
<lang Ada>with Ada.Text_IO, History_Variables;
procedure Test_History is
package Int_With_Hist is new History_Variables(Integer);
-- define a history variable I: Int_With_Hist.Variable;
Sum: Integer := 0;
begin
-- assign three values I.Set(3); I.Set(I.Get + 4); I.Set(9);
-- non-destructively display the history for N in reverse 0 .. I.Defined-1 loop Ada.Text_IO.Put(Integer'Image(I.Peek(N))); end loop; Ada.Text_IO.New_Line;
-- recall the three values while I.Defined > 0 loop Sum := Sum + I.Get; I.Undo; end loop; Ada.Text_IO.Put_Line(Integer'Image(Sum));
end Test_History;</lang>
The program generates the following output:
3 7 9 19
Sample 2: The History of a String
<lang Ada>with Ada.Text_IO, History_Variables;
procedure Test_History is
package Str_With_Hist is new History_Variables(String);
-- define a history variable S: Str_With_Hist.Variable;
Sum: Integer := 0;
begin
-- assign three values S.Set("one"); S.Set(S.Get & S.Get); --"oneone" S.Set("three");
-- non-destructively display the history for N in reverse 0 .. S.Defined-1 loop Ada.Text_IO.Put(S.Peek(Generation => N) &" "); end loop; Ada.Text_IO.New_Line;
-- recall the three values while S.Defined > 0 loop Sum := Sum + S.Get'Length; S.Undo; end loop; Ada.Text_IO.Put_Line(Integer'Image(Sum));
end Test_History;</lang>
This time, the output is:
one oneone three 14
AutoHotkey
AutoHotkey records a history of your keypresses, but not your variables. The closest you can come is with a class:
<lang ahk>HV := new HistoryVariable HV.var := 1 HV.var := 2 HV.var := "Latest value" Msgbox % HV.var "`n" HV[2] "`n" HV[3]
class HistoryVariable { __Set(aName, aValue) { this.Insert(1,aValue) Return aValue } __Get(aName) { Return this[1] } }</lang>
C#
<lang c sharp>using System; using System.Collections; using System.Collections.Generic; using System.Linq;
namespace History {
class Program { static void Main(string[] args) { var h = new HistoryObject(); h.Value = 5; h.Value = "foo"; h.Value += "bar";
var history = h.ToArray();
for (int i = 0; i < history.Length; i++) { Console.Write("{0}{1}", history[i], ((i >= history.Length - 1) ? "\n" : " <- ")); }
h.Undo(); h.Undo(); h.Undo();
Console.WriteLine(h.Value); }
private class HistoryObject : IEnumerable<object> { public HistoryObject() { _history = new Stack<object>(); // Initiates the history stack. }
public object Value { get // Returns the top value from the history if there is one. Otherwise null. { if (_history.Count > 0) return _history.Peek(); return null; } set { _history.Push(value); } // Adds the specified value to the history. }
public void Undo() { if (_history.Count > 0) _history.Pop(); // Removes the current value from the history. }
// History stack that will hold all previous values of the object. private readonly Stack<object> _history;
public IEnumerator<object> GetEnumerator() { return _history.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }
}</lang>
Sample Output
foobar <- foo <- 5
Clojure
Clojure does not have history variables, but it can be accomplished via a watcher function that can track changes on a variable.
<lang clojure> (def a (ref 0)) (def a-history (atom [@a])) ; define a history vector to act as a stack for changes on variable a (add-watch a :hist (fn [key ref old new] (swap! a-history conj new))) </lang>
- Sample Output:
user=> (dosync (ref-set a 1)) 1 user=> (dosync (ref-set a 2)) 2 user=> (dosync (ref-set a 3)) 3 user=> @a-history [0 1 2 3] user=> (dotimes [n 3] (println (peek @a-history)) (swap! a-history pop)) 3 2 1 nil user=> @a-history [0]
D
D does not have history variables. The following implementation provides a generic HistoryVariable that protects the historical values by defining them as 'const'.
<lang d>import std.stdio, std.array, std.string, std.datetime, std.traits;
/// A history variable. struct HistoryVariable(T) {
/// A value in a point in time. static struct HistoryValue { SysTime time; T value;
// Alternative to the more common toString. //void toString(scope void delegate(string) output) const { void toString(scope void delegate(const(char)[]) output)const { output(format("%s; %s", time, value)); } }
const(HistoryValue)[] values;
private void addValue(T value) { values ~= HistoryValue(Clock.currTime(), value); }
void opAssign(T value) { addValue(value); }
@property T currentValue() const pure nothrow { return values.back.value; }
alias currentValue this;
@property auto history() const pure nothrow { return values; }
/** Demonstrating D's compile-time reflection features. The member functions that are in this 'static if' block would be added for types T that are arrays (including strings). */ static if (isArray!T) { // Append-with-assign operator. void opOpAssign(string op : "~")(T element) { addValue(currentValue ~ element); }
// Similar implementation for other array operators... }
}
void main() {
HistoryVariable!int x; x = 1; x = 2; x = 3; writefln("%(%s\n%)\n", x.history);
HistoryVariable!string s; s = "hello"; s ~= " world"; s = "goodby"; writefln("%(%s\n%)", s.history);
}</lang>
- Sample Output:
2013-Jan-19 23:04:55.1660302; 1 2013-Jan-19 23:04:55.1660407; 2 2013-Jan-19 23:04:55.1660424; 3 2013-Jan-19 23:04:55.1662551; hello 2013-Jan-19 23:04:55.1662581; hello world 2013-Jan-19 23:04:55.1662596; goodby
Erlang
If we consider the usage of history variables, like not losing the old value by mistake or having a way to get an old value, then Erlang can really help. Being single assignment Erlang will not allow you to lose an old value, and it is certain that the previous values are always there. The downside is that you must handle this manually.
- Output:
1> V1 = "123". "123" 2> V1 = V1 ++ "qwe". ** exception error: no match of right hand side value "123qwe" 3> V2 = V1 ++ "qwe". "123qwe" 4> V3 = V2 ++ "ASD". "123qweASD" 5> V1. "123" 6> V2. "123qwe" 7> V3. "123qweASD"
Forth
Forth does not have history variables, but it is trivial to define them. This one uses a linked list. The history variable is initialized to zero to avoid expensive sanity checks in H@. H-- undoes the assignment of a value and effectively returns a history variable to its former state. <lang forth>: history create here cell+ , 0 , -1 , ;
- h@ @ @ ;
- h! swap here >r , dup @ , r> swap ! ;
- .history @ begin dup cell+ @ -1 <> while dup ? cell+ @ repeat drop ;
- h-- dup @ cell+ @ dup -1 = if abort" End of history" then swap ! ;</lang>
A sample session:
history z ok 23 z h! ok z h@ . 23 ok 34 z h! ok z h@ . 34 ok 45 z h! ok z h@ . 45 ok z .history 45 34 23 ok z dup h-- h@ . 34 ok z dup h-- h@ . 23 ok z dup h-- h@ . 0 ok z dup h-- h@ . :86: End of history
Go
We're all in this for the extra points. Mallon and Takota seem happy with sequences, but time and timestamps were mentioned on LtU. Beyond a separate sequence for each history variable, timestamps enable multiple variables to be seen in a common temporal sequence. In Go, with it's attention to concurrency, this might be done with flexibility for proper handling of shared variables, and efficient handling of variables limited to a single thread. <lang go>package main
import (
"fmt" "sort" "sync" "time"
)
// data type for history variable (its an int) type history struct {
timestamp tsFunc hs []hset
}
// data type for timestamp generator type tsFunc func() time.Time
// data type for a "set" event type hset struct {
int // new value t time.Time // timestamp
}
// newHistory creates a history variable func newHistory(ts tsFunc) history {
return history{ts, []hsetTemplate:T: ts()}
}
// int returns the current value func (h history) int() int {
return h.hs[len(h.hs)-1].int
}
// set does what you expect and returns the timestamp recorded for the event func (h *history) set(x int) time.Time {
t := h.timestamp() h.hs = append(h.hs, hset{x, t}) return t
}
// dump displays a complete history func (h history) dump() {
for _, hs := range h.hs { fmt.Println(hs.t.Format(time.StampNano), hs.int) }
}
// recall recalls the value stored in the history variable at time t. // if the variable had not been created yet, ok is false. func (h history) recall(t time.Time) (int, /*ok*/ bool) {
i := sort.Search(len(h.hs), func(i int) bool { return h.hs[i].t.After(t) }) if i > 0 { return h.hs[i-1].int, true } return 0, false
}
// newTimestamper returns a function that generates unique timestamps. // Use a single timestamper for multiple history variables to preserve // an unambiguous sequence of assignments across the multiple history // variables within a single goroutine. func newTimestamper() tsFunc {
var last time.Time return func() time.Time { if t := time.Now(); t.After(last) { last = t } else { last.Add(1) } return last }
}
// newProtectedTimestamper generates unique timestamps for concurrent // goroutines. func newProtectedTimestamper() tsFunc {
var last time.Time var m sync.Mutex return func() (t time.Time) { t = time.Now() m.Lock() // m protects last if t.After(last) { last = t } else { last.Add(1) t = last } m.Unlock() return }
}
func main() {
// enable history variable support appropriate for single goroutine. ts := newTimestamper() // define a history variable h := newHistory(ts) // assign three values. (timestamps kept for future reference.) ref := []time.Time{h.set(3), h.set(1), h.set(4)} // non-destructively display history fmt.Println("History of variable h:") h.dump() // recall the three values. (this is non-destructive as well, but // different than the dump in that values are recalled by time.) fmt.Println("Recalling values:") for _, t := range ref { rv, _ := h.recall(t) fmt.Println(rv) }
}</lang> Output:
History of variable h: Dec 3 18:51:17.292260000 0 Dec 3 18:51:17.292262000 3 Dec 3 18:51:17.292264000 1 Dec 3 18:51:17.292270000 4 Recalling values: 3 1 4
Haskell
There are no native Haskell history variables, but they are simple to implement.
<lang haskell>import Data.IORef
newtype HVar a = HVar (IORef [a])
newHVar :: a -> IO (HVar a) newHVar value = fmap HVar (newIORef [value])
readHVar :: HVar a -> IO a readHVar (HVar ref) = fmap head (readIORef ref)
writeHVar :: a -> HVar a -> IO () writeHVar value (HVar ref) = modifyIORef ref (value:)
undoHVar :: HVar a -> IO () undoHVar (HVar ref) = do
(_ : history) <- readIORef ref writeIORef ref history
getHistory :: HVar a -> IO [a] getHistory (HVar ref) = readIORef ref
-- Testing main :: IO () main = do
var <- newHVar 0 writeHVar 1 var writeHVar 2 var writeHVar 3 var getHistory var >>= print undoHVar var undoHVar var undoHVar var</lang>
J
J does not natively support "history variables", but the functionality is easy to add:
<lang j>varref_hist_=:'VAR','_hist_',~] set_hist_=:4 :0
V=.varref x if.0>nc<V do.(<V)=:end. (<V)=.V~,<y y
) getall_hist_=:3 :0
(varref y)~
) length_hist_=: #@getall get_hist_=: _1 {:: getall</lang>
Example use:
<lang j> 'x' set_hist_ 9 9
'x' set_hist_ 10
10
'x' set_hist_ 11
11
get_hist_ 'x'
11
length_hist_ 'x'
3
getall_hist_ 'x'
┌─┬──┬──┐ │9│10│11│ └─┴──┴──┘</lang>
Note that each value is contained in a box, so different values do not need to be type compatible with each other. If this is considered a defect then assertions could be added to enforce type compatibility across assignments.
Note that only nouns are supported here: If you want to store verbs using this mechanism you will need to use their gerunds.
Oberon-2
<lang oberon2> MODULE HVar; IMPORT Out, Conv; TYPE (* Generic Object *) Object* = POINTER TO ObjectDesc; ObjectDesc = RECORD Show: PROCEDURE(o:Object); AsString: PROCEDURE(o: Object; VAR str: ARRAY OF CHAR); END;
(* Integers *) Integer = POINTER TO IntegerDesc; IntegerDesc = RECORD (ObjectDesc) val: INTEGER; END;
(* Chars *) Char = POINTER TO CharDesc; CharDesc = RECORD (ObjectDesc) val: CHAR; END;
Node = POINTER TO NodeDesc; NodeDesc = RECORD val: Object; next: Node; END;
(* History Variable *) HVar* = POINTER TO HVarDesc; HVarDesc = RECORD first, last: Node; size-: INTEGER; END;
PROCEDURE CharAsString(o:Object; VAR dst: ARRAY OF CHAR); BEGIN IF LEN(dst) >= 2 THEN dst[1] := 0X; END; dst[0] := o(Char)^.val END CharAsString;
PROCEDURE IntAsString(o:Object; VAR dst: ARRAY OF CHAR); BEGIN Conv.ConvInt(o(Integer)^.val,dst); END IntAsString;
PROCEDURE ShowInt(o:Object); BEGIN Out.Int(o(Integer)^.val,10); END ShowInt;
PROCEDURE ShowChar(o:Object); BEGIN Out.Char(o(Char)^.val); END ShowChar;
PROCEDURE BoxChar(val: CHAR): Char; VAR c: Char; BEGIN NEW(c); c^.val := val; c^.Show := ShowChar; c^.AsString := CharAsString; RETURN c; END BoxChar;
PROCEDURE BoxInt(val:INTEGER): Integer; VAR i: Integer; BEGIN NEW(i); i^.val := val; i^.Show := ShowInt; i^.AsString := IntAsString; RETURN i; END BoxInt;
PROCEDURE InitNode(): Node; VAR l: Node; BEGIN NEW(l); l.val := NIL; l.next := NIL; RETURN l; END InitNode;
PROCEDURE InitHVar(): HVar; VAR hv: HVar; BEGIN NEW(hv); hv.first := NIL; hv.last := NIL; hv.size := 0; RETURN hv; END InitHVar;
PROCEDURE (v: HVar) Value(): Object; BEGIN RETURN v^.first^.val; END Value;
PROCEDURE (v: HVar) Set(o: Object); VAR l: Node; BEGIN l := InitNode(); l^.val := o; IF (v^.first = v^.last) & (v^.first = NIL) THEN v^.first := l; v^.last := l; INC(v^.size); ELSIF (v^.first = v^.last) & (v^.first # NIL) THEN v^.first^.next := l; v^.last := v^.first.next; INC(v^.size); ELSIF (v^.first # v^.last) THEN v^.last^.next := l; v^.last := l; INC(v^.size); END END Set;
PROCEDURE (v: HVar) Undo(): Object; VAR o: Object; BEGIN IF (v^.first = v^.last) & (v^.first = NIL) THEN o := NIL; ELSIF (v^.first = v^.last) & (v^.first # NIL) THEN o := v^.first^.val; v^.first := NIL; v^.last := NIL; DEC(v^.size); ELSE o := v^.first^.val; v^.first := v^.first^.next; DEC(v^.size); END; RETURN o; END Undo;
PROCEDURE (v: HVar) Print(); VAR iter : Node; BEGIN iter := v.first; WHILE(iter # NIL) DO iter^.val^.Show(iter^.val); iter := iter^.next; END; Out.Ln; END Print;
PROCEDURE ShowVal(val: Object); VAR s: ARRAY 128 OF CHAR; BEGIN val^.AsString(val,s); Out.String("> ");Out.String(s);Out.Ln; END ShowVal; VAR history: HVar;
BEGIN history := InitHVar(); history.Set(BoxChar(64X)); history.Set(BoxInt(10)); history.Set(BoxInt(15)); history.Set(BoxInt(20)); history.Print(); ShowVal(history.Undo()); ShowVal(history.Undo()); ShowVal(history.Undo()); END HVar. </lang>
OxygenBasic
Simple history class for fixed length types that do not contain volatile pointer members. <lang oxygenbasic> '============ class History '============
indexbase 0
string buf sys ii,ld,pb
method constructor(sys n=1000, l=sizeof sys) {buf=nuls n*l : pb=strptr buf : ld=l : ii=0} method destructor () {clear} ' method setup(sys n=1000, l=sizeof sys) {buf=nuls n*l : pb=strptr buf : ld=l : ii=0} method clear() {buf="" : pb=0 : ld=0 : ii=0} method max (sys i) {if i>ii{ii=i}} method count() as sys {return ii} method size () as sys {return ld} ' method get (any*p,i) {copy @p, pb+i*ld,ld } 'out method add (any*p) {copy pb+ii*ld,@p,ld : ii++} 'in method put (any*p,sys i) {copy pb+i*ld,@p,ld : max i} 'in ' end class
'==== 'TEST '====
'this works for fixed length types
'it will not work for types containing 'volatile pointers. eg: string members
type vector double x,y,z
vector v
new History hv(1000,sizeof v) 'give number of records and variable size
sys i for i=0 to 9
v<=i,i*10,i*100 'assign new values to vector hv.add v 'add to history
next
string tab=chr(9) : cr=chr(13)+chr(10) string pr="Data History of v" cr cr pr+="n" tab "x" tab "y" tab "z" cr vector sv ' for i=hv.count()-1 to 0 step -1
hv.get sv,i pr+=i tab sv.x tab sv.y tab sv.z cr
next
print pr 'result '9,90,900 : 8,80,800 ...
del hv </lang>
PARI/GP
<lang parigp>default(histsize, 1000) \\ or some other positive number to suit 1+7 sin(Pi) 2^100 \a1 \\ display history item #1, etc. % \\ alternate syntax %1 \\ alternate syntax \a2 \a3 [%1, %2, %3] \\ or any other command using these values</lang>
Perl
Implemented via tie (and what's the usefulness of this?) <lang Perl>package History;
sub TIESCALAR { my $cls = shift; my $cur_val = shift; return bless []; }
sub FETCH { return shift->[-1] }
sub STORE { my ($var, $val) = @_; push @$var, $val; return $val; }
sub get(\$) { @{tied ${+shift}} } sub on(\$) { tie ${+shift}, __PACKAGE__ } sub off(\$) { untie ${+shift} } sub undo(\$) { pop @{tied ${+shift}} }
package main;
my $x = 0; History::on($x);
for ("a" .. "d") { $x = $_ }
print "History: @{[History::get($x)]}\n";
for (1 .. 3) { print "undo $_, "; History::undo($x); print "current value: $x\n"; }
History::off($x); print "\$x is: $x\n";</lang>Output<lang>History: a b c d undo 1, current value: c undo 2, current value: b undo 3, current value: a $x is: a</lang>
Perl 6
<lang perl6>class HistoryVar {
has @.history; has $!var handles <Str gist FETCH Numeric>; method STORE($val) is rw { push @.history, [now, $!var]; $!var = $val; }
}
my $foo := HistoryVar.new;
$foo = 1; $foo = 2; $foo += 3; $foo = 42;
.say for $foo.history; say "Current value: $foo";</lang>
- Output:
Instant:1387608436.366940 (Any) Instant:1387608436.388883 1 Instant:1387608436.402433 2 Instant:1387608436.413677 5 Current value: 42
Python
<lang Python>import sys
HIST = {}
def trace(frame, event, arg):
for name,val in frame.f_locals.items(): if name not in HIST: HIST[name] = [] else: if HIST[name][-1] is val: continue HIST[name].append(val) return trace
def undo(name):
HIST[name].pop(-1) return HIST[name][-1]
def main():
a = 10 a = 20
for i in range(5): c = i
print "c:", c, "-> undo x3 ->", c = undo('c') c = undo('c') c = undo('c') print c print 'HIST:', HIST
sys.settrace(trace) main()</lang>Output<lang>c: 4 -> undo x3 -> 1 HIST: {'a': [10, 20], 'i': [0, 1, 2, 3, 4], 'c': [0, 1], 'name': ['c']}</lang>
PicoLisp
<lang PicoLisp>(de setH ("Var" Val)
(when (val "Var") (with "Var" (=: history (cons @ (: history))) ) ) (set "Var" Val) )
(de restoreH ("Var")
(set "Var" (pop (prop "Var" 'history))) )</lang>
Test:
: (setH 'A "Hello world") -> "Hello world" : (setH 'A '(a b c d)) -> (a b c d) : (setH 'A 123) -> 123 : A -> 123 : (get 'A 'history) -> ((a b c d) "Hello world") : (restoreH 'A) -> (a b c d) : (restoreH 'A) -> "Hello world" : A -> "Hello world" : (restoreH 'A) -> NIL
PL/I
<lang PL/I> declare t float controlled;
do i = 1 to 5; /* a loop to read in and save five values. */ allocate t; get (t); end;
do while (allocation(t) > 0); /* a loop to retrieve the values. */ put (t); free t; end; </lang>
Protium
<lang protium>Turn history on <@ DEFHST>__on</@> Notify Protium we are interested in the variable mv <@ DEFHST>mv</@> Assign a value: <@ LETVARLIT>mv|first value</@><@ SAYVAR>mv</@> Reassign the value: <@ LETVARLIT>mv|second value</@><@ SAYVAR>mv</@> Reassign the value: <@ LETVARLIT>mv|third value</@><@ SAYVAR>mv</@> Dump history <@ SAYDMPHSTVAR>mv</@> Current value: <@ SAYVAR>mv</@> Undo once: <@ ACTUNDVAR>mv</@><@ SAYVAR>mv</@> Undo twice: <@ ACTUNDVAR>mv</@><@ SAYVAR>mv</@> Turn history off <@ DEFHST>__off</@></lang>
Same code, Simplified Chinese dialect <lang protium>Turn history on <# 定义变量史>__on</#> Notify Protium we are interested in the variable mv <# 定义变量史>mv</#> Assign a value: <# 指定变量字串>mv|first value</#><# 显示变量>mv</#> Reassign the value: <# 指定变量字串>mv|second value</#><# 显示变量>mv</#> Reassign the value: <# 指定变量字串>mv|third value</#><# 显示变量>mv</#> Dump history <# 显示全内容变量史变量>mv</#> Current value: <# 显示变量>mv</#> Undo once: <# 运行撤消变量>mv</#><# 显示变量>mv</#> Undo twice: <# 运行撤消变量>mv</#><# 显示变量>mv</#> Turn history off <# 定义变量史>__off</#> </lang>
Sample output
Turn history on Notify Protium we are interested in the variable mv Assign a value: first value Reassign the value: second value Reassign the value: third value Dump history third value^second value^first value^ Current value: third value Undo once: second value Undo twice: first value Turn history off
PureBasic
<lang PureBasic>; integer history variable
Structure historyint
List value.i()
EndStructure
Procedure SetInt (*var.historyint, val.i)
AddElement(*var\value()) If (ListSize(*var\value()) = 1) *var\value() = 0 AddElement(*var\value())
EndIf *var\value() = val
EndProcedure
Procedure ShowHistory (*var.historyint)
ForEach *var\value() Debug *var\value() Next
EndProcedure
Procedure UndoInt (*var.historyint)
If (ListSize(*var\value()) = 1) ProcedureReturn EndIf DeleteElement(*var\value())
EndProcedure
- ----------------------------------------------
Define x.historyint
For i = 0 To 3
setint(x, Random(50)+100 )
Next
Debug "x history:" ShowHistory(x)
Debug ""
For i = 0 To 3
UndoInt(x) Debug "undo, x = "+Str(x\value())
Next </lang>
output
x history: 0 131 109 143 108 undo, x = 143 undo, x = 109 undo, x = 131 undo, x = 0
Racket
Racket does not come with history variables, but they can be provided as a library as follows:
<lang racket>
- lang racket
(require rackunit)
(struct hvar (current history) #:mutable)
(define (make-hvar v) (hvar v empty))
(define (hvar-set! hv new)
(match-define (hvar cur hist) hv) (set-hvar-history! hv (cons cur hist)) (set-hvar-current! hv new))
(define (hvar-undo! hv)
(match-define (hvar cur (cons old hist)) hv) (set-hvar-current! hv old) (set-hvar-history! hv hist))
- unit tests
(define hv (make-hvar 0)) (hvar-set! hv 1) (check-equal? (hvar-current hv) 1) (hvar-set! hv 2) (hvar-set! hv 3) (check-equal? (hvar-history hv) '(2 1 0)) (hvar-undo! hv) (hvar-undo! hv) (hvar-undo! hv) (check-equal? (hvar-current hv) 0) </lang>
REXX
Version 1
The REXX language doesn't support histories, but can be coded with little trouble.
The history list part of the VARSET subroutine could be separated into its
own if you wanted to keep the subroutine's function pure.
<lang rexx>/*REXX pgm shows a method to track history of assignments to a REXX var.*/
varset!.=0 /*initialize the whole shebang. */
call varset 'fluid',min(0,-5/2,-1) ; say 'fluid=' fluid
call varset 'fluid',3.14159 ; say 'fluid=' fluid
call varset 'fluid',' Santa Claus' ; say 'fluid=' fluid
call varset 'fluid',,999
say 'There were' result "assignments (sets) for the FLUID variable."
exit /*stick a fork in it, we're done.*/
/*─────────────────────────────────────VARSET subroutine────────────────*/
varset: arg ?x; parse arg ?z, ?v, ?L /*varName, value, optional-List. */
if ?L== then do /*not list, so set the X variable*/
?_=varset!.0.?x+1 /*bump the history count (SETs). */ varset!.0.?x=?_ /* ... and store it in "database"*/ varset!.?_.?x=?v /* ... and store the SET value.*/ call value(?x),?v /*now, set the real X variable.*/ return ?v /*also, return the value for FUNC*/ end
say /*show blank line for readability*/
do ?j=1 to ?L while ?j<=varset!.0.?x /*list "set" history.*/ say 'history entry' ?j "for var" ?z":" varset!.?J.?x end /*?j*/
return ?j-1 /*return the num of assignments. */</lang> output
fluid= -2.5 fluid= 3.14159 fluid= Santa Claus history entry 1 for var fluid: -2.5 history entry 2 for var fluid: 3.14159 history entry 3 for var fluid: Santa Claus There were 3 assignments (sets) for the FLUID variable.
Version 2
<lang rexx> /* REXX ***************************************************************
- Demonstrate how the history of assignments can be kept and shown
- 13.07.2012 Walter Pachl Rewrite of Version 1 for (my) readability
- varset.i.varu contains the ith value assigned to var
- varset.0.varu contains the number of assignments so far
- /
varset.=0 /*initialize the assignment count */
call varset 'fluid',min(0,-5/2,-1) ; say 'fluid=' fluid call varset 'fluid',3.14159 ; say 'fluid=' fluid call varset 'fluid',3.14159 ; say 'fluid=' fluid call varset 'fluid',' Santa Claus' ; say 'fluid=' fluid call varset 'FLUID',' Easter Rabbit' ; say 'fluid=' fluid
say 'There were' varset('fluid',,'L'),
'assignments (sets) for the FLUID variable.'
exit
varset: Procedure Expose varset. /**********************************************************************
- record values assigned to var (=varu as Rexx is case insensitive)
- Invoke with list<> to list the sequence of assigned values
- and return the number of assignments made (using this routine)
- /
Parse Upper Arg varu /* name of variable in uppercase */ Parse arg var,value,list /*varName, value, optional-List. */
if list= then do /*not list, so set the X variable*/ z=varset.0.varu+1 /*bump the history count (SETs). */ varset.z.varu=value /* ... and store it in "database"*/ varset.0.varu=z /*the new history count */ call value var,value /*now assign the value to var */ end else Do Say /*show blank line for readability*/ do i=1 to varset.0.varu /*list the assignment history */ say 'history entry' i "for var" var":" varset.i.varu end end Return varset.0.varu /*return the number of assignments. */
</lang>
Ruby
This uses trace_var, which only works for global variables: <lang ruby>foo_hist = [] trace_var(:$foo){|v| foo_hist.unshift(v)}
$foo = "apple" $foo = "pear" $foo = "banana"
p foo_hist # => ["banana", "pear", "apple"] </lang>
Scala
Scala doesn't have a native support for history variables, but it's quite easy to implement them. The following class uses same conventions as ML's mutable reference cells. (i.e. !
as accessor, and :=
as mutator.)
<lang scala>class HVar[A](initialValue: A) extends Proxy {
override def self = !this override def toString = "HVar(" + !this + ")" def history = _history
private var _history = List(initialValue) def unary_! = _history.head def :=(newValue: A): Unit = { _history = newValue :: _history } def modify(f: A => A): Unit = { _history = f(!this) :: _history } def undo: A = { val v = !this _history = _history.tail v }
}</lang> Usage: <lang scala>scala> val h = new HVar(3) h: HVar[Int] = HVar(3)
scala> h := 11
scala> h := 90
scala> !h res32: Int = 90
scala> h.history res33: List[Int] = List(90, 11, 3)
scala> h.undo res34: Int = 90
scala> h.undo res35: Int = 11
scala> h.undo res36: Int = 3</lang>
Smalltalk
Smalltalk doesn't have native support for history variables. It could be implemented using (implementation dependent) tracing facilities, or by changing the code generator (which is part of the library and open for change). The following implementation is portable and in the spirit of the Lisp implementation above, and defines a new "assignment operator": <lang smalltalk>Object subclass:'HVar'
instanceVariableNames:'values' classVariableNames: poolDictionaries: category:'example'.
!
!HVar methodsFor:'accessing'!
<-- value
(values ifNil:[values := OrderedCollection new]) add:value. ^ value
!
value
^ values last
!
undo
values removeLast.
!
history
^ history
! !
|x|
x := HVar new. x <-- 1. x value. x <-- 2. x <-- (x value + 1). x value. x history. </lang>
Tcl
Though Tcl's variables don't have history by default, it can be added easily through the use of traces: <lang tcl># Define the history machinery proc histvar {varName operation} {
upvar 1 $varName v ___history($varName) history switch -- $operation {
start { set history {} if {[info exist v]} { lappend history $v } trace add variable v write [list histvar.write $varName] trace add variable v read [list histvar.read $varName] } list { return $history } undo { set history [lrange $history 0 end-1] } stop { unset history trace remove variable v write [list histvar.write $varName] trace remove variable v read [list histvar.read $varName] }
}
} proc histvar.write {key varName args} {
upvar 1 $varName v ___history($key) history lappend history $v
} proc histvar.read {key varName args} {
upvar 1 $varName v ___history($key) history set v [lindex $history end]
}</lang> Demonstrating how to use it: <lang tcl># Enable history for foo histvar foo start set foo {a b c d} set foo 123 set foo "quick brown fox" puts $foo puts foo-history=[join [histvar foo list] ", "] puts $foo histvar foo undo puts $foo histvar foo undo puts $foo histvar foo stop</lang> Output:
quick brown fox foo-history=a b c d, 123, quick brown fox quick brown fox 123 a b c d
- Programming Tasks
- Solutions by Programming Task
- Ada
- AutoHotkey
- C sharp
- Clojure
- D
- Erlang
- Forth
- Go
- Haskell
- J
- Oberon-2
- OxygenBasic
- PARI/GP
- Perl
- Perl 6
- Python
- PicoLisp
- PL/I
- Protium
- PureBasic
- Racket
- REXX
- Ruby
- Scala
- Smalltalk
- Tcl
- 8086 Assembly/Omit
- 80386 Assembly/Omit
- AWK/Omit
- BASIC/Omit
- Batch File/Omit
- C/Omit
- C++/Omit
- GUISS/Omit
- Locomotive Basic/Omit
- Lotus 123 Macro Scripting/Omit
- Lua/Omit
- ML/I/Omit
- UNIX Shell/Omit
- Visual Basic/Omit
- ZX Spectrum Basic/Omit
- Déjà Vu/Omit