Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

Rebol pickling recipes ?

 [1/21] from: jason:cunliffe:verizon at: 3-Oct-2002 20:50


I am playing around with little Rebol database objects on my server. Want to use them for simple login/password use. But also some metadata to be included with file upload over http. After a few days with Python I am wondering how to 'pickle' Rebol objects? http://www.python.org/doc/current/lib/module-pickle.html <quote> 3.14 pickle -- Python object serialization The pickle module implements a fundamental, but powerful algorithm for serializing and de-serializing a Python object structure. ``Pickling'' is the process whereby a Python object hierarchy is converted into a byte stream, and ``unpickling'' is the inverse operation, whereby a byte stream is converted back into an object hierarchy. Pickling (and unpickling) is alternatively known as ``serialization'', ``marshalling,''3.2 or ``flattening'', however the preferred term used here is ``pickling'' and ``unpickling'' to avoid confusing. </quote> A related question is how to compress and decompress these for obvious security reasons. I know Vanilla uses objects a lot for its dynasnips. I am interested to send Rebol pickles over http via forms as part a very simple, granular, low-tech distributed persistence experiment. I might be barking up the wrong paradigm tree. Or you lot may already be light years ahead me.. Anyway, like most things in Rebol its fun to try, and something good may come of it :-) thanks ./Jason

 [2/21] from: greggirwin:mindspring at: 3-Oct-2002 21:49


Hi Jason, << After a few days with Python I am wondering how to 'pickle' Rebol objects? >> I guess that depends on exactly what your needs are. With REBOL, you have a number of options. You can MOLD objects and then write them out, you can write out just the spec block for them, you can serialize things out as blocks - or any data really - even if they aren't objects. This is one of those things that is so simple in REBOL, it makes you forget how hard it is in other languages (i.e. that they have to provide serialization mechanisms which were probably a lot of work for them to write :). The great benefit of code/data duality shines bright. SAVE and MOLD both have an /ALL refinement now which creates a "true" serialized format.
>> o: make object! [a: 1 b: none c: [zippy--pinhead--com] d: false] >> mold o
== { make object! [ a: 1 b: none c: [zippy--pinhead--com] d: false ]}
>> mold/all o
== { #[object! [ a: 1 b: #[none] c: #[email! "[zippy--pinhead--com]"] d: #[false] ]]}
>> o2: do load mold o >> probe o2
make object! [ a: 1 b: none c: [zippy--pinhead--com] d: false ]
>> o2: load mold/all o >> probe o2
make object! [ a: 1 b: none c: [zippy--pinhead--com] d: false ] --Gregg

 [3/21] from: al:bri:xtra at: 4-Oct-2002 20:13


Jason wrote:
> ...I am wondering how to 'pickle' Rebol objects?
I've got two functions that work with the older Rebol releases, that flatten and "restore" an object "sea". They're called 'Freeze and 'Melt. They correctly save and restore cyclic or net-like Rebol data structures. I haven't yet updated them for the latest Rebol beta versions. Andrew Martin ICQ: 26227169 http://valley.150m.com/ -><- -- Attached file included as plaintext by Listar -- -- File: Freeze.r Rebol [ Name: 'Freeze Title: "Freeze" File: %"Freeze.r" Author: "Andrew Martin" eMail: [Al--Bri--xtra--co--nz] Web: http://valley.150m.com Date: 3/July/2002 Version: 1.0.0 Purpose: {Freezes an object sea.} Category: [util db file 5] Acknowledgements: "Romano Paolo Tenca" ] make object! [ Magic: '. ; This must be the same as the 'Melt function! Find-Same: func [Series [series!] Value [any-type!]] [ while [ all [ found? Series: find/only/case Series :Value not same? first Series :Value ] ][ Series: next Series ] Series ] Freeze-Value: function [Sea [block!] Fish] [Path Value Index] [ if all [ not lit-path? :Fish not path? :Fish any [ function? :Fish object? :Fish series? :Fish ] ] [ Path: make path! reduce [Magic] Value: either series? :Fish [head :Fish] [:Fish] either found? Index: Find-Same Sea :Value [ Index: index? Index ] [ append/only Sea :Value Index: length? Sea ] append :Path Index if all [ series? :Fish 1 < Index: index? Fish ] [ append/only :Path Index ] Fish: :Path ] :Fish ] set 'Freeze function ["Freezes Object Sea" Sea [block!]] [Block Object] [ foreach Fish Sea [ switch type?/word :Fish [ block! [ Block: Fish forall Block [ Block/1: Freeze-Value Sea pick Block 1 ] ] object! [ Object: Fish foreach Word next first Object [ set in Object Word Freeze-Value Sea get in Object Word ] ] ] ] Sea ; At this point, the 'Sea has become ice. :) ] ] -- Attached file included as plaintext by Listar -- -- File: Melt.r Rebol [ Name: 'Melt Title: "Melt" File: %"Melt.r" Author: "Andrew Martin" eMail: [Al--Bri--xtra--co--nz] Web: http://valley.150m.com Date: 3/July/2002/21:35 Version: 1.0.1 Purpose: {Melts object ice into fluid Rebol script.} Category: [util script file db 5] Acknowledgements: "Romano Paolo Tenca" ] make object! [ Magic: '. ; This must be the same as the 'Freeze function! Melt-Value: function [Ice [block!] Path] [Value] [ Value: :Path if all [ path? :Path Magic = first :Path 2 <= length? :Path integer? second :Path ] [ Value: pick Ice second :Path if all [ 3 = length? :Path integer? third :Path ] [ Value: at Value third :Path ] ] Value ] set 'Melt function ["Melts Object Ice" Ice [block!]] [Rule Value] [ parse Ice Rule: [ any [ Value: path! ( Value/1: Melt-Value Ice Value/1 ) | into Rule | any-type! ] end ] Ice ; At this point, the 'Ice has become sea. :) ] ]

 [4/21] from: laurent:giroud:libertysurf at: 4-Oct-2002 23:03


> << After a few days with Python I am wondering how to 'pickle' Rebol
objects? >>>
> I guess that depends on exactly what your needs are. With REBOL, you have a > number of options. You can MOLD objects and then write them out, you can
<<quoted lines omitted: 6>>
> SAVE and MOLD both have an /ALL refinement now which creates a "true" > serialized format.
One recent thread on the list insisted on the fact that nothing in rebol can be evaluated correctly outside of its original context (if I understood correctly). So reading your answer I wonder if this applies to objects printed with 'mold ? Is it possible for such objects to contain a different content when loaded back into a different context than the one they were "saved" from ? And if this possible context "confusion" exists is there any way to make sure that objects are saved along with all the necessary context ? Note that I am not writing any code related to the subject, I am just curious ;) Also I did not manage to see what was exactly the difference beetween doing "do load mold/all" and simply doing "do load mold" in your example. Both seems to produce the same result, so what benefit does the /all exactly bring in that case ? Best regards, Laurent

 [5/21] from: anton:lexicon at: 5-Oct-2002 11:50


The difference is that datatype information is lost sometimes. See: type? load mold none ; == word! type? load mold/all none ; == none! mold converted none to a string! "none", then load converted it to a word! 'none. mold/all converted none to a special new syntax which load understands and which preserves the datatype information: "#[none]" Now when that is loaded you get the true rebol value that you started with: none, which has datatype none! Other values also have the same problem, and are solved in the same way. The /all refinement of mold is only available in the latest betas, and in Rebol/Base. Anton.

 [6/21] from: jason:cunliffe:verizon at: 4-Oct-2002 16:50


Andrew Martin wrote:
> I've got two functions that work with the older Rebol releases, that > "flatten" and "restore" an object "sea". They're called 'Freeze and 'Melt. > They correctly save and restore cyclic or net-like Rebol data structures. > > I haven't yet updated them for the latest Rebol beta versions.
Thanks very much Andrew :-) I'd like to hear when/where/how you use these yourself.. ./Jason

 [7/21] from: g:santilli:tiscalinet:it at: 5-Oct-2002 12:12


Hi Laurent, On Friday, October 4, 2002, 11:03:04 PM, you wrote: LG> So reading your answer I wonder if this applies to objects printed with 'mold ? LG> Is it possible for such objects to contain a different content when loaded back LG> into a different context than the one they were "saved" from ? Yes, it is. LG> And if this possible context "confusion" exists is there any way to make sure LG> that objects are saved along with all the necessary context ? Well, with a lot of effort, you could do it, but you'd probably end up MOLDing the whole system. LG> Also I did not manage to see what was exactly the difference beetween doing "do LG> load mold/all" and simply doing "do load mold" in your example. The difference is between LOAD MOLD/ALL and DO MOLD (i.e. you need to evaluate in the latter). Also, consider the following:
>> b: reduce [make object! [a: 1 b: 2] true none make hash! [1 2 3 4]]
== [ make object! [ a: 1 b: 2 ] true none make hash! [1 2 3 4]]
>> length? b
== 4
>> foreach element b [print type? element]
object logic none hash
>> b': load mold b
== [ make object! [ a: 1 b: 2 ] true none make hash! [1 2 3 4]]
>> length? b'
== 8
>> foreach element b' [print type? element]
word word block word word word word block
>> b'': load mold/all b
== [ make object! [ a: 1 b: 2 ] true none make hash! [1 2 3 4]]
>> length? b''
== 4
>> foreach element b'' [print type? element]
object logic none hash HTH, Gabriele. -- Gabriele Santilli <[g--santilli--tiscalinet--it]> -- REBOL Programmer Amigan -- AGI L'Aquila -- REB: http://web.tiscali.it/rebol/index.r

 [8/21] from: laurent:giroud:libertysurf at: 5-Oct-2002 19:56


Hi Anton and Gabriele, thanks for your answers, this makes effectively much sense now. I'm glad I asked since I now have a better vision of how rebol handles data internally. It's hard to get a grasp on it at the beginning, making the difference beetween a word and its value, understanding that rebol code is no more than blocks and strings until it gets 'loaded and evaluated (via 'do). So I appreciate much to be able to see a bit further in that area, thanks again ! LG>> And if this possible context "confusion" exists is there any way to make sure LG>> that objects are saved along with all the necessary context ?
> Well, with a lot of effort, you could do it, but you'd probably > end up MOLDing the whole system.
Is it possible ? If I am right this would allow a kind of persistent rebol environment : 'molding system before quitting and 'loading it a few days later and finding the environment exactly in the same state. That could have many applications. Wouldn't it be a way to implement modules for a Rebol/HardBase version which would have nothing but native functions available ? -> Just 'loading carefully molded data containing only what's needed for a specific use. Would this be possible ? It would be interesting to know if the current "program counter" is part of the system data as this might allow running scripts to be restored at the exact point where they stopped, this would useful too in some cases. Do you think it's feasible ? Regards, Laurent -- Laurent Giroud

 [9/21] from: g:santilli:tiscalinet:it at: 5-Oct-2002 20:50


Hi Laurent, On Saturday, October 5, 2002, 7:56:50 PM, you wrote:
>> Well, with a lot of effort, you could do it, but you'd probably >> end up MOLDing the whole system.
LG> Is it possible ? You'd have to handle circular references by yourself. Also, preserving non-object contexts is not trivial and probably impossible in the general case. (Ladislav has some examples of how to list the words in a given context, but AFAIK they look for them in SYSTEM/WORDS, so if you have "loaded" the word without using LOAD they wouldn't be able to find it.) Another problem would be how to handle native functions (or actions etc.), that cannot be molded. LG> If I am right this would allow a kind of persistent rebol environment : 'molding LG> system before quitting and 'loading it a few days later and finding the LG> environment exactly in the same state. That could have many applications. That's what you get with FORTH. I don't think it is possible in REBOL in the general case, but you could do it "partially". ->> Just 'loading carefully molded data containing only what's needed for a LG> specific use. Would this be possible ? Well, that's what you do when you write a script --- i.e. you're writing carefully molded data. :-) LG> It would be interesting to know if the current "program counter" is part of the LG> system data as this might allow running scripts to be restored at the exact LG> point where they stopped, this would useful too in some cases. That is not available (as a lot of other internal state). Regards, Gabriele. -- Gabriele Santilli <[g--santilli--tiscalinet--it]> -- REBOL Programmer Amigan -- AGI L'Aquila -- REB: http://web.tiscali.it/rebol/index.r

 [10/21] from: laurent:giroud:libertysurf at: 5-Oct-2002 22:13


Hi Gabriele,
> You'd have to handle circular references by yourself. Also, > preserving non-object contexts is not trivial and probably
<<quoted lines omitted: 4>>
> how to handle native functions (or actions etc.), that cannot be > molded.
Each answer seem to bring up new questions again ;) What exactly do you mean by "loading" words without using 'load ? Do you mean that (for example) something like "test: 5" defines a word named test of type integer? and with value the number five and that this word is not stored in the system/ words but in an internal non accessible list of words ? LG>> If I am right this would allow a kind of persistent rebol environment : 'molding LG>> system before quitting and 'loading it a few days later and finding the LG>> environment exactly in the same state. That could have many applications.
> That's what you get with FORTH. I don't think it is possible in > REBOL in the general case, but you could do it "partially".
I guess that by "partially" you mean "only for the context that your script(s) have knowledge of" ? Would that mean that, for example, one would have to store all code and data within a single object to make sure everything he needs is correctly restored (puting aside the need to handle circular references properly) ? ->>> Just 'loading carefully molded data containing only what's needed for a LG>> specific use. Would this be possible ?
> Well, that's what you do when you write a script --- i.e. you're > writing carefully molded data. :-)
How could I not see it ??? I lack vitamins for sure ;) Regards, Laurent

 [11/21] from: lmecir:mbox:vol:cz at: 7-Oct-2002 9:21


Hi Laurent,
> What exactly do you mean by "loading" words without using 'load ?
As I see it, Gabriele meant, that you can make a word without using LOAD as follows: a-word: first make block! "xxxyyy" probe a-word ; xxxyyy The word we made this way cannot be found in REBOL/WORDS: in rebol/words a-word ; == none
> LG>> If I am right this would allow a kind of persistent rebol environment
: 'molding
> LG>> system before quitting and 'loading it a few days later and finding
the
> LG>> environment exactly in the same state. That could have many
applications.
> > That's what you get with FORTH. I don't think it is possible in > > REBOL in the general case, but you could do it "partially". > > I guess that by "partially" you mean "only for the context that your
script(s)
> have knowledge of" ? > Would that mean that, for example, one would have to store all code and
data
> within a single object to make sure everything he needs is correctly
restored
> (puting aside the need to handle circular references properly) ?
Not exactly. Let's see this: block1: reduce [a-word] ; == [xxxyyy] block2: compose/deep [[(a-word)]] ; == [[xxxyyy]] block3: reduce [first use block1 block2 first use block1 block2] set first block3 1 set second block3 2 Now we can explore the properties of BLOCK3: probe block3 ; == [xxxyyy xxxyyy] reduce block3 ; == [1 2] Let's suppose, that we just use the MOLD function to make BLOCK3 persistent. If we LOAD the result back, we don't get a block having the same properties as BLOCK3 and that is the trouble. Regards -Ladislav

 [12/21] from: g:santilli:tiscalinet:it at: 6-Oct-2002 12:05


Hi Laurent, On Saturday, October 5, 2002, 10:13:48 PM, you wrote: LG> Each answer seem to bring up new questions again ;) That's good, isn't it? :-) LG> What exactly do you mean by "loading" words without using 'load ? This is somewhat a long story; I'll try to make it short.
>> find first system/words 'a-word
== [a-word]
>> find first system/words 'another-word
== [another-word]
>> find first system/words 'a-word-never-defined-before
== [a-word-never-defined-before] You see, it looks like REBOL's SYSTEM/WORDS already contains all possible words. But actually, it doesn't. They just get added by LOAD, at the time the text you write at the console is LOADed into REBOL, just before it is evaluated. So:
>> load "a-different-word"
== a-different-word
>> 'a-different-word2
== a-different-word2
>> find first system/words 'a-different-word
== [a-different-word a-different-word2] LOAD adds 'A-DIFFERENT-WORD to SYSTEM/WORDS (you see, it gets added before 'A-DIFFERENT-WORD2). However, LOAD is not the only function that can "create" a word.
>> to-block "a block of words"
== [a block of words] It doesn't add the words to SYSTEM/WORDS:
>> find first system/words first to-block "different-again"
== none So no track remains about the fact that that word has been created. One could think of creating a context made of words that are not added to SYSTEM/WORDS; this means that if you don't have access to that context in some way (i.e. if it isn't an object, or the context of a function, etc.) it is not possible to say which words are part of that context. As a result, you cannot reproduce that context by simply looking at the SYSTEM. BTW, I just found out this curious behavior:
>> find first system/words to-word "never-defined-this"
== none
>> find first system/words to-word "never-defined-this"
== [never-defined-this] which shows that using TO-WORD the word gets added AFTER the evaluation of that expression. I cannot give an explanation of this; it might be some kind of side effect of the current implementation (this is REBOL/View 1.2.8.3.1 3-Aug-2002); Ladislav, Romano, do you have any ideas? LG> I guess that by "partially" you mean "only for the context that your script(s) LG> have knowledge of" ? Yup, something like that. :) LG> Would that mean that, for example, one would have to store all code and data LG> within a single object to make sure everything he needs is correctly restored LG> (puting aside the need to handle circular references properly) ? That could help. However, as long as you are in control of what it is needed to restore your system, you can easily do it. (I.e. if you know what state your script needs, you just save it and then restore it.) Regards, Gabriele. -- Gabriele Santilli <[g--santilli--tiscalinet--it]> -- REBOL Programmer Amigan -- AGI L'Aquila -- REB: http://web.tiscali.it/rebol/index.r

 [13/21] from: rotenca:telvia:it at: 6-Oct-2002 13:27


> BTW, I just found out this curious behavior: > >> find first system/words to-word "never-defined-this"
<<quoted lines omitted: 6>>
> implementation (this is REBOL/View 1.2.8.3.1 3-Aug-2002); > Ladislav, Romano, do you have any ideas?
Yes i agree, it is strange. It seems that the word is added to the context only after the current expression ends. The current expression can be a do block and very complex but the word appears loaded only at its end. The strange thing is that the word seems already binded to the global context, but first cannot visualize it. My first conclusions are: 1) first does not show all the words of the global context in every cases. 2) the object system/words is only a representation of the global context and it is updated only when the interpreter conclude an evaluation and before scanning the code block for the next instruction. How i already thought, adding new words to system/words is not a real necessity for Rebol it seems only a facility for the programmer. It seems the same in not beta release of View. --- Ciao Romano

 [14/21] from: rotenca:telvia:it at: 6-Oct-2002 13:34


Hi, Gabriele the situation about the global context makes me think that RT should give us a new function which can get the TRUE context of every word. Until today i thought that at least for the global context we had already it, but your discovery shows that it is false also for the global context. It seems that objects are not so close to contexts as I (we?) thought. --- Ciao Romano

 [15/21] from: laurent:giroud:libertysurf at: 6-Oct-2002 14:08


Hi Ladislav,
> Now we can explore the properties of BLOCK3: > probe block3 ; == [xxxyyy xxxyyy] > reduce block3 ; == [1 2] > Let's suppose, that we just use the MOLD function to make BLOCK3 persistent. > If we LOAD the result back, we don't get a block having the same properties > as BLOCK3 and that is the trouble.
Ok, I get it. The problem is that MOLD (as well as PROBE) does not preserve context binding information at all (MOLD/all does preserve a bit more of it but not all). One would need to have some kind of access to this information in order to preserve it, and that would imply to rewrite a modified MOLD from scratch. It is funny to see how this thread finally rejoins the other one on binding :) I guess that it shows how important this notion is to rebol. Thanks again ! Regards, Laurent -- Laurent Giroud

 [16/21] from: dockimbel:free at: 6-Oct-2002 15:43


Hi Gabriele, IIRC, Carl or Holger said once that words can exist in an unbounded state so that means that there're not even bounded to the global context. Let's create an unbounded word :
>> z: first to-block "unbound-word"
== unbound-word
>> :z
== unbound-word
>> get z
** Script Error: unbound-word is not defined in this context ** Near: get z This confirms that 'unbound-word is not bound to global context.
>> unbound-word: 1
== 1
>> get z
** Script Error: unbound-word is not defined in this context ** Near: get z The version created with 'to-block at the global context level lives out of contexts.
>> get x: to-word :z
== 1
>> get z
** Script Error: unbound-word is not defined in this context ** Near: get z
>> x
== unbound-word
>> get x
== 1
>> bind :z 'system
== unbound-word
>> get z
** Script Error: unbound-word is not defined in this context ** Near: get z 'to-word seems a good way to create a bounded word! from the unbounded one, but i see no way to bound the former one to any context... Now about the "curious behaviour", i guess that (first system/words) is evaluated before (to-word "never-defined-this"), so you get the word list before the new word is added to global context, that's why you get 'none the first time and get the word the second time.
>> z: to-word "never-defined-this9999" find first system/words z
== [never-defined-this9999] Regards, -DocKimbel. Gabriele Santilli wrote: [...]

 [17/21] from: lmecir:mbox:vol:cz at: 7-Oct-2002 16:29


Hi Gabriele,
> BTW, I just found out this curious behavior: > >> find first system/words to-word "never-defined-this"
<<quoted lines omitted: 6>>
> implementation (this is REBOL/View 1.2.8.3.1 3-Aug-2002); > Ladislav, Romano, do you have any ideas?
this is just an evaluation order artifact - FIRST SYSTEM/WORDS isn't a living thing , it is just a "freezed copy". You can get a normal behaviour, if you try this: in rebol/words to-word "never-defined-this" ; == never-defined-this , while: in rebol/words first to-block "never-defined-this-2" ; == none Cheers -L

 [18/21] from: rotenca:telvia:it at: 6-Oct-2002 17:17


Hi Doc,
> >> bind :z 'system > == unbound-word
<<quoted lines omitted: 3>>
> 'to-word seems a good way to create a bounded word! from the unbounded > one, but i see no way to bound the former one to any context...
I do not understand well. Bind take a word and change it to another word bound to an existing context, but only if that word already exist in the context. It can't add a new word to a context. This can be done only for the global context by Load and by make (any)word! There are 2 distint actions: 1) adding a word to a context, 2) bind a word to a context.
> Now about the "curious behaviour", i guess that (first system/words) is > evaluated before (to-word "never-defined-this"), so you get the word > list before the new word is added to global context, that's why you get > 'none the first time and get the word the second time.
You are right, it is only question of evaluation order. So we can be sure that an object is an exact reproduction of internal rebol context. :-) But i always want a get-context for use and function context. :-( --- Ciao Romano

 [19/21] from: g:santilli:tiscali:it at: 6-Oct-2002 21:07


>IIRC, Carl or Holger said once that words can exist in an unbounded >state so that means that there're not even bounded to the global >context.
Yes. That was what I was referring to. What was strange to me was TO-WORD, not TO-BLOCK.
>Now about the "curious behaviour", i guess that (first system/words) is >evaluated before (to-word "never-defined-this"), so you get the word >list before the new word is added to global context, that's why you get >'none the first time and get the word the second time.
That's probably true! I was not thinking about the fact that FIRST SYSTEM/WORDS now returns a copy of the list of words in the object, not the list itself as it was in early /Core 2.x releases. So, it looks like I've just been stupid. :-) If I had tried using LOAD too, I would have found the error immediately. Regards, Gabriele. __________________________________________________________________ Tiscali ADSL, l'offerta su misura per te a partire da 24,95 euro al mese! Scopri la tua adsl ideale con il TEST ON LINE http://point.tiscali.it/adsl/

 [20/21] from: g::santilli::tiscali::it at: 6-Oct-2002 20:58


My PC crashed today, dunno if it's the HD or the CPU... So, I'll be a little slow in reading the list during the following days...
>Until today i thought that at least for the global context we had already >it, >but your discovery shows that it is false also for the global context.
It could be a bug in TO-WORD, too.
>It seems that objects are not so close to contexts as I (we?) thought.
That is an interesting conclusion. But maybe, it is just that the way the global context is extended is a trick or so, and shows why they didn't add the same ability for other contexts. Also, SYSTEM/WORDS is not a real object after all... Regards, Gabriele. __________________________________________________________________ Tiscali ADSL, l'offerta su misura per te a partire da 24,95 euro al mese! Scopri la tua adsl ideale con il TEST ON LINE http://point.tiscali.it/adsl/

 [21/21] from: rotenca:telvia:it at: 6-Oct-2002 22:53


Hi Gabriele,
>>Until today i thought that at least for the global context we had already >>it, >>but your discovery shows that it is false also for the global context. >It could be a bug in TO-WORD, too.
Don't you think, now, that it was only the order of evaluation in that to-word example? Or there is something i do not understand? --- Ciao Romano

Notes
  • Quoted lines have been omitted from some messages.
    View the message alone to see the lines that have been omitted