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


Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение