URBI - введение в urbiScript - часть 2

1. введение в urbiScript — часть 1
2. введение в urbiScript — часть 2

Продолжим рассмотрение возможностей скриптового языка urbiScript от компании Gostai, предназначенного для управления роботами.

Напомню, что urbiScript выполняется на Urbi-сервере, который запускается либо на роботе, либо на компьютере и к которому можно подключиться через сеть простым telnet-ом.

Взаимодействие с аппаратной частью робота осуществляется через объекты UObject, которые выполняют роль драйверов (прослойка между Urbi и аппаратной частью робота).
Данные драйвера реализуются в виде библиотек (.dll), подключаются к серверу Urbi и далее могут прозрачным образом использоваться в urbiScript (вызывать функции, изменять переменные).

9. Объекты
Все используемые в urbiScript сущности являются объектами (и переменные определённые через var и строки).
Объект в urbiScript строится на простой концепции — список слотов.
Слот, в данном случае, — это значение связанное с именем.
Т.о. объект — это список названий слотов, каждое из которых индексирует значение (как словарь).

// Создаём объект с двумя слотами
class Foo
{
	var a = 42;
	var b = "foo";
};
[00000000] Foo


Чтобы получить список названий слотов объекта можно воспользоваться методом localSlotNames:
// посмотрим список слотов
Foo.localSlotNames;
[00000000] ["a", "asFoo", "b", "type"]


Значение пременной объекта можно получить, обратившись к ней через точку (.)
// зная имя слота, можно посмотреть его содержимое
Foo.a;
[00000000] 42
Foo.b;
[00000000] "foo"


Так же есть метод inspect, который распечатывает более подробную информацию о структуре объекта:
Foo.inspect;
[00000009] *** Inspecting Foo
[00000010] *** ** Prototypes:
[00000011] *** Object
[00000012] *** ** Local Slots:
[00000014] *** a : Float
[00000015] *** asFoo : Code
[00000016] *** b : String
[00000013] *** type : String


Все объекты urbiScript имеют своим родителем Object.

// содане переменной o как нового объекта
var o = Object.clone;
[00000000] Object_0x00000000
// посмотрим его содержимое
o.inspect;
[00006725] *** Inspecting Object_0x00000000
[00006725] *** ** Prototypes:
[00006726] *** Object
[00006726] *** ** Local Slots:


Получили пустой объект.
Обратите внимание, что объект выводится, как Object_ и далее следует шестнадцатеричное число — это уникальный идентификатор (есть у каждого urbiScript-объекта).

Приведённому выше пустому объекту можно динамически создать слоты, используя метод
setSlot, который в качестве параметров принимает название слота и его значение:

o.setSlot("a", 42);
[00000000] 42
o.inspect;
[00009837] *** Inspecting Object_0x00000000
[00009837] *** ** Prototypes:
[00009837] *** Object
[00009838] *** ** Local Slots:
[00009838] *** a : Float


выше создан слот a в котором хранится 42. Создать слот можно и другим способом — используя ключевое слово var:

// эквивалентно: o.setSlot("b", "foo").
var o.b = "foo";
[00000000] "foo"
o.inspect;
[00072678] *** Inspecting Object_0x00000000
[00072678] *** ** Prototypes:
[00072679] *** Object
[00072679] *** ** Local Slots:
[00072679] *** a : Float
[00072680] *** b : String


получить слот можно используя метод getSlot, который в качестве параметра принимает название слота.
Используя setSlot() и getSlot() можно динамически работать со слотами объекта.

function set(object, name, value)
{
	// используем setSlot с динамическим названием слота
	return object.setSlot("x_" + name, value);
} |;
function get(object, name)
{
	// используем getSlot с динамическим названием слота
	return object.getSlot("x_" + name);
} |;
var x = Object.clone;
[00000000] Object_0x00000000
set(x, "foo", 0);
[00000000] 0
set(x, "bar", 1);
[00000000] 1
x.localSlotNames;
[00000000] ["x_bar", "x_foo"]
get(x, "foo");
[00000000] 0
get(x, "bar");
[00000000] 1

Как не трудно догадаться чтобы изменить содержимое слота существует метод updateSlot, который принимает 2 параметра — название слота и новое значение:

o.a;
[00000000] 42
o.updateSlot("a", 51);
[00000000] 51
o.a;
[00000000] 51


так же несложно догадаться, что можно поступить намного проще и использовать оператор =

o.b;
[00000000] "foo"
// эквивалентно: o.updateSlot("b", "bar")
o.b = "bar";
[00000000] "bar"
o.b;
[00000000] "bar"


Но здесь необходимо знать название слота, а updateSlot() позволяет изменить слот, название которого генерируется динамически.
Обратите внимание, что попытка создать слот с одинаковым именем вернёт ошибку:

var o.c = 0;
[00000000] 0
// нельзя переопределять слот
var o.c = 1;
[00000000:error] !!! slot redefinition: c
// изменение значения ошибку не вызовет
o.c = 1;
[00000000] 1


И разумеется слот можно удалить, используя метод removeSlot, который принимает в качестве параметра название слота:

o.localSlotNames;
[00000000] ["a", "b", "c"]
o.removeSlot("c");
[00000000] Object_0x00000000
o.localSlotNames;
[00000000] ["a", "b"]


Остаётся заметить, что попытки чтения, обновления, удаления слотов, которых не существует в объекте вернут ошибку:

o.d;
[00000000:error] !!! lookup failed: d
o.d = 0;
[00000000:error] !!! lookup failed: d


10. Методы

Метод в urbiScript — это слот объекта, содержащий функцию.
Дотуп к методу осуществляется так же через точку "."
Внутри метода можно использовать ключевое слово this, которое как и в С++ даст доступ к текущему объекту.

var o = Object.clone;
[00000000] Object_0x0
// сохраняем функцию 'f', как слот объекта 'o'.
function o.f ()
{
	echo("This is f with target " + this);
	return 42;
} |;
// получим содержимое слота f
o.getSlot("f");
[00000001] function () {
	echo("This is f with target ".'+'(this));
	return 42;
}
// вызовем метод
o.f;
[00000000] *** This is f with target Object_0x0
[00000000] 42
// можно и с круглыми скобками
o.f();
[00000000] *** This is f with target Object_0x0
[00000000] 42


Получается, что т.к. метод — это всего-лишь слот объекта, а вызов метода не требует обязательного наличия круглых скобок, то методом можно замещать аттрибуты объекта без изменения интерфейса объекта.
Чтобы узнать что же содержит слот — можно использовать метод getSlot:

//  метод 'empty' возвращает true если строка пустая
"foo".empty;
[00000000] false
"".empty;
[00000000] true
"".getSlot("empty");
[00000000] Primitive_0xf20488
// посмотрим другой метод
"".getSlot("asList");
[00000000] function () {
split("")
}
"foo".size;
[00000000] 3
"foo".getSlot("size");
[00000000] Primitive_0x0


как видим метод asList — это метод-обёртка, который вызывает функцию split()
А методы empty и size — это методы-примитивы — функции реализованные на С++.

11. Модель переменных в urbiScript
У каждого объекта в urbiScript есть свой уникальный идентификатор. А так как все сущности urbiScript — это объекты, то и идентификатор есть у всех сущностей. Посмотреть его можно через метод uid:

var o = Object.clone;
[00000000] Object_0x100000
o.uid;
[00000000] "0x100000"
42.uid;
[00000000] "0x200000"
42.uid;
[00000000] "0x300000" 


Как видно, введение целочисленного значения 42 порождает два отдельных объекта.

Чтобы продолжить разбираться далее нужно вспомнить полезные операторы сравнения:
== возвращает симантическое равенство (сравнивается величины (содержимое объектов))
=== возвращает физическое равенство (т.е. если два объекта имеют одинаковый uid):

var a = 42;
[00000000] 42
var b = 42;
[00000000] 42
a == b;
[00000000] true
a === b;
[00000000] false


Используя эти операторы можно легко убедиться, что в urbiScript при определении переменной или слота не происходит копирование значения (как в С или С++), а происходит связывание с уже существующим значением (как в Ruby или Java).

var a = 42;
[00000000] 42
var b = 42;
[00000000] 42
var c = a; // c связывается(указывает) с тем же объектом, что и a.
[00000000] 42
// a, b и c - равны: они имеют одинаково значение
a == b && a == c;
[00000000] true
// Но только a и c - фактически один и тот же объект
a === b;
[00000000] false
a === c;
[00000000] true


Получается, что a и c указывают на один и тот же объект(число 42) и модификации a затронут и c:

a.localSlotNames;
[00000000] []
b.localSlotNames;
[00000000] []
c.localSlotNames;
[00000000] []
var a.flag; // создание слота в a.
a.localSlotNames;
[00000000] ["flag"]
b.localSlotNames;
[00000000] []
c.localSlotNames;
[00000000] ["flag"]


Однако, обновление слота или локальной переменной не вызовет обновления связанного значения.

var a = 42;
[00000000] 42
var b = a;
[00000000] 42
// b и a указывают на число 42
a === b;
[00000000] true
// Обновление b не скажется на связанном значении 42,
// а просто b теперь будет ссылаться на число 51.
b = 51;
[00000000] 51
// Поэтому a остаётся неизменным:
a;
[00000000] 42


Понимание двух последних примеров очень важно!
На аргументы функций и методов так же можно ссылаться и модифицировать:

function test(arg)
{
	var arg.flag; // добавляется слот аргументу arg
	echo(arg.uid); // печатаем идентификатор uid
} |;
var x = Object.clone;
[00000000] Object_0x1
x.uid;
[00000000] "0x1"
test(x);
[00000000] *** 0x1
x.localSlotNames;
[00000000] ["flag"]


Однако, нужно очень внимательно подходить к такому изменению аргументов, т.к. поведение может быть не таким, как вы ожидаете:

function test(arg)
{
	// обновляем локальную переменную arg,
	// но это никак не сказывается на связанном аргументе
	arg = 1;
} |;
var x = 0;
[00000000] 0
test(x);
[00000000] 1
// x не модифицируется
x;
[00000000] 0


12. Операторы if/while/for/switch
Операторы if/while/for аналогичны одноимённым операторам в С/С++.

12.1 if

if (true)
	echo("ok");
[00000000] *** ok
if (false)
	echo("ko")
else
	echo("ok");
[00000000] *** ok


можно использовать внутри выражений:

echo({ if (false) "a" else "b" });


12.2 while

var x = 2;
[00000000] 2
while (x < 40)
{
  x += 10;
  echo(x);
};
[00000000] *** 12
[00000000] *** 22
[00000000] *** 32
[00000000] *** 42


12.3 for

for (var x = 2; x < 40; x += 10)
	echo(x);
[00000000] *** 2
[00000000] *** 12
[00000000] *** 22
[00000000] *** 32


другой вариант использования for — передавать ему коллекцию переменных в виде в виде списка.
Т.е. сначала идёт for, затем var, идентификатор, двоеточие или in и выражение:

for (var e : [1, 2, 3]) { echo(e) }; //for (var e in [1, 2, 3]) { echo(e) };
[00000000] *** 1
[00000000] *** 2
[00000000] *** 3


12.4 break и continue

как и в С++ внутри циклов for и while можно использовать ключевые слова break и continue:
break — выполняет остановку цикла (переход за границу цикла)
continue — запускает следующую итерацию цикла.

var i = 5|
for (; true; echo(i))
{
	if (i > 8)
		break;
	i++;
};
[00000000] *** 6
[00000000] *** 7
[00000000] *** 8
[00000000] *** 9

for (var i = 0; i < 8; i++)
{
	if (i % 2 != 0) // если число нечётное 
		continue;
	echo(i);
};
[00000000] *** 0
[00000000] *** 2
[00000000] *** 4
[00000000] *** 6


12.5 switch

Синтаксис оператора switch аналогичен С/С++, но в urbiScript он работает с любыми типами объектов, а не только с целочисленными. Сравнение аналогично оператору ==.
Так же не требуется использовать break;

switch ("bar")
{
case "foo": 0;
case "bar": 1;
case "baz": 2;
case "qux": 3;
};
[00000000] 1


12.6 do

оператор do позволяет выполнить сразу несколько действий над объектом.

Например, можно написать так:

var o1 = Object.clone;
[00000000] Object_0x100000
var o1.one = 1;
[00000000] 1
var o1.two = 2;
[00000000] 2
echo(o1.uid);
[00000000] *** 0x100000


, а можно то же самое реализовать через оператор do:

var o2 = Object.clone;
[00000000] Object_0x100000
// все сообщения в данной области предназначены объекту o2
do (o2)
{
	var one = 1; // var - простой вызов setSlot
	var two = 2;
	echo(uid);
};
[00000000] *** 0x100000
[00000000] Object_0x100000


12.7 loop

ключевое слово loop по смыслу эквивалентно бесконечному циклу while(true)

{
	var n = 10|;
	var res = []|;
	loop;
	{
		n--;
		res << n;
		if (n == 0)
			break
	};
	res
}
==
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0];


13. Области и выражения
Области в urbiScript — это выражения и поэтому они могут быть использованы там, где ожидается выражение:

// области выполняются до последнего выражения внутри:
{ 1; 2; 3};
[00000000] 3
// область - это выражение
echo({1; 2; 3});
[00000000] *** 3


Внутри области можно использовать внутренние переменные:

var x = 0; // Define the outer x.
[00000000] 0
{
	var x = 1; // определяется внутренняя переменная x.
	x = 2; // изменение значения
	echo(x); // внутренней переменной x
};
[00000000] *** 2
x; // внешний x не затрагивался
[00000000] 0
{
	x = 3; // а вот это уже внешний x.
	echo(x);
};
[00000000] *** 3
x;
[00000000] 3


14. Локальные функции
Функции могут быть определены в любом месте программы. Если они определяются внутри области, то и доступ к ним может быть только в данной области:

function max3(a, b, c) // максимальная из 3 переменных
{
	function max2(a, b)
	{
		if (a > b)
			return a
		else
			return b
	};
	max2(a, max2(b, c));
} | {};
max3(2,4,1); // проверим
[00000000] 4


15. Замыкания
Замыкание — это захват функцией переменной внешней функции. urbiScript поддерживает замыкание, т.о. функция может ссылаться на внешнюю переменную пока та видна (в области).

function printSalaries(rate)
{
	var charges = 100;
	function computeSalary(hours)
	{
		// rate и charges захватываются
		// из внешней функции
		rate * hours - charges
	};
	echo("Alice's salary is " + computeSalary(35));
	echo("Bob's salary is " + computeSalary(30));
} | {};
printSalaries(15);
[00000000] *** Alice's salary is 425
[00000000] *** Bob's salary is 350


Замыкание так же позволяет изменять внешние переменные:
var a = 0;
[00000000] 0
var b = 0;
[00000000] 0
function add(n)
{
	// x и y обновляются замыканием
	a += n;
	b += n;
	void
} | {};
add(25);
add(25);
add(1);
a;
[00000000] 51
b;
[00000000] 51


NB!
Стоит обратить внимание, что urbiScript испольует автоматический сборщик мусора(automatic garbage collection). Т.е. при создании нового объекта можно не заботиться о последующей очистке памяти. urbiScript подсчитывает количество ссылок на объект и в случае когда этот счётчик достигает нуля — объект удаляется.

{
	var x = List.new; // новый объект списка
	x << 42;
};
// за пределами области этого объекта уже нет.


Однако, как отмечается в документации, это свойство не является частью языка и автоматическая сборка мусора в будущем может быть изменена. Поэтому не стоит полагаться на текущее поведение сборщика мусора или детерминизм времени удаления объекта.
К тому же, текущий алгоритм уборки мусора не удалит объекты, если они ссылаются друг на друга.

Читать далее: введение в urbiScript — часть 3
  • +1
  • 31 августа 2010, 13:54
  • noonv

Комментарии (0)

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.