Hi all,
I have seen (on this ML and elsewhere) many proposals how should REBOL
blocks behave. (recently e.g. BrianH's rage-related proposal in R3 blog
discussion). I think, that it may be interesting to find the behaviour
that will be "the most popular" between REBOL users.
===How to specify the behaviour?
Variants are:
*Specify results of some expressions
*Write a REBOL prototype code implementing the behaviour you prefer
*Other way?
===Results of expressions
Problems:
*This kind of specification may be "incomplete" - not specifying all
properties
*Such a specification may even be "unimplementable", if we choose
properties that contradict each other
===REBOL prototype implementation
I prefer REBOL prototype implementation as a way how to specify the
behaviour, since it:
*It is easily translatable to other programming languages
*Instantly proves the "implementability" of the behaviour
*Allows us to estimate the "cost" of the implementation (estimate the
memory requirements and the speed)
*Completely and unambiguously specifies the behaviour
*Allows everyone to devise his behaviour tests and check the results
(see some tests below e.g.)
===My contributions
I wrote two implementation prototypes in REBOL, one of them is my
"past-tail/past-head tolerant" implementation called UNL, which is
optimized for easy any-based indexing (not just one-based, or
zero-based, see below).
My second prototype called IMP is a "position-preserving" (as opposed to
index-preserving) implementation. I was inspired by BrianH's
contribution to the R3 blog range discussion when writing it. It is
interesting, since it shows, that even blocks can "preserve position".
My current favourite is the UNL implementation (see the reasons below).
Rebol [
Author: "Ladislav Mecir"
Title: "unl-block"
Purpose: {
past-tail/past-head tolerant block implementation prototype
with zero-based indexing
Advantages:
- the same speed as the current implementation has
- the same memory consumption as for the current implementation
- past-tail/past-head handling allows for any-base indexing
- strings can work the same way
Disadvantages:
- small backward incompatibility
}
]
unl-min-index: -536870912
unl-max-index: 2147483647 + unl-min-index
unl-add: func [
a [integer!]
b [integer!]
] [
any [
all [
unl-max-index - a <= b
unl-max-index
]
all [
unl-min-index - a >= b
unl-min-index
]
a + b
]
]
unl-block: make object! [
aux: none
index: none
]
unl-index?: func [
u-blk [object!]
] [
u-blk/index
]
unl-skip: func [
u-blk [object!]
offset [integer!]
] [
make u-blk [index: unl-add u-blk/index offset]
]
unl-head: func [
u-blk [object!]
] [
make u-blk [index: 0]
]
unl-head?: func [
u-blk [object!]
] [
u-blk/index = 0
]
unl-past-head?: func [
u-blk [object!]
] [
u-blk/index < 0
]
unl-aux: make object! [
data: none
]
unl-make-empty: func [] [
make unl-block [
aux: make unl-aux [data: copy []]
index: 0
]
]
unl-pick: func [
u-blk [object!]
offset [integer!]
] [
pick u-blk/aux/data 1 + unl-add u-blk/index offset
]
unl-tail: func [
u-blk [object!]
] [
make u-blk [index: length? u-blk/aux/data]
]
unl-tail?: func [
u-blk [object!]
] [
u-blk/index = length? u-blk/aux/data
]
unl-empty?: func [
u-blk [object!]
] [
u-blk/index >= length? u-blk/aux/data
]
unl-past-tail?: func [
u-blk [object!]
] [
u-blk/index > length? u-blk/aux/data
]
unl-length?: func [
u-blk [object!]
] [
max 0 (length? u-blk/aux/data) - max 0 u-blk/index
]
unl-insert-only: func [
[catch]
u-blk [object!]
value [any-type!]
] [
if unl-past-head? u-blk [
throw make error! "past head!"
]
if unl-past-tail? u-blk [
throw make error! "past tail!"
]
if unl-max-index <= length? u-blk/aux/data [
throw make error! "series overflow!"
]
insert/only skip u-blk/aux/data u-blk/index get/any 'value
make u-blk [index: u-blk/index + 1]
]
unl-remove: func [
u-blk [object!]
] [
if any [
unl-tail? u-blk
unl-past-tail? u-blk
unl-past-head? u-blk
] [return u-blk]
remove at u-blk/aux/data u-blk/index + 1
u-blk
]
unl-same?: func [
a [object!]
b [object!]
] [
found? all [
a/index = b/index
same? a/aux/data b/aux/data
]
]
comment [
; one-base indexing made easy:
; make an empty u-blk
u-blk: unl-make-empty
; insert 1
unl-insert-only u-blk 1
; skip "past head"
u-blk1: unl-skip u-blk -1
; index check
unl-index? u-blk1 ; == -1
; now we can use one-based indexing
unl-pick u-blk1 1 ; == 1
]
Rebol [
Author: "Ladislav Mecir"
Date: 14-Feb-2007/9:11:06+1:00
Title: "imp-block"
Purpose: {
A "position keeping" block implementation variants
with zero-based indexing
Advantages:
- improved LIST! compatibility
- may look more "natural" to some users
- does not "generate" past-tail positions
Disadvantages:
- speed - one more indirection level
- memory consumption: additional 96 bits per element
- small backward incompatibility
- impractical for strings
}
]
imp-element: make object! [
value: none
reference: none
]
imp-reference: make object! [
index: none
next: none
]
imp-aux: make object! [
start: none
tail: none
]
imp-block: make object! [
aux: none
reference: none
]
handle-removed: func [
reference [object!]
] [
if reference/next [
handle-removed reference/next
reference/index: reference/next/index
reference/next: none
]
]
imp-tail?: imp-empty?: func [
i-block [object!]
] [
handle-removed i-block/reference
i-block/reference/index = i-block/aux/tail
]
imp-head?: func [
i-block [object!]
] [
handle-removed i-block/reference
i-block/reference/index = 0
]
imp-tail: func [
i-block [object!]
/local tail+1
] [
tail+1: i-block/aux/tail + 1
make i-block [reference: i-block/aux/start/:tail+1/reference]
]
imp-head: func [
i-block [object!]
] [
make i-block [reference: i-block/aux/start/1/reference]
]
imp-index?: func [
i-block [object!]
] [
handle-removed i-block/reference
i-block/reference/index
]
imp-length?: func [
i-block [object!]
] [
i-block/aux/tail - imp-index? i-block
]
imp-skip: func [
i-block [object!]
offset [integer!]
/local index
] [
index: imp-index? i-block
if offset < negate index [return imp-head i-block]
if i-block/aux/tail - index <= offset [return imp-tail i-block]
index: index + offset + 1
make i-block [reference: i-block/aux/start/:index/reference]
]
imp-pick: func [
i-block [object!]
index [integer!]
/local base
] [
base: imp-index? i-block
if index < negate base [return none]
if i-block/aux/tail - base <= index [return none]
base: pick i-block/aux/start index + base + 1
return get/any in base 'value
]
imp-remove: func [
{just the "basic" remove - without refinements}
i-block [object!]
/local index start index+1 i-tail new-reference
] [
if imp-empty? i-block [return i-block]
index: imp-index? i-block
index+1: index + 1
i-tail: i-block/aux/tail
start: i-block/aux/start
; move the elements
repeat i i-tail - index [
new-reference: pick start index+1 + i
new-reference/reference/index: index + i - 1
poke start index + i new-reference
]
remove back tail start
; shorten the i-block
i-block/aux/tail: i-tail - 1
; mark the original reference as "removed"
new-reference: start/:index+1/reference
i-block/reference/next: new-reference
i-block/reference: new-reference
i-block
]
imp-insert-only: func [
{just the "basic" insert/only - no other refinements}
i-block [object!]
value [any-type!]
/local index i-tail start new-reference index+1
] [
index: imp-index? i-block
index+1: index + 1
i-tail: i-block/aux/tail
start: i-block/aux/start
; enlarge the i-block
insert tail start none
; move the elements
repeat i i-tail - index + 1 [
new-reference: pick start i-tail + 2 - i
new-reference/reference/index: i-tail + 2 - i
poke start i-tail + 3 - i new-reference
]
i-block/aux/tail: i-tail + 1
; create a new element
start/:index+1: make imp-element [reference: make imp-reference []]
start/:index+1/reference/index: index
error? set/any in start/:index+1 'value get/any 'value
i-block
]
imp-same?: func [
i-block1 [object!]
i-block2 [object!]
] [
found? all [
same? i-block1/aux i-block2/aux
equal? imp-index? i-block1 imp-index? i-block2
]
]
imp-make-empty: func [] [
make imp-block [
aux: make imp-aux [
start: reduce [make imp-element []]
tail: 0
]
reference: make imp-reference [index: 0]
aux/start/1/reference: reference
]
]
imp-to-block: func [
i-block [object!]
/local result start
] [
start: skip i-block/aux/start imp-index? i-block
result: make block! 0
repeat i imp-length? i-block [
insert/only tail result get in start/:i 'value
]
result
]
imp-mold: func [
i-block [object!]
] [
mold/all imp-to-block i-block
]
comment [
; behaviour comparisons
include %unl-block.r
; empty block creation
i-blk: imp-make-empty
u-blk: unl-make-empty
blk: make block! 0
; tail? test #1
imp-tail? i-blk ; == true
unl-tail? u-blk ; == true
tail? blk ; == true
; insert 1
imp-insert-only i-blk 1
unl-insert-only u-blk 1
insert/only blk 1
; insert tail 2
imp-insert-only imp-tail i-blk 2
unl-insert-only unl-tail u-blk 2
insert/only tail blk 2
; position change after insert test #1, see the differences
imp-tail? i-blk ; == true - i-blk "keeps the original position"
unl-tail? u-blk ; == false - u-blk "keeps index"
tail? blk ; == false - blk "keeps index"
; jump to tail
i-blk: imp-tail i-blk
u-blk: unl-tail u-blk
blk: tail blk
; big positive skip test #1
imp-head? imp-skip i-blk 2147483647 ; == false - correct!
unl-head? unl-skip u-blk 2147483647 ; == false - correct!
head? skip blk 2147483647 ; == true - bug!
; index? test #1
imp-index? i-blk ; == 2 - zero-based
unl-index? u-blk ; == 2 - zero-based
index? blk ; == 3 - one-based
; pre-tail position
i-blk2: imp-skip i-blk -1
u-blk2: unl-skip u-blk -1
blk2: skip blk -1
; index? test #2
imp-index? i-blk2 ; == 1 - zero-based
unl-index? u-blk2 ; == 1 - zero-based
index? blk2 ; == 2 - one-based
; pick test #1
imp-pick i-blk2 0 ; == 2
unl-pick u-blk2 0 ; == 2
pick blk2 1 ; == 2
; remove the first element
imp-remove imp-head i-blk
unl-remove unl-head u-blk
remove head blk
; pick test #2
imp-pick i-blk2 0 ; == 2 - i-blk2 keeps position
unl-pick u-blk2 0 ; == none - u-blk2 keeps index
pick blk2 1 ; == none - blk keeps index
; index? test #3
imp-index? i-blk2 ; == 0 - i-blk2 keeps position
unl-index? u-blk2 ; == 1 - u-blk2 keeps index
index? blk2 ; == 2 - blk keeps index
; tail? test #2
imp-tail? i-blk2 ; == false - keeps position
unl-tail? u-blk2 ; == true - keeps index
tail? blk2 ; == true - keeps index
; index? test #4
imp-index? i-blk ; == 1 - keeps position
unl-index? u-blk ; == 2 - keeps index
index? blk ; == 2 - suspicious result
; tail? test #3
imp-tail? i-blk ; == true - really the tail position
unl-tail? u-blk ; == false - (actually past-tail; tail? and empty?
are *not* identical)
tail? blk ; == true - suspicious result, see below
; same? test #1
imp-same? i-blk imp-tail i-blk ; == true - consistent with the above
unl-same? u-blk unl-tail u-blk ; == false - consistent with the above
same? blk tail blk ; == false - inconsistent with the above
; insert again
imp-insert-only imp-head i-blk 1
unl-insert-only unl-head u-blk 1
insert/only head blk 1
; index? test #5
imp-index? i-blk2 ; == 1 - keeps position
unl-index? u-blk2 ; == 1 - keeps index
index? blk2 ; == 2 - keeps index
; pick test #3
imp-pick i-blk2 0 ; == 2 - keeps position
unl-pick u-blk2 0 ; == 2 - keeps index
pick blk2 1 ; == inconsistent with index? test #3 & #4
; index? test #6
imp-index? i-blk ; == 2 - keeps position
unl-index? u-blk ; == 2 - keeps index
index? blk ; == 3 - inconsistent with index? test #3 & #4
]
- Ladislav
--
To unsubscribe from the list, just send an email to
lists at rebol.com with unsubscribe as the subject.