API Design

classic Classic list List threaded Threaded
21 messages Options
12
Reply | Threaded
Open this post in threaded view
|

API Design

Tobias Kieslich

Hi there,

I'm designing a library which has a Buffer module that allows to  
contain binary data.  Think of it as a string just with less features  
:-P ... except it is mutable.  Now I'm writing a companion module  
called Buffer.Segment that allows access to just a limited part of the  
buffer which is defined by an offset and a length.  I'd plan the API  
look like this:

b  = Buffer( 1024 )      -- buffer with 124 bytes
s  = Segment( b )        -- creates a segment starting at the first  
byte of the buffer as long as the buffer itself
s1 = Segment( 128, 256 ) -- Segment with 128 byte offset and 256 bytes length
s2 = Segment( 439 )      -- this is the question, shall this be:

                      - a segment starting at byte 439 and have a  
length to the end of the buffer, or
                      - a segment starting at first byte being 439 bytes long

I don't think there is a 'right' answer, but to all those people who  
work with this kind of stuff, what would feel more natural to you?
Also, does Segment( offset, length ) makes more sense than Segment(  
length, offset ) ?

Thanks for the input,

  - Tobias


Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tomás Guisasola-2
Hi Tobias

It seems strange to me that the Segment does not refer to the buffer.  
Would'nt you need to provide the buffer?  Something like:

b = Buffer (1024)
s1 = b:Segment (128, 256) -- from 128 to 384
s2 = b:Segment (439)      -- from 439 to 1024 (which is the end of the
buffer)

IMHO, since the offset is the first argument, it seems to me that it
should be more important than the length, thus the above interpretation.

Regards,
Tomás

On 2017-09-18 15:18, [hidden email] wrote:

> Hi there,
>
> I'm designing a library which has a Buffer module that allows to
> contain binary data.  Think of it as a string just with less features
> :-P ... except it is mutable.  Now I'm writing a companion module
> called Buffer.Segment that allows access to just a limited part of the
>  buffer which is defined by an offset and a length.  I'd plan the API
> look like this:
>
> b  = Buffer( 1024 )      -- buffer with 124 bytes
> s  = Segment( b )        -- creates a segment starting at the first
> byte of the buffer as long as the buffer itself
> s1 = Segment( 128, 256 ) -- Segment with 128 byte offset and 256 bytes
> length
> s2 = Segment( 439 )      -- this is the question, shall this be:
>
>                      - a segment starting at byte 439 and have a
> length to the end of the buffer, or
>                      - a segment starting at first byte being 439 bytes
> long
>
> I don't think there is a 'right' answer, but to all those people who
> work with this kind of stuff, what would feel more natural to you?
> Also, does Segment( offset, length ) makes more sense than Segment(
> length, offset ) ?
>
> Thanks for the input,
>
>  - Tobias

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Egor Skriptunoff-2
b = Buffer(1024)
s1 = b:Segment(128, 256) -- from 128 to 384
s1 = b:Segment(128):size(256) -- from 128 to 384
s1 = b:Segment(128):to(384) -- from 128 to 384
s2 = b:Segment(439)      -- from 439 to 1024
s3 = b:Segment()      -- from 0 to 1024
s4 = b:Segment():size(128)      -- from 0 to 128
s5 = b:Segment(128):to(384):next() -- from 384 to 640
s5 = b:Segment(128):to(384):shift(10) -- from 138 to 394
Reply | Threaded
Open this post in threaded view
|

Re: API Design

Martin
In reply to this post by Tobias Kieslich
On 09/18/2017 07:18 PM, [hidden email] wrote:

> s1 = Segment( 128, 256 ) -- Segment with 128 byte offset and 256 bytes length
> s2 = Segment( 439 )      -- this is the question, shall this be:
>
>                      - a segment starting at byte 439 and have a length to the
> end of the buffer, or
>                      - a segment starting at first byte being 439 bytes long
>
> I don't think there is a 'right' answer, but to all those people who work with
> this kind of stuff, what would feel more natural to you?
> Also, does Segment( offset, length ) makes more sense than Segment( length,
> offset ) ?
>
> Thanks for the input,
>
>  - Tobias

I feel (<offset>, <length>) order is more natural for describing segments.

But for slightly different task, say function to read <length> bytes from
current or specified stream position, (<length>, <offset>) order is natural.

-- Martin

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Russell Haley
In reply to this post by Egor Skriptunoff-2
On Mon, Sep 18, 2017 at 11:48 AM, Egor Skriptunoff
<[hidden email]> wrote:
> b = Buffer(1024)
> s1 = b:Segment(128, 256) -- from 128 to 384
> s1 = b:Segment(128):size(256) -- from 128 to 384
> s1 = b:Segment(128):to(384) -- from 128 to 384
> s2 = b:Segment(439)      -- from 439 to 1024
> s3 = b:Segment()      -- from 0 to 1024
> s4 = b:Segment():size(128)      -- from 0 to 128
> s5 = b:Segment(128):to(384):next() -- from 384 to 640
> s5 = b:Segment(128):to(384):shift(10) -- from 138 to 394

+1.

b:Segment() - I would think this would be 0 to the length of the buffer.

I like the :next() and :shift() ideas, but that would mean the module
would need internal state on what the size of the 'chuck' would be?
Perhaps:

cs = 256
s4 = b:Segment(128,cs):next(cs)

or if state IS desired:

b.def_chuck_size = 256
s4 = b:Segment(128):next()

But that gets pretty messy I think... Maybe:

offset = 128
b.def_chuck_size = 256
s1 = b:Segment(offset)
sx = b:Next()

That just seems to add complication for syntax though.

Russ

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Frank Kastenholz-2
In reply to this post by Tobias Kieslich
Hi

Xxxx(start, Len)
Or
Xxxx(start, end)

Are what I personally like

Or subscripts ala buffer[start,end] :-)

More important than that are, imho, some higher level issues like
Consistency --- do it the same everywhere (as opposed to memcpy vs strcpy; one is source, dest and the other is dest,src.... grrrr)

Also keep it intuitive ... e.g., most people will the 1st arg is always the start, so don't have the normal mode be xxxx(start,len) and then have a 1-argument variant of xxxx(len) with the buffer start being an implied 1st argument.

And .. having implied additional optional arguments is something I've discouraged in the past.  Typing the additional arguments is no problem
It stresses the "do you really want xxx for argument n?" In the programmers mind .... as well as the "we really mean xxxx!" In the mind of the next programmer to read the code

Frank

> On Sep 18, 2017, at 2:18 PM, [hidden email] wrote:
>
>
> Hi there,
>
> I'm designing a library which has a Buffer module that allows to contain binary data.  Think of it as a string just with less features :-P ... except it is mutable.  Now I'm writing a companion module called Buffer.Segment that allows access to just a limited part of the buffer which is defined by an offset and a length.  I'd plan the API look like this:
>
> b  = Buffer( 1024 )      -- buffer with 124 bytes
> s  = Segment( b )        -- creates a segment starting at the first byte of the buffer as long as the buffer itself
> s1 = Segment( 128, 256 ) -- Segment with 128 byte offset and 256 bytes length
> s2 = Segment( 439 )      -- this is the question, shall this be:
>
>                    - a segment starting at byte 439 and have a length to the end of the buffer, or
>                    - a segment starting at first byte being 439 bytes long
>
> I don't think there is a 'right' answer, but to all those people who work with this kind of stuff, what would feel more natural to you?
> Also, does Segment( offset, length ) makes more sense than Segment( length, offset ) ?
>
> Thanks for the input,
>
> - Tobias
>
>


Reply | Threaded
Open this post in threaded view
|

Re: API Design

Sean Conner
In reply to this post by Tobias Kieslich
It was thus said that the Great [hidden email] once stated:
>
> I'm designing a library which has a Buffer module that allows to  
> contain binary data.  Think of it as a string just with less features  
> :-P

  There was a heated discussion about this back in 2014:

        http://lua-users.org/lists/lua-l/2014-08/msg00819.html

I wrote test code (it's still available) to test that a buffer userdata
would be faster than Lua immutable strings.  Immutable strings were faster,
but like I said, I could have done something wrong.

> I don't think there is a 'right' answer, but to all those people who  
> work with this kind of stuff, what would feel more natural to you?
> Also, does Segment( offset, length ) makes more sense than Segment(  
> length, offset ) ?

  When I work with this kind of data, I either pass it to C for parsing (Lua
5.1), use string.unpack() (Lua 5.3) or LPeg, depending upon the type of
data.  For building data, it's either string concatenation, C (binary
packets under Lua 5.1) or string.pack() (Lua 5.3).

  -spc (Coroutines never did get back to me about my code ... )


Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tobias Kieslich
In reply to this post by Egor Skriptunoff-2
Quoting Egor Skriptunoff <[hidden email]>:

> b = Buffer(1024)
> s1 = b:Segment(128, 256) -- from 128 to 384
> s1 = b:Segment(128):size(256) -- from 128 to 384
> s1 = b:Segment(128):to(384) -- from 128 to 384
> s2 = b:Segment(439)      -- from 439 to 1024
> s3 = b:Segment()      -- from 0 to 1024
> s4 = b:Segment():size(128)      -- from 0 to 128
> s5 = b:Segment(128):to(384):next() -- from 384 to 640
> s5 = b:Segment(128):to(384):shift(10) -- from 138 to 394

Thank you guys. not providing b:Segment() is a big oversight on my part.
All this is great input and gave me a lot to think about.  I'll have some
refactoring to do ...

-Tobias



Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tobias Kieslich
In reply to this post by Sean Conner

It was thus said that the Great Sean Conner <[hidden email]>  once stated:

> It was thus said that the Great [hidden email] once stated:
>>
>> I'm designing a library which has a Buffer module that allows to
>> contain binary data.  Think of it as a string just with less features
>> :-P
>
>   There was a heated discussion about this back in 2014:
>
> http://lua-users.org/lists/lua-l/2014-08/msg00819.html
>
> I wrote test code (it's still available) to test that a buffer userdata
> would be faster than Lua immutable strings.  Immutable strings were faster,
> but like I said, I could have done something wrong.
>
>> I don't think there is a 'right' answer, but to all those people who
>> work with this kind of stuff, what would feel more natural to you?
>> Also, does Segment( offset, length ) makes more sense than Segment(
>> length, offset ) ?
>
>   When I work with this kind of data, I either pass it to C for parsing (Lua
> 5.1), use string.unpack() (Lua 5.3) or LPeg, depending upon the type of
> data.  For building data, it's either string concatenation, C (binary
> packets under Lua 5.1) or string.pack() (Lua 5.3).
>
>   -spc (Coroutines never did get back to me about my code ... )

I never tested it myself, so thanks for the code!

The main reason why I have a buffer module is not just for mutable network
buffers.  I do have a binary packer/parser that works on Buffer,Segments and
Lua strings alike, however, when you pass it a buffer it can do simple writes
which makes it really convenient:

> Pack   = require( "t.Pack" )
> Buffer = require( "t.Buffer" )
>
> -- this uses the same identifiers as string.pack
> -- `r` is a signed bit-sized integer, `R` is un-signed, `v` is boolean
> p      = Pack(
>>      { threeUInt  = '>I3' }
>>    , { twoIntegs  = '<i2' }
>>    , { twoBytes      = Pack(
>>         { signedByte = 'b' }
>>       , { unsignByte = 'B' }
>>    ) }
>>    , { bits       = Pack( 'r4','R7',Pack( 'r3', 8  
>> ),'r15','v','v','R1','R1','r1','r1' ) }
>>    , { tail       = '>I5<I4h' }
>> )
>
> s = 'AbCdEfGhIjKlMnOpQrStVwxy'
>
> -- 'execution' of a packer allows for read or write operations
> print( p.bits[3][4]( s ) )
-3
> for i,v in pairs( p.bits[3] ) do print( i, v(s) ) end
1       2
2       2
3       -2
4       -3
5       1
6       1
7       3
8       3
> -- fails, because s is immutable
> p.bits[3][4]( s, 2 )
stdin:1: bad argument #2 to '?' (Can't write value to string type)
stack traceback:
         [C]: in field '?'
         stdin:1: in main chunk
         [C]: in ?
>
> sz = Pack.getSize( p )  -- size in bytes, #p gets the number of elements
> b = Buffer( sz )
> b:toHex()
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> -- buffers are mutable
> p.bits[3][4]( b, 2 )
> p.bits[6]( b, true )
> b:toHex( )
00 00 00 00 00 00 00 00 00 04 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00
>




Reply | Threaded
Open this post in threaded view
|

Re: API Design

Lourival Vieira Neto
In reply to this post by Tobias Kieslich
Hi Tobias,

> I'm designing a library which has a Buffer module that allows to contain
> binary data.

Perhaps you would like to take a look at Luadata [1].

[1] https://github.com/lneto/luadata

Regards,
--
Lourival Vieira Neto

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Sean Conner
In reply to this post by Tobias Kieslich
It was thus said that the Great [hidden email] once stated:
>
> The main reason why I have a buffer module is not just for mutable network
> buffers.  I do have a binary packer/parser that works on Buffer,Segments
> and Lua strings alike, however, when you pass it a buffer it can do simple
> writes which makes it really convenient:

  We have a difference of approach.  I tend to convert raw binary data into
native Lua data (usually a table of fields) as soon as possible, do all the
manipulations using straight Lua, then pack everything back up into raw
binary data.  

  -spc


Reply | Threaded
Open this post in threaded view
|

Re: API Design

Russell Haley
In reply to this post by Lourival Vieira Neto
On Mon, Sep 18, 2017 at 2:12 PM, Lourival Vieira Neto
<[hidden email]> wrote:
> Hi Tobias,
>
>> I'm designing a library which has a Buffer module that allows to contain
>> binary data.
>
> Perhaps you would like to take a look at Luadata [1].
>
> [1] https://github.com/lneto/luadata

Hi, my understanding is code on Github with no explicit license is
"All Rights Reserved". Is this intended or would you be willing to
provide a more liberal license?

Thanks,
Russ

> Regards,
> --
> Lourival Vieira Neto
>

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Russell Haley
On Mon, Sep 18, 2017 at 3:23 PM, Russell Haley <[hidden email]> wrote:

> On Mon, Sep 18, 2017 at 2:12 PM, Lourival Vieira Neto
> <[hidden email]> wrote:
>> Hi Tobias,
>>
>>> I'm designing a library which has a Buffer module that allows to contain
>>> binary data.
>>
>> Perhaps you would like to take a look at Luadata [1].
>>
>> [1] https://github.com/lneto/luadata
>
> Hi, my understanding is code on Github with no explicit license is
> "All Rights Reserved". Is this intended or would you be willing to
> provide a more liberal license?
>
> Thanks,
> Russ

My bad, my fingers work faster than my brain. I looked into the files
themselves and see it is Licensed.

Russ

>> Regards,
>> --
>> Lourival Vieira Neto
>>

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tobias Kieslich
In reply to this post by Frank Kastenholz-2

Quoting Frank Kastenholz <[hidden email]>:

> Hi
>
> Xxxx(start, Len)
> Or
> Xxxx(start, end)
>
> Are what I personally like
>
> Or subscripts ala buffer[start,end] :-)
>
> More important than that are, imho, some higher level issues like
> Consistency --- do it the same everywhere (as opposed to memcpy vs  
> strcpy; one is source, dest and the other is dest,src.... grrrr)
>
> Also keep it intuitive ... e.g., most people will the 1st arg is  
> always the start, so don't have the normal mode be xxxx(start,len)  
> and then have a 1-argument variant of xxxx(len) with the buffer  
> start being an implied 1st argument.
>
> And .. having implied additional optional arguments is something  
> I've discouraged in the past.  Typing the additional arguments is no  
> problem
> It stresses the "do you really want xxx for argument n?" In the  
> programmers mind .... as well as the "we really mean xxxx!" In the  
> mind of the next programmer to read the code
>
> Frank

That raises an interesting question.  Currently I have a couple of
ways to create a Buffer( not the segment ), and they are inconsistent:

b  = Buffer( 1024 ) -- empty Buffer with 1024 bytes
s  = 'This is some example content'
b1 = Buffer( s )    -- buffer filled with s, #b1 == #s
b2 = Buffer( b1 )   -- buffer filled with content of b1; #b1 == #b2
                     -- this is a true clone, not a reference
sg = b1:Segment( 1, 5) -- Segment[1:5]; #sg == 5
b3 = Buffer( sg )   -- buffer with content of sg, #b3 == #sg
                     -- true clone of content

b4 = Buffer(14, b1) -- buffer with first 14 bytes from b1

As you can see the arguments are inconsistent;
either (number, content) or (number) or (content)
Any ideas how to reconcile that?

- Tobias


Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tobias Kieslich
In reply to this post by Russell Haley

Quoting Russell Haley <[hidden email]>:

> b:Segment() - I would think this would be 0 to the length of the buffer.
>
> I like the :next() and :shift() ideas, but that would mean the module
> would need internal state on what the size of the 'chuck' would be?
> Perhaps:
>
> cs = 256
> s4 = b:Segment(128,cs):next(cs)
>
> or if state IS desired:
>
> b.def_chuck_size = 256
> s4 = b:Segment(128):next()
State is not an issue, the instances are already stateful.  I'm going
ahead with Egors ideas :-)

-Tobias




Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tobias Kieslich
In reply to this post by Lourival Vieira Neto

Quoting Lourival Vieira Neto <[hidden email]>:

> Hi Tobias,
>
>> I'm designing a library which has a Buffer module that allows to contain
>> binary data.
>
> Perhaps you would like to take a look at Luadata [1].
>
> [1] https://github.com/lneto/luadata
>
> Regards,
> --
> Lourival Vieira Neto

That looks interesting. Very different approach from what I'm trying to
provide but similar functionality. I like it!

-Tobias



Reply | Threaded
Open this post in threaded view
|

Re: API Design

Frank Kastenholz-2
In reply to this post by Tobias Kieslich
On 9/18/17 6:34 PM, [hidden email] wrote:

>
> Quoting Frank Kastenholz <[hidden email]>:
>
>> Hi
>>
>> Xxxx(start, Len)
>> Or
>> Xxxx(start, end)
>>
>> Are what I personally like
>>
>> Or subscripts ala buffer[start,end] :-)
>>
>> More important than that are, imho, some higher level issues like
>> Consistency --- do it the same everywhere (as opposed to memcpy vs
>> strcpy; one is source, dest and the other is dest,src.... grrrr)
>>
>> Also keep it intuitive ... e.g., most people will the 1st arg is
>> always the start, so don't have the normal mode be xxxx(start,len)
>> and then have a 1-argument variant of xxxx(len) with the buffer start
>> being an implied 1st argument.
>>
>> And .. having implied additional optional arguments is something I've
>> discouraged in the past.  Typing the additional arguments is no problem
>> It stresses the "do you really want xxx for argument n?" In the
>> programmers mind .... as well as the "we really mean xxxx!" In the
>> mind of the next programmer to read the code
>>
>> Frank
>
> That raises an interesting question.  Currently I have a couple of
> ways to create a Buffer( not the segment ), and they are inconsistent:
>
> b  = Buffer( 1024 ) -- empty Buffer with 1024 bytes
> s  = 'This is some example content'
> b1 = Buffer( s )    -- buffer filled with s, #b1 == #s
> b2 = Buffer( b1 )   -- buffer filled with content of b1; #b1 == #b2
>                     -- this is a true clone, not a reference
> sg = b1:Segment( 1, 5) -- Segment[1:5]; #sg == 5
> b3 = Buffer( sg )   -- buffer with content of sg, #b3 == #sg
>                     -- true clone of content
I do not see a problem with these -- they all do the same thing (create
a new buffer) and use something else to initialize it. it is sort of
the difference between using floats or integers for your "b=Buffer(1024)"
call.
> b4 = Buffer(14, b1) -- buffer with first 14 bytes from b1
Normally I'd say that since you can do this the same as your b3
example, it doesn't deserve a special case or form of API.
On the other hand, I can imagine your buffers might get very
big -- so you could optimize the segmenting and subsetting of
the buffer, saving some copies, etc, that might not otherwise
be needed.

As a meta-(meta-point) I allow myself to break my own "rules"
if I've given the matter a lot of thought and there is a
really really good reason/benefit to do so.

On the other hand, I may just be trying to rationalize my own
irrational behavior.


Frank


Reply | Threaded
Open this post in threaded view
|

Re: API Design

Hugo Musso Gualandi
In reply to this post by Tobias Kieslich
>
> As you can see the arguments are inconsistent;
> either (number, content) or (number) or (content)
> Any ideas how to reconcile that?
>

In cases like this I like having a different constructor for each case:

    b1 = Buffer.new(1024)
    b2 = Buffer.fromString("hello world")
    b3 = Buffer.fromBuffer(b2, 14)

One advantage of separating the functions is that if you have a type
error in your code and pass the wrong value to the constructor you get
an error immediately instead of the program silently doing the wrong
thing.

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tomás Guisasola-2
In reply to this post by Tobias Kieslich
Hi Tobias

> That raises an interesting question.  Currently I have a couple of
> ways to create a Buffer( not the segment ), and they are inconsistent:
>
> b  = Buffer( 1024 ) -- empty Buffer with 1024 bytes
> s  = 'This is some example content'
> b1 = Buffer( s )    -- buffer filled with s, #b1 == #s
> b2 = Buffer( b1 )   -- buffer filled with content of b1; #b1 == #b2
>                     -- this is a true clone, not a reference
If it is a clone, why don't you provide a method for it:

b2 = b1:clone()

> sg = b1:Segment( 1, 5) -- Segment[1:5]; #sg == 5
> b3 = Buffer( sg )   -- buffer with content of sg, #b3 == #sg
>                     -- true clone of content
Since b3 is not a Segment, it is not a clone, isn't it?  I would call it
differently:

b3 = sg:buffer() -- or sg:newbuffer() or something else...

> b4 = Buffer(14, b1) -- buffer with first 14 bytes from b1
This is another clone isn't it?  Then name it clone!

b4 = b1:clonefirst (14) -- clone b1 from 1 to 14
b5 = b1:clonefrom (14) -- clone b1 from 14 to the end

> As you can see the arguments are inconsistent;
> either (number, content) or (number) or (content)
> Any ideas how to reconcile that?
b6 = b1:clone (init, end) -- This seems to be more general; default: (1,
#b1)

Regards,
Tomás

Reply | Threaded
Open this post in threaded view
|

Re: API Design

Tobias Kieslich
In reply to this post by Sean Conner

Quoting Sean Conner <[hidden email]>:

> It was thus said that the Great [hidden email] once stated:
>>
>> The main reason why I have a buffer module is not just for mutable network
>> buffers.  I do have a binary packer/parser that works on Buffer,Segments
>> and Lua strings alike, however, when you pass it a buffer it can do simple
>> writes which makes it really convenient:
>
>   We have a difference of approach.  I tend to convert raw binary data into
> native Lua data (usually a table of fields) as soon as possible, do all the
> manipulations using straight Lua, then pack everything back up into raw
> binary data.
>
>   -spc

While t.Pack can do more it could assist with that approach:
> Pack   = require( "t.Pack" )
> Buffer = require( "t.Buffer" )
> binary = "WhatEver is in the PayLOad..."
> p = Pack( ">I6<i4c11d" )  -- "Sequence" Packer
> t = p( binary)
> print( #binary, Pack.getSize( p ), t[1], t[2], t[3], t[4] )
29      29      96105823225206  1763734117      s in the Pa      
3.0343664813352e-86
> -- or named ( those have to be single table key/val pairs to preserve order )
> p1 = Pack( {a = ">I6"}, {b = "<i4"}, {c = "c11"}, {d = "d"} )
> o1 = p1( binary )
> print( o1 )    -- OrderedHashTable is provided in the library
t.OrderedHashTable: 0x55774a1f28c0
> for k,v in pairs(o1) do print(k,v) end  -- iterates in guaranteed  
> ordered fashion
a       96105823225206
b       1763734117
c       s in the Pa
d       3.0343664813352e-86

I don't have yet a Constructor which allows to generate a Packed buffer/string
from an ordered table but it's somewhere on the roadmap ...

- Tobias


12