una tener name longitud description descripcion debe cuantos content contador caracteres module namespaces code-organization rebol rebol3

module - tener - meta descripcion



¿Cómo se enlazan las palabras dentro de un módulo de Rebol? (1)

¡Entiendo que el module! ¡tipo proporciona una mejor estructura para espacios de nombres protegidos que object! o la ''use función de ''use . ¿Cómo se vinculan las palabras dentro del módulo? Veo algunos errores relacionados con las palabras no vinculadas:

REBOL [Type: ''module] set ''foo "Bar"

Además, ¿cómo distingue Rebol entre una palabra local y el módulo ( ''foo ) y el de una función del sistema ( ''set )?

Actualización menor, poco después:

Veo que hay un interruptor que cambia el método de enlace:

REBOL [Type: ''module Options: [isolate]] set ''foo "Bar"

¿Qué hace esto de manera diferente? ¿Qué problemas hay al usar este método por defecto?


OK, esto va a ser un poco complicado.

En Rebol 3 no existen las palabras del sistema, solo hay palabras. Algunas palabras se han agregado a la biblioteca de tiempo de ejecución lib , y set es una de esas palabras, que tiene una función asignada. Los módulos importan palabras de lib , aunque lo que significa "importación" depende de las opciones del módulo. Eso podría ser más complicado de lo que esperabas, así que déjame explicarte.

Módulos regulares

Para empezar, voy a ver qué significa importar para los módulos "regulares", los que no tienen ninguna opción especificada. Comencemos con tu primer módulo:

REBOL [Type: ''module] set ''foo "Bar"

En primer lugar, tiene una suposición errónea aquí: la palabra foo no es local para el módulo, es lo mismo que set . Si desea definir foo como una palabra local, tiene que usar el mismo método que con los objetos, use la palabra como palabra clave en el nivel superior, como esta:

REBOL [Type: ''module] foo: "Bar"

La única diferencia entre foo y set es que aún no has exportado ni añadido la palabra foo a lib . Cuando hace referencia a palabras en un módulo que no ha declarado como palabras locales, tiene que obtener sus valores y / o enlaces de algún lado. Para los módulos regulares, primero vincula el código a lib , a continuación, lo anula vinculando el código nuevamente al contexto local del módulo. Cualquier palabra definida en el contexto local estará vinculada a ella. Cualquier palabra no definida en el contexto local retendrá sus enlaces anteriores, en este caso lib . Eso es lo que significa "importar" para los módulos regulares.

En su primer ejemplo, suponiendo que no lo haya hecho usted mismo, la palabra foo no se agregó a la biblioteca de ejecución antes de tiempo. Eso significa que foo no estaba obligado a lib , y dado que no fue declarado como una palabra local, tampoco estaba vinculado al contexto local. Entonces, como resultado, foo no estaba obligado a nada en absoluto. En su código, eso fue un error, pero en otro código podría no serlo.

Módulos aislados

Hay una opción "aislar" que cambia la forma en que los módulos importan cosas, convirtiéndolo en un módulo "aislado". Usemos su segundo ejemplo aquí:

REBOL [Type: ''module Options: [isolate]] set ''foo "Bar"

Cuando se crea un módulo aislado, cada palabra del módulo, incluso en código anidado, se recopila en el contexto local del módulo. En este caso, significa que set y foo son palabras locales. Los valores iniciales de esas palabras se establecen en cualquier valor que tengan en lib en el momento en que se crea el módulo. Es decir, si las palabras están definidas en lib en absoluto. Si las palabras no tienen valores en lib , inicialmente tampoco tendrán valores en el módulo.

Es importante notar que esta importación de valores es algo único. Después de esa importación inicial, cualquier cambio en estas palabras hechas fuera del módulo no afecta las palabras en el módulo. Es por eso que decimos que el módulo está "aislado". En el caso de su ejemplo de código, significa que alguien podría cambiar lib/set y no afectaría su código.

Pero hay otro tipo de módulo importante que te perdiste ...

Guiones

En Rebol 3, los scripts son otro tipo de módulo. Aquí está su código como script:

REBOL [] set ''foo "Bar"

O si lo desea, dado que los encabezados de scripts son opcionales en Rebol 3:

set ''foo "Bar"

Los scripts también importan sus palabras de lib , y las importan en un contexto aislado, pero con un giro: todas las secuencias de comandos comparten el mismo contexto aislado, conocido como el contexto de "usuario". Esto significa que cuando cambie el valor de una palabra en un script, el siguiente script para usar esa palabra verá el cambio cuando comience. Entonces, si después de ejecutar el script anterior, intenta ejecutar este:

print foo

Luego imprimirá "Bar", en lugar de tener que ser indefinido, aunque foo aún no está definido en lib . Es posible que le resulte interesante saber que si usa Rebol 3 de forma interactiva, ingresa comandos en la consola y obtiene resultados, cada línea de comando que ingrese es un script separado. Entonces, si su sesión se ve así:

>> x: 1 == 1 >> print x 1

Las líneas x: 1 e print x son secuencias de comandos separadas, mientras que las segundas aprovechan los cambios realizados en el contexto del usuario.

El contexto del usuario se supone que es local de la tarea, pero por el momento ignoremos eso.

¿Por qué la diferencia?

Aquí es donde volvemos a la cuestión del "funcionamiento del sistema", y que Rebol no los tiene. La función de set es como cualquier otra función. Puede implementarse de manera diferente, pero sigue siendo un valor normal asignado a una palabra normal. Una aplicación tendrá que gestionar muchas de estas palabras, por eso tenemos módulos y la biblioteca de tiempo de ejecución.

En una aplicación habrá cosas que deben cambiar, y otras cosas que no deben cambiar, y qué cosas dependen de la aplicación. Deberá agrupar sus cosas, mantener las cosas organizadas o para tener control de acceso. Habrá cosas definidas a nivel mundial, y cosas definidas localmente, y deseará tener una forma organizada de llevar las cosas globales a los lugares locales, y viceversa, y resolver cualquier conflicto cuando más de una cosa quiera definir cosas con el mismo nombre.

En Rebol 3, usamos módulos para agrupar cosas, para mayor comodidad y control de acceso. Utilizamos la biblioteca de tiempo de ejecución lib como un lugar para recopilar las exportaciones de los módulos y resolver conflictos, a fin de controlar qué se importa a los lugares locales como otros módulos y el contexto del usuario. Si necesita anular algunas cosas, haga esto cambiando la biblioteca de tiempo de ejecución y, si es necesario, propagando sus cambios a los contextos del usuario. Incluso puede actualizar módulos en tiempo de ejecución y hacer que la nueva versión del módulo anule las palabras exportadas por la versión anterior.

Para módulos regulares, cuando las cosas se anulan o se actualizan, su módulo se beneficiará de dichos cambios. Suponiendo que esos cambios sean un beneficio, esto puede ser algo bueno. Un módulo regular coopera con otros módulos y scripts regulares para hacer que un entorno compartido funcione.

Sin embargo, a veces necesita mantenerse separado de este tipo de cambios. Tal vez necesites una versión particular de alguna función y no quieras actualizarla. Tal vez su módulo se cargará en un entorno menos confiable y no quiere que se piratee su código. Tal vez solo necesitas que las cosas sean más predecibles. En casos como este, es posible que desee aislar su módulo de este tipo de cambios externos.

La desventaja de estar aislado es que, si hay cambios en la biblioteca de tiempo de ejecución que pueda desear, no los obtendrá. Si su módulo es de alguna manera accesible (por ejemplo, al haber sido importado con un nombre), es posible que alguien le pueda propagar esos cambios, pero si no puede acceder a él, no tiene suerte. Con suerte, ha pensado en monitorear lib para ver los cambios que desea, o hacer referencia a las cosas a través de lib directamente.

Aún así, nos hemos perdido otro problema importante ...

Exportador

La otra parte de administrar la biblioteca de tiempo de ejecución y todos estos contextos locales es la exportación. Tienes que sacar tus cosas de alguna manera. Y el factor más importante es algo que no sospecharía: si su módulo tiene un nombre o no.

Los nombres son opcionales para los módulos de Rebol 3. Al principio, esto podría parecer simplemente una forma de simplificar la escritura de módulos (y en la propuesta original de Carl, esa es exactamente la razón). Sin embargo, resulta que hay muchas cosas que puedes hacer cuando tienes un nombre que no puedes cuando no lo haces, simplemente por lo que es un nombre: una forma de referirse a algo. Si no tiene un nombre, no tiene forma de referirse a algo.

Puede parecer algo trivial, pero aquí hay algunas cosas que un nombre te permite hacer:

  • Puede decir si un módulo está cargado.
  • Puede asegurarse de que un módulo solo se cargue una vez.
  • Puede decir si una versión anterior de un módulo estaba allí antes, y tal vez actualizarlo.
  • Puede obtener acceso a un módulo que se cargó anteriormente.

Cuando Carl decidió hacer nombres opcionales, nos dio una situación en la que sería posible crear módulos para los que no podrías hacer ninguna de esas cosas. Dado que las exportaciones de módulos estaban destinadas a ser recopiladas y organizadas en la biblioteca de tiempo de ejecución, tuvimos una situación en la que podía tener efectos en la biblioteca que no podía detectar fácilmente, y módulos que se recargaban cada vez que se importaban.

Por lo tanto, para mayor seguridad, decidimos cortar por completo la biblioteca de tiempo de ejecución y exportar las palabras de estos módulos sin nombre directamente a los contextos locales (módulo o usuario) que los estaban importando. Esto hace que estos módulos sean efectivamente privados, como si fueran propiedad de los contextos de destino. Tomamos una situación potencialmente incómoda y la convertimos en una característica.

Fue una característica tal que decidimos apoyarlo explícitamente con una opción private . Hacer de esto una opción explícita nos ayuda a lidiar con el último problema que no nos causó un nombre: hacer que los módulos privados no tengan que recargarse una y otra vez. Si le da un nombre a un módulo, sus exportaciones aún pueden ser privadas, pero solo necesita una copia de lo que está exportando.

Sin embargo, nombrado o no, privado o no, son 3 tipos de exportación.

Módulos con nombres regulares

Tomemos este módulo:

REBOL [type: module name: foo] export bar: 1

Importar esto agrega un módulo a la lista de módulos cargados, con la versión predeterminada de 0.0.0, y exporta una bar palabras a la biblioteca de tiempo de ejecución. "Exportar" en este caso significa agregar una bar palabras a la biblioteca de tiempo de ejecución si no está allí, y establecer esa palabra lib/bar en el valor que la palabra foo/bar tiene después de que foo ha terminado de ejecutarse (si no es establecido ya).

Vale la pena señalar que esta exportación automática ocurre solo una vez, cuando el cuerpo de foo termina de ejecutarse. Si realiza un cambio en foo/bar después de eso, eso no afecta lib/bar . Si también desea cambiar la lib/bar , debe hacerlo manualmente.

También vale la pena señalar que si lib/bar ya existe antes de que se importe foo , no se agregará otra palabra. Y si lib/bar ya está configurado en un valor (no desactivado), la importación de foo no sobrescribirá el valor existente. Primero en llegar, primero servido. Si desea anular un valor existente de lib/bar , tendrá que hacerlo manualmente. Así es como usamos lib para administrar sobrescrituras.

Las principales ventajas que nos brinda la biblioteca de tiempo de ejecución es que podemos administrar todas nuestras palabras exportadas en un solo lugar, resolviendo conflictos y anulaciones. Sin embargo, otra ventaja es que la mayoría de los módulos y scripts en realidad no tienen que decir lo que están importando. Siempre que la biblioteca de tiempo de ejecución se complete correctamente con todas las palabras que necesita, su secuencia de comandos o módulo que cargue más adelante estará bien. Esto hace que sea más fácil poner un montón de declaraciones de importación y cualquier anulación en el código de inicio que configura todo lo que necesitará el resto del código. Esto tiene como objetivo facilitar la organización y la escritura del código de la aplicación.

Nombrados módulos privados

En algunos casos, no desea exportar sus cosas a la biblioteca principal en tiempo de ejecución. Stuff in lib se importa en todo, por lo que solo debe exportar material a lib que quiera que esté disponible en general. A veces querrás crear módulos que solo exporten material para los contextos que lo deseen. A veces tiene algunos módulos relacionados, una instalación general y un módulo de utilidad más o menos. Si este es el caso, es posible que desee crear un módulo privado.

Tomemos este módulo:

REBOL [type: module name: foo options: [private]] export bar: 1

La importación de este módulo no afecta lib . En cambio, sus exportaciones se recopilan en una biblioteca de tiempo de ejecución privada que es local para el módulo o contexto de usuario que está importando este módulo, junto con los de cualquier otro módulo privado que el destino está importando, y luego se importa al destino desde allí. La biblioteca de tiempo de ejecución privada se usa para la misma resolución de conflicto para la que se utiliza lib . La biblioteca de ejecución principal lib tiene prioridad sobre la biblioteca privada, por lo tanto, no confíe en que la lib privada anule las cosas globales.

Este tipo de cosas es útil para crear módulos de utilidad, API avanzadas u otros trucos similares. También es útil para crear un código sólido modular que requiera importaciones explícitas, si eso es lo que le interesa.

Vale la pena señalar que si su módulo no exporta en realidad nada, no hay diferencia entre un módulo privado nombrado o un módulo público nombrado, por lo que básicamente se trata como público. Todo lo que importa es que tiene un nombre. Lo que nos lleva a ...

Módulos sin nombre

Como se explicó anteriormente, si su módulo no tiene un nombre, entonces debe tratarse como privado. Sin embargo, más que privado, ya que no puedes saber si está cargado, no puedes actualizarlo o incluso no volver a cargarlo. Pero, ¿y si eso es lo que quieres?

En algunos casos, realmente desea que su código se ejecute para el efecto. En estos casos, hacer que su código vuelva a ejecutar cada vez es lo que desea hacer. Tal vez es un guión con el que se está ejecutando do pero estructurarlo como un módulo para evitar la filtración de palabras. Tal vez está haciendo una mixin, algunas funciones de utilidad que tienen algún estado local que necesita inicialización. Podría ser casi cualquier cosa.

Frecuentemente hago que mi archivo %rebol.r un módulo sin nombre porque quiero tener más control sobre lo que exporta y cómo. Además, como está hecho para el efecto y no necesita ser recargado o actualizado, no tiene sentido darle un nombre.

No es necesario un ejemplo de código, los anteriores actuarán de esta manera.

Espero que esto te brinde una visión general suficiente del diseño del sistema de módulos de R3.