Pull to refresh

Comments 30

  • PUT /admin/articles — создать статью.
  • POST /admin/articles — обновить статью.

Вроде должно быть наоборот POST — создание, PUT — обновление
По аналогии с
GET /articles/:id — отдельная статья.

обновление статьи тоже должно быть по id
PUT /admin/articles:id — обновить статью.
Как то не очень компактно и понятно.

Вот memory cache REST server на Erlang:

{deps, [ {rest,  ".*", {git, "git://github.com/synrc/rest", "HEAD"}}]}


-module(users).
    -behaviour(rest).
    -compile({parse_transform, rest}).
    -include("users.hrl").
    -export(?REST_API).
    -rest_record(user).

    init() -> ets:new(users, [public, named_table, {keypos, #user.id}]).
    populate(Users) -> ets:insert(users, Users).
    exists(Id) -> ets:member(users, wf:to_list(Id)).
    get() -> ets:tab2list(users).
    get(Id) -> [User] = ets:lookup(users, wf:to_list(Id)), User.
    delete(Id) -> ets:delete(users, wf:to_list(Id)).
    post(#user{} = User) -> ets:insert(users, User);
    post(Data) -> post(from_json(Data, #user{}))


Использовать так:

curl -i -X POST -d "id=vlad" localhost:8000/rest/users
    curl -i -X POST -d "id=doxtop" localhost:8000/rest/users
    curl -i -X GET localhost:8000/rest/users
    curl -i -X PUT -d "id=5HT" localhost:8000/rest/users/vlad
    curl -i -X GET localhost:8000/rest/users/5HT
    curl -i -X DELETE localhost:8000/rest/users/5HT


23K RPS
Стоит отметить что за этой компактностью стоит несколько скрытых файлов/либ:

0) Собственно сам REST endpoint для Erlang web-фреймворка N2O
220 строк кода (cloc output) =>
➜  rest cloc ./src 
       5 text files.
       5 unique files.                              
       1 file ignored.

http://cloc.sourceforge.net v 1.62  T=0.02 s (167.8 files/s, 11284.5 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Erlang                           4             47              2            220
-------------------------------------------------------------------------------
SUM:                             4             47              2            220
-------------------------------------------------------------------------------


1) Макроса REST_API уже нет,
вместо
-export(?REST_API). 

это
-export([init/0, populate/1, exists/1, get/0, get/1, post/1, delete/1]).

2) Файл c users.hrl еще 13 строк кода
users.hrl
-record(user, {id, name, email, proplist = [{facebook, udefined},
                                            {github, "github.com/b0oh"},
                                            {local, undefined},
                                            {twitter, udefined}],
               string = "common",
               number = 12,
               list_of_strings = ["one", "two", "three"],
               list_of_numbers = [34958726345, 12],
               nested_proplists = [{nested, [{number, 12},
                                             {string, "common"},
                                             {list_of_strings, ["one", "two", "three"]},
                                             {list_of_atoms, [one, two, three]},
                                             {list_of_numbers, [100000, 2,3 ]}]}]}).


3) и последнее, это wf:to_list — вызов конвертации из соответствующего модуля, еще 178 строк кода.

Немного больше чем написал 5HT изначально, но все равно меньше чем у автора.
Да уж, Erlang с Cowboy сервером тут просто «рулят и бибкают». Но Erlang для этого и создавался…
сравнивать производительность сервиса работающего с erts и сервиса работающего с mariadb это нормально?
А где написано, что я сравинивал производительность? Я привел цифру для приведенного кода.
Переключишь с ETS на KVS — цифра изменится, но код не изменится.
Я вообще думаю, что ненормально приводить DB throughput в статье про веб фремворк.
Но если для Habrahabr это ок, то пусть будет :-)
А ну ясно. Про странность сравнения согласен. Хотя в общем-то можно показать сколько накладных расходов вносит web framework… Не в курсе, что OK для Habrahabr, что нет…
У меня все показано в README сколько N2O вносит по сравнению с чистым web server.
Там отдельно цифры с сессиями, без сессий, с шаблонами, без шаблонов и т.д.
И мои цифры можно проверить, так как тесты выложены github.com/maxlapshin/fpbenchmark
stateless-сервиса очень удобно использовать Basic-аутентификацию

видимо имелось в виду для stateful, опечатка наверное
stateless конечно. Stateful был бы, если запонимал sessionid.
Спасибо за статью. Если позволите, пара моих постов по связанной теме: раз, два, три, четыре и далее по ссылкам.

По своему опыту скажу, что для написания REST'ов и микросервисов Haskell просто отлично подходит уже сегодня. Можно еще прикрутить blaze-html и писать веб спокойно.
И вам спасибо за блог eax.me, регулярно его читаю и нахожу очень полезным.
String — список обычных ASCII-символов, восьмибитных, естественно. Этот тип данных встроен в язык.

Это не совсем так
String = [Char]
-- sizeOf ('x' :: Char) == 4

Просто это всё же обычный список со всеми вытекающими, но юникод в нём помещается.
У нас недавно на работе устраивали двухдневный хакатон. Каждый должен был выбрать язык программирования, которым он не владеет и за два дня написать программу: считать из бинарного файла данные и кое-что там посчитать, основываясь на этих данных. Я выбрал хаскель. Ничего из этого не вышло. Даже не получилось считать данные из файла)
Но язык, конечно, интересный.
это конечно не очень хорошо с моей стороны, но позвольте поинтересоваться, была ли у вас на хакатоне возможность обращаться к книгам, интернету, или общаться с сообществами языков. Просто уж за два то (рабочих) дня при наличии обучаеющего человека можно получить очень неслабое введение в язык, вплоть до того, что потом быть способным работать в команде, в которой есть 1-2 хорошо разбирающихся человека.
Я тоже так думал, что смогу разобраться за два дня. В итоге не получилось. Сейчас мне кажется, что все, что вы написали применимо к обычным, си-подобным языкам.
Можно было пользоваться чем угодно. У нас даже есть человек, который знает хаскель. Но обращаться к нему, я решил, что не спортивно.
Если это пример для начинающих изучать haskell — то человек все равно ничего не поймет. Поскольку объяснены либо очевидные вещи, либо ненужные.

Если это пример для человека, который умеет писать на данном языке — то тут нет ничего интересного.
И почитайте про линзы, вам поможет многое легче писать, и многие теоретические вещи понять (а оттуда и легкость в практике).

И да, зачем типы писать в `makeDbConfig`?
Но в целом, если это было написано после не столько продолжительного изучения haskell — то клево, рад видеть в наших рядах ;)
Бенчмарки хорошо бы тоже куда-нибудь выложить…
> ghc-options: -O3 -threaded -N32

это очень странная идея:

1. `-O3` все же нету, максимальный уровень оптимизации это `-O2`, но, в целом нужен ли он
тоже нужно смотреть, т.к. из-за очень большого инлайна он может работать даже хуже, чем
`-O`, что правда случается редко

2. `-N32` у вас 32 ядерный ноутбук? Если нет, то используйте или `-N` — количество системных
процессов в rts равное количеству ядер, или даже чуть меньше, в противном случае можно
получить существенное замедление. Тоже нужно смотреть, что лучше на бенчмарках

3. Установка минимального размера для выделения памяти `-A` тоже может сильно помочь,
как уже было указано выше.

ну и есть пакет ghc-gc-tune, который тоже можно использовать, чтобы подобрать параметры
gc, дающие максимальную производительность для данной программы.
> Text — тип данных, предназначенный как для ASCII, так и для UTF-символов. Находится в библиотеке text и существует в двух видах: строгой и ленивой. Подробнее — здесь

Тип данных предназначенный для хранения текстовой информации внутри программы. В Text хранятся UTF-16, т.е. не может представить все множество представимое типом Char. Является unpinned, т.е. данные могут перемещаться сборщиком мусора. Поддерживает stream-fusion, т.е. операции поддерживающие stream fusion объеденятся в одну мегаоперацию, которая не создает промежуточных структур. Т.е., например, `T.replace «a» «ab». T.replace «c» «de»` не будет создавать промежуточную строку между операциями replace (требует хотя бы -O). Но при этом не поддерживает эффективные блочные операции.

> ByteString — предназначен для сериализации строк в поток байтов. Поставляется в библиотеке bytestring и также в двух вариантах: строгом и ленивом.

Очень неточное высказываение. Тип данных ByteString предназначен для представления бинарных данных, IO взаимодействий и взаимодействий с внешними функциями (FFI). ByteString представлен массивом байтов (Word8). ByteString является pinned, т.е. гарантировано не будет перемещен сборщиком мусора (что может приводить к фрагментации памяти). Не поддерживает stream-fusion в полной мере. Может использовать блочные операции. В новых версиях есть ShortByteString являющийся unpinned.

Как-то так.
Что значит восклицательный знак перед типами данных в AuthSettings и зачем он нужен?
Сделать поле строгим. Т.е. если данные вычисляются то WHNF (weak head normal form), то и поля отмеченные строгими вычисляются до WHNF. Помогает избежать излишних отложенных вычислений в структурах данных и является общим правилом (уже практически принятым с сообществе).
Спасибо, а можете привести несколько примеров, когда он точно нужен, и без него нельзя, когда он точно не нужен, и с ним нельзя, и когда всё равно, с ним или без?
уже ниже привел пример, чтобы лучше понять как оно работает. Существует достаточно простое правило:
если вы знаете, что вам нужна ленивость в поле, т.е. это как-то используется алгоритмами — то не делайте его строгим, в противном случае — делайте
.

Данное правило не совсем хорошее, поскольку на каком-то этапе можно упустить случаи, когда ленивость поможет.

Примеры структур, где нужна ленивость, например структуры использующие для построения завязывание в узел или результаты из «будущего», структуры для мемоизации, структуры в которых хранятся значения большая часть из которых может быть не вычислена, бесконечные структуры. При этом вычисления на ленивых стуктурах проще объединяются (compose)

Примеры структур, где ленивость вредна, элементараные структуры, которые могут накапливать вычисления, например:
`foldl' (\(x, y) c -> (x+c, y*c)) (0,1) [...]` несмотря на то, что свертка строгая в «полях» кортежа будет накапливаться вычисления и вычеслены будут только вконце.

Учитывая сказанное выше, я бы сказал, что строкими нужно делать поля во всяких пользотельстких структурах, на которых не строится control flow.
Ниже пример, поясняющий разницу:

data A = A Int
data B = B Int
data C = C ![Int]

mk constr conv = const . conv

Далее в интерпретаторе:

> let a = mk A id 7
> :sprint a
a = _  -- у нас thunk вместо значения
> (\x@(A _) -> True) a -- вычисляем до WHNF
True
> :sprint a
a = A _  -- конструктор вычислен в поле Thunk
> let b = mk B id 8
> :sprint b
b = _
> (\x@(B _) -> True) b
True
> :sprint b
b = B 8 -- конструктор вычислен и поле тоже вычислено
*Main> let c = mk C id (replicate 7 9)
*Main> (\x@(C _) -> True) c
True
*Main> :sprint c
c = C (9 : _)   -- как видим поле вычислено только до WHNF
Sign up to leave a comment.

Articles