netch80: (bird)
[personal profile] netch80

Сегодня мы представляем яркого представителя NoSQL мира, kdb+ - "time series database" системы "колоночник", популярную в кругах алгоритмического трейдинга и околобиржевого процессинга. Имеет встроенные языки, называемые q (более человеческий, по публичному утверждению авторов) и k (сами догадаетесь). 32-битная версия бесплатна. 64-битная версия продаётся, по слухам, за 50000$ за рабочее место. (Я ничего такого не слышал и не знаю, где эти слухи ходят! Все имена и цифры выдуманы.) Посмотрим, какой фантастический функционал предлагают за эти, не боюсь откровенного слова, смешные деньги.

Пример кода на q:

ld:{if[not type key L::`$(-10_string L),string x;.[L;();:;()]];i::j::
  -11!(-2;L);if[0<type i;-2 (string L),
  " is a corrupt log. Truncate to length ",(string last i)," and restart";exit 1];
  hopen L};

Эта система - для настоящих профессионалов, которые ценят свои вложенные усилия и хотят получить достойную оплату своего труда. Хотя кое-что не соблюдено: по ней печатают книги. Боюсь, зря: надо было ограничиться курсами.

Начнём с простого. Чему равно 3+4*5? Калькулятор Microsoft Windows в чайниковском режиме скажет 35, в инженерном - 23. Проверяем:

q)3+4*5
23

Кажется, что всё как обычно? Переместим умножение.

q)3*4+5
27

Вы ожидали 17 в любом случае? Вы ничего не понимаете. Надо думать как "ненаучный" режим, но считать справа налево, а не слева направо. Привыкайте к миру высоких биржевых технологий, где ask это продажа, а bid это покупка. Скобки - ваш друг:

q)(3*4)+5
17

Кстати, тут круглые скобки. Но не обольщайтесь! В отличие от ламерских языков навроде Python и Javascript, для вызова функции нужны [квадратные; скобки], а для записи списка (круглые;скобки); аргументы, естественно, разделяются не запятой, а точкой с запятой:

q)a:{x+y}
q)a[3;4]
7
q)last `a`b`c
`c

Но это не единственный вариант. Вообще, набор одного лишнего символа в программе в полевых условиях настоящего брокера, ведущего свои дела и в вокзальной уборной Пхеньяна, стоит 100$. Именно поэтому каждый пробел что-то меняет:

q)last `a`b`c
`c
q)last (`a`b`c)
`c
q)last (`a `b `c)
'b

Как сделать по-настоящему эффективный код? Для этого надо максимально переопределить функции, которые называть знаками препинания. Вы сказали - Perl? Нет, они слабаки.

q)`a`b`c!1 2 3
a| 1
b| 2
c| 3

Это создали словарь. Кстати, чтобы было понятнее переходящим с других языков, ключи и значения не рядом. Нет, сначала все ключи, потом все значения.

А тут изменили ключ для таблицы (две первые колонки сделали primary key):

q)t:([]a:1 2 3;b:10 20 30;c:`x`y`z)
q)2!t
a b | c
----| -
1 10| x
2 20| y
3 30| z

А тут многоаргументная форма выполняет роль SQL операторов update и delete:

/ following is: delete from t where c2 = `y
![t;enlist (=;`c2; enlist `y);0b;`symbol$()]

Но настоящий профессионал этим не ограничится! Для начала, используем все значения данных, включая типизированный null:

With a 0N on the left hand side, returns the right hand side after
printing its unformatted text representation to console. This is useful for debugging,
or avoiding formatting which may obscure the data's structure.

Продолжим. Как вызывать внутренние функции, если не через тот же знак? У нас есть много интересного. Например: -11!(-2;x) - given a valid logfile, return the number of chunks. Или: −17!x - flip endian-ness of kdb+ data file x. Читать файл с другим порядком байт напрямую? Вы не профессионал. Это же так важно - развернуть 100TB файл данных на месте, зато потом быстро по нему бегать.

Когда-то настоящие профессионалы знали адреса всех переменных в памяти и одной командой POKE с отрицательными аргументами могли сделать то, что ламер, обчитавшийся Дейкстры, писал в 200 строк. Мы завидуем тем временам и стараемся возродить лучшие традиции.

Порядок аргументов тоже должен соответствовать наиболее естественным желаниям автора, а не понятности для всяких посторонних.

q)5_0 1 2 3 4 5 6 7 8      / drop the first 5 (leaving the last 4)
5 6 7 8
q)-5_0 1 2 3 4 5 6 7 8     / drop the last 5 (leaving the first 4)
0 1 2 3
q)0 1 2 3 4 5 6 7 8_5      / drop the 5th item (leaving everything else)
0 1 2 3 4 6 7 8

У всех целочисленных типов есть null value. Булевский - целочисленный:

q)2~2
1b
q)2~4
0b
q)2~2i
0b

В последнем примере, в терминах языка C, (long) 2 != (int) 2 (естественно!) Но главное, что null в boolean это true (все согласны, что null это не false?):

q)"b"$0
0b
q)"b"$1
1b
q)"b"$0N
1b

Использовать для вывода данных ту же форму, что для ввода - это несекьюрно! Надо конвертировать.

q)2
2
q)enlist 2
,2
q),2
',

Кстати, последняя строчка - это сообщение об ошибке. Лаконично и - понимающему достаточно, остальные пусть учатся. Кстати, часто ошибки более подробно описаны:

!
-11
(0;`:/home/user/data/sym2015.07.22)

с таким сообщением с проблемой мгновенно разберётся даже школьник.

Отрицательный тип - это скаляр, положительный - список. Но не тогда, когда задаёшь конверсию к типу - там нужен только положительный:

q)type 2
-7h
q)type enlist 2
7h
q)-7h$2h
'type
q)7h$2h
2

Но, если входное данное - строка, то нужен отрицательный, иначе будет получен код символа:

q)-7h$"2"
2
q)7h$"2"
50

Можно типы задавать также односимвольными строками (не путать с символами), но нельзя - целыми иного типа, чем short:

q)"J"$"2"
2
q)"j"$"2"
50
q)7$"2"
'type
q)-7$"2"
'type

Кстати о строках. Строка из одного символа - это строка и в то же время не строка:

q)string 2
,"2"
q)string 25
"25"

Правда же, просто и понятно? Следующий пример поможет закреплению:

q)"2"
"2"
q)type "2"
-10h
q)enlist "2"
,"2"
q)type enlist "2"
10h
q)"25"
"25"
q)type "25"
10h
q)enlist enlist "2"
,"2"
q)type enlist enlist "2"
0h

Если аргумент enlist является типизированным списком, он завернёт аргумент в общий список. На выводе это не отразится, потому что список из одного элемента - это то же самое, что сам этот элемент. Ну, почти всегда.

Почему "почти"? Вот есть такая функция inv, которая инвертирует матрицу. Проверим:

q)inv 10
0 1 2 3 4 5 6 7 8 9

Непохоже на инверсию матрицы 1x1? Всё правильно, оно сработало как til, а не inv. А всё потому, что и til, и inv - это обёртки вокруг k, где обе превращаются в !:, а дальше выбирается действие по типу аргумента.

В руководстве написано, что dyadic (это то, что у остальных binary) функции можно писать вызывать и как f[a;b], и как a f b. Давайте посмотрим, как это выглядит с определёнными пользователем функциями.

q)type (+)
102h
q)2+3
5
q)+[2;3]
5
q)type mod
k){x-y*x div y}[@:]
q)-13 mod 4
3
q)mod[-13;4]
3
q)f:+
q)type f
102h
q)f[2;3]
5
q)2 f 3
'type
q)f:{x+y}
q)f[2;3]
5
q)2 f 3
'type

2 f 3 - это не вызов функции, в отличие от 2+3 или 2 insert 3. Это список чисел (тип 6h), и функциям в нём не место. Вы спросите, почему не общий список (0h)? Если вы это спрашиваете, вы ещё не поняли всю суть сути глубин. В общем, вызывайте функционально, и незачем сбивать логику парсинга.

Внимательный читатель также мог заметить, что деление с остатком выполняется как F-деление, а не как T-деление. Долг в полтора доллара это долг в два доллара и депозит в 50 центов, очевидно. Нефиг тут со своими низкоуровневыми привычками приходить! Но это не означает право делить на отрицательное число, в отличие от языков для драйверов и GUI, система не пустит вас выполнить финансово недопустимую операцию:

q)13 mod -4
0N
q)-13 mod -4
0N

Значение, которое вернула операция - это null типа long, я его уже упоминал. kdb+ реализует избранные места из Language Independent Arithmetic, ибо это то, что жизенно необходимо для финансовых расчётов. Там даже целочисленная бесконечность есть (наверно, для отображения внутреннего долга США?) Но, если хотите, можно сделать её конечной, просто преобразовав к более широкому типу:

q)2147483647i
0Wi
q)"j"$0Wi
2147483647
q)"f"$0Wi
2.147484e+09

Или даже просто к другому типу:

q)"h"$0Wi
32767h

(Я, кажется, ошибся - это долг одной Одессы, штат Аризона.)

Наверно, поэтому унарного минуса вам не положено - кто ж меняет знак у числа? Это недопустимо, потому что депозит со временем сжимается, долг растёт, а де́бет активного счёта равен кре́диту пассивного:

q)-(-2)
'-

Но, если вы хотите странного, можете вызвать neg. Гарантия на вашу карму при этом потеряется! Исключений нет. Вы предупреждены.

Функции в q являются объектами первого класса. Вы думаете, type - это функция? Руководство говорит, что да. Проверим.

+ - это однозначно функция:

q)a:+
q)a
+
q)type a
102h
q)a[3;4]
7

А type - это функция? Сама про себя говорит, что нет, она - generic null.

q)a:type
q)type a
101h

Хотя она при этом выполняется как следует (хотя... кто это за нас тут решает?):

q)a[1]
-7h
q)a["abc"]
10h

и не так, как generic null:

q)a:(::)
q)a[1]
1
q)a["abc"]
"abc"

Так всё-таки, что такое type? Она уворачивается на ходу от нашего взгляда:

q)+
+
q)*
*
q)insert
insert
q)type
@:

Но применить в таком виде её, конечно, не получится:

q)@: 2
' 

Теперь попытаемся её применить к некоторым другим операторам...

q):
:
q)type :
:[@:]
q)type type :
@:[@:]
q)type type type :
@@:[@:]
q)type type type type :
@@@:[@:]
q)type type type type type :
@@@@:[@:]
q)type +
+[@:]
q)type type +
@+[@:]
q)type type type +
@@+[@:]

Интересно, правда?

generic null - это значение, это и функция. В q, всё есть функция. Попробуем испытать её суть:

q)type ::
@:

Но мы поступим по-ламерски - применим грубую силу:

q)type (::)
101h

Так виднее, но до ужаса примитивно и нагоняет зевоту. Поиграемся в длину?

q)type type ::
@@:
q)type type type ::
@@@:

Где-то мы это уже видели. А можно ещё так:

q)type :::
@:
q)type ::::
@:
q)type :::::
@:
q)type ::::::
@:
q)type type type ::::::
@@@:

Методы функционального программирования ценятся! Под своими особыми именами:

q){x*3} each (3 4 5)
9 12 15
q)(+) over 1 2 3 4 5
15

В корневом пространстве имён не может быть имени длины 0. В других - может:

q).foo.:`bar
q).foo.
`bar
q)`.foo[]
| bar
q).foo.foo:`bar2
q)`.foo[]
   | bar
foo| bar2

но находясь в его контексте - вы его по-простому не вызовете (синтаксически нет способа дать имя длины 0), надо вызывать с указанием пространства имён. "Не больно-то и хотелось", замечают с галёрки. Мы согласны.

Кстати, .z это не пространство имён, оно только притворяется:

q)`.z[]
'.z
q)key `.u
``init`del`sel`pub`add`sub`end`genv`ld`tick`endofday`ts`upd`t`w`d`l`L`j`i
q)key `.z
q)

несмотря на то, что в нём есть интересные штуки вроде текущего времени:

q).z.z
2015.07.22T08:21:32.033

при этом притворяется настолько эффективно, что в нём можно создавать свои секретные переменные:

q).z.aXY:120
q).z.aXY
120

И снова type и странный парсинг.

q)type .
.[@:]
q)type ..
.[@:].
Это не вызов type к результату, ибо
q)type[.]
102h
q)type[..]
'.

Но только ли type тут влияет? Похоже, нет.

q)2+.
+[2].
q)2-.
-[2].
q)type (2+.)
105h
q)2..
'2..
q)2 . .
.[2].

Думаю, вы уже осознали всю мощь средства композиции функций по этим примерам. Теперь, в отличие от героя Мольера, вы будете путаться в композициях функций сознательно.

Замыканий нет, зато есть карринг (в местных терминах - projection) по любым аргументам. В случае закрытия всех аргументов, карринг автоматически превращается в вызов:

q)a:{x-y}
q)b:a[3]
q)b
{x-y}[3]
q)type a
100h
q)type b
104h
q)b[1]
2
q)b[44]
-41
q)b:a[;14]
q)b
{x-y}[;14]
q)b[1]
-13
q)b[22]
8

Сколько вариантов apply вы знаете? Только . и @, да? А так?

q)a:{x+10}
q)0(`a;1)
11
q)0(`a;9)
19
q)a:{x+y+z}
q)0[(`a;1;2;3)]
6

Число как функция - это же просто очевидно. Правда, стандартные функции почему-то нельзя так применить... а зачем вам это, а? Кстати, чем этот apply отличается: он, в отличие от обычных, попадает в лог операций с данными. Да, я тут по секрету выдал, что ещё и лог такой есть. Только туда не попадёт тот, у кого доку́ментов нету.

q)a:type
q)0[(`a;1)]
-7h
q)0[(`neg;1)]
'neg

Только программист может знать, когда нужно позвать сборку мусора. Исполняющая система не берёт на себя такую ответственность! Так приятно наблюдать, как от вызова .Q.gc процесс освобождает пару сотен гигабайт за раз. Кстати, эта функция, поскольку в .Q, является внутренней и не предназначенной к использованию. Но вы её всё равно вызывайте, потому что больше некому. И ещё: -20!0 это то же самое, что .Q.gc[], подсказывает Капитан Очевидность. Доказательство: сумма символов кодов строки ".Q.gc" и кода '!' делится на 8.

Есть ли изменяемые таблицы на диске? Конечно, нет, это же непрофессионально. Профессионально - это 1TB RAM и запись таблицы на диск одним действием по концу биржевого дня. Ключи таблиц - для слабаков, которые не в состоянии оторваться от материнской соски SQL, поэтому set[] отказывается писать таблицы с ключом как splayed (когда каждая колонка в своём файле). Кстати, если хотите сохранить все таблицы по окончанию дня на диск, вызовите .Q.hdpf. Я ожидал .Q.nzdkptfhxy, но, видимо, она будет только в следующей версии.

Конечно же, надо сделать самые простые и надёжные правила записи комментариев в коде:

A line which has / as its first character and contains at least one other non-whitespace character is a whole-line comment and is ignored entirely.

A / on a line by itself begins a multiline comment which is terminated by the next \ on a line by itself.

A \ on a line by itself with no preceding matching / will comment to end of file.

If a / is not matched by a \, the multiline comment is unterminated and continues to end of file.

The / and \ must be the first char on the line, but may be followed by any amount of whitespace.

На самом деле вам подсунули значительно лучший мех! Пробелы перед '/' тоже допустимы и создают комментарий. Кстати, о пробелах. Вот так вот нельзя:

r:{ (x*y)+z
}

Система очень понятно опишет проблему:

k){0N!x y}
'{
@
"q"
"r:{ (x*y)+z"

А как можно? А вот так:

r:{ (x*y)+z
 }

Исключительно для вашего удобства, система запомнит определение функции со всеми пробелами. Кто сказал "Питон"? Выведите его из зала.

Чтобы открыть файл, его название надо задать символом (в далёком прошлом программирования, во всяких LISP, Ruby, Erlang, это звалось "атом"). Строка - это наивное детство, тут не применяется. Количество символов в системе ограничено, в отличие от строк? Ну так вы ж не Нортон Коммандер пишете. Кстати, если у вас удалённый хост называется "c", вы до него не доберётесь. Соблюдайте соглашения об именовании хостов. Что? URL?? Шесть лишних символов???

Но: если вам надо подгрузить файл кода, то нужна строка с именем файла, и то же для выполнения через eval.

Функция system зовёт или внутреннюю команду, такую, как загрузить файл на исполнение, установить параметры вроде порта для IPC, или команду ОС, если такой внутренней нет. Я думаю, никто не будет возражать, что такое совмещение функциональности логичнее всего. Если в ОС появятся команды с названиями типа w, тем хуже для ОС: что она такое по сравнению с нашим средством? Оболочка дешёвая...

Надеемся, что наше короткое введение, особенно ценное для пользователей таких хило-студенческих и бесперспективных языков, как C++ и Java, поможет вам войти в мир настоящих профессионалов программирования. Не надо бегать за курсами на Forex, ваш удел - непосредственно создавать колебания курсов!

Date: 2015-07-30 12:13 pm (UTC)
From: [identity profile] antimirov.livejournal.com
Википедия говорит про K:

"Advocates of the language emphasize its speed, facility in handling arrays, and expressive syntax."
Edited Date: 2015-07-30 12:14 pm (UTC)

Date: 2015-07-30 01:56 pm (UTC)
From: [identity profile] netch80.livejournal.com
Слово expressive тут недостаточно ёмко и полно выражает все чувства тех, кто пытается это сопровождать.

Date: 2015-07-31 11:34 am (UTC)
From: [identity profile] lionet.livejournal.com
Есть мнение, что в твоих словах присутствует некоторый элемент дружеского подтрунивания.

Date: 2015-07-31 09:05 pm (UTC)
From: [identity profile] netch80.livejournal.com
Если это от слова "труна", которое означает "гроб", то я в целом согласен.

Date: 2016-11-15 08:45 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
а "гроб" – который из "гроб гроб кладбище пидор"

Date: 2015-07-30 03:50 pm (UTC)
From: [identity profile] egorfine.livejournal.com
В этом после определенно не хватает [livejournal.com profile] sorhed.

Date: 2015-07-31 12:07 pm (UTC)
From: [identity profile] egorfine.livejournal.com
потому что он биржевик и любитель экзотических технологий.

Date: 2015-08-14 09:47 am (UTC)
From: [identity profile] netch80.livejournal.com
Он знает про это средство и явно не старается восхвалять.

Date: 2015-08-01 04:38 pm (UTC)
ext_605364: geg MOPO4 (Default)
From: [identity profile] gegmopo4.livejournal.com
Прекрасно! Промышленный брейнфак. И не говорите теперь, что язык <подставить ваш предмет хейтерства> имеет запутанный синтаксис и неочевидную семантику.

А можно спрятать часть поста под кат, пожалуйста?
Edited Date: 2015-08-01 04:40 pm (UTC)

Date: 2016-02-04 08:12 pm (UTC)
From: [identity profile] p1r4nh4.livejournal.com
Это ты как-то прямо совсем неслабо копнул, впечатляет.

Сделано в Канаде!

Date: 2016-02-04 08:56 pm (UTC)
From: [identity profile] lev.livejournal.com
спасибо, это прекрасно

Date: 2016-11-16 02:06 pm (UTC)
From: [identity profile] inv2004.livejournal.com
Отчасти многие описанные проблемы вызваны именно надстройкой-для-менеджеров-Q. Когда так как K имеет куда как более понятную и _простую_ структуру.

В Q есть небольшие проблемы с выводом и именами функций, которые являются просто вызывом K примитивов, если смотреть на них - то всё намного понятнее. Для избежания этого, я использую k - режим. Но правда коллеги в отделе меня не поддерживают и считают Q более понятным, у меня обратное мнение.
Edited Date: 2016-11-16 02:10 pm (UTC)

Date: 2016-11-16 03:15 pm (UTC)
From: [identity profile] netch80.livejournal.com
> Когда так как K имеет куда как более понятную и _простую_ структуру.

Всё зло растёт в первую очередь из K. Никуда не делись, например, изуверски перегруженные разным функционалом операторы.

> Но правда коллеги в отделе меня не поддерживают и считают Q более понятным, у меня обратное мнение.

Я согласен с ними. Безобразие не улучшается, но хотя бы можно его понятнее представить.

Date: 2016-11-17 11:21 am (UTC)
From: [identity profile] inv2004.livejournal.com
тут можно вернуться к вечной дискуссии - "понятнее кому? тем кто не разбирается в языке и не может запомнит 2x20 оператора? тем, у кого у кого набита рука - не вызывает проблем написать * вместо first или +/"

Profile

netch80: (Default)
netch80

April 2017

S M T W T F S
      1
234 5678
9101112131415
1617 181920 2122
23242526272829
30      

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 27th, 2017 10:29 am
Powered by Dreamwidth Studios