← All posts tagged Haskell

Посоветуйте, кто пользовался, хороший LRU для хаскеля (или как искать). При том, что скорее всего придётся немного допилить его API под конкретную нужду (а может взять и свой небольшой написать?).

Паттерн использования такой: идёт поток ивентов, среди них нужно учитывать только последний по какому-то id-полю. То есть пока я думал сделать LRU-кеш, переписывая по этому самому id-полю значения, при удалении из LRU считать объект "конечным", также при окончании ивентов всё, что внутри LRU считать конечным.

Вопрос к читавшим Functional Data Structures Окасаки. Вы читали вникая в доказательства или нет? То есть, я сейчас прочитал до места, где сложность очередей доказали через Banker's и Physicist Method'ы, и осознал, что либо мне нужно перечитывать всё с самого начала (а перед этим желательно почитать много разных примеров попроще по доказательству при помощи амортизации), либо забить и читать доказательства поверхностно, а внимательно читать только код.

Драйвер редиса возвращает `Redis (Either Reply a)`. При операции `HGET` возвращает, соответственно, `Redis (Either Reply (Maybe ByteString))`, типа значения по ключу может и не быть. Программист для упрощения пишет сначала специальную функцию `redis'`, которая заворачивает ответ редиса в EitherT-based монаду, чтоб соединять подобные вычисления и возвращать первую неудачу:

```
newtype Redis' a = Redis' (EitherT Redis.Reply Redis.Redis a) deriving (MonadIO)
instance Monad Redis' where
    return = Redis' . EitherT . return . Right
    (Redis' m) >>= f = Redis' $ m >>= \rv -> unwrapRedis' $ f rv
redis' = Redis' . EitherT
```

Таким образом, теперь, если вы получаете ответ `Redis (Either Reply a)`, вы его преобразуете в

```
Redis' (EitherT Reply Redis a)
```

и можете соединять подобные вычисления в do-блоке типа:

```
do
    res <- redis' $ Redis.hget foo bar
    res2 <- redis' $ Redis.hget baz zab
```

и вычисление остановится на первом возврате ошибки.

Далее. Программисту необходимо по кучке значений сделать `HGET` и вернуть это как-то внутри кортежа, поскольку мы внутри новой монады `Redis'` -- завернуть результат в неё. В случае, если значения хоть по одному ключу не существует, хочется вернуть `Nothing` для всех. Потому создаётся новая монада:

```
newtype HashFields a = HashFields (MaybeT Redis' a)
    deriving (Functor, Monad)
instance Applicative HashFields where
    pure = return
    (<*>) = ap
```

описывающая вычисления типа `Redis'`, которые могут вернуть неудачу. Пишется новая функция

```
hashField = HashFields . MaybeT . redis'
```

способная завернуть результат неудачи в новую монаду, которая умеет останавливаться на первой неудаче. Также пишется специальная функция для `HGET`:

```
getField :: ByteString -- ^ Key
         -> ByteString -- ^ Hash field name
         -> HashFields String
getField key field = fmap toString $ hashField $ Redis.hget key field
```

Также напишем функцию, которая "запустит" наше вычисление:

```
getRedisFields :: forall a. HashFields a -> Redis' (Maybe a)
getRedisFields (HashFields f) = runMaybeT f
```

И теперь лёгким движением руки мы можем сделать что-то вроде:

```
getRedisFields ((,,,,,) <$>
    getField k "foo" <*>
    getField k "bar" <*>
    getField k "baz" <*>
    getField k "zab" <*>
    getField k "rab" <*>
    getField k "oof")
```

Вопрос: вам не кажется это "слишком"? Я пока еще не настолько просто манипулирую типами в голове, чтоб ощутить всю ситуацию, хорошо хоть в целом могу медленно прости по шагам по коду, но есть ощущение, что что-то здесь не так.

Крайне нравится "Type Inference and Optimization for an Impure World" (правда, я пока где-то 70 из 240 страниц осилил). Рассказ о хаскеле-подобном языке Disciple, отличающимся наличием изменяемых переменных и их аннотациями в виде "регионов" и "эффектов" на уровне типов (тоже с выводом).

cs.anu.edu.au

И почему людям приходит в голову делать API по типу hedis (драйвер для редиса)? У них на каждую операцию ответ вида `Redis (Either Response a)`, где Response — это такая смесь, которая заодно может содержать ошибку (`Response = Error | MultiResponse | ...`). Ну естественно, нормальному человеку лень писать вокруг этого обвязку (точнее, "она уже в планах, уж теперь-то точно"). В результате вот такой ошибки вида "оно не вставляется" я и занимался получасовым дебагом, блин (причем, непонятно, баг ли это в хедисе или где-то у меня).

Проект, в котором я с недавнего времени работаю, ищет еще одного haskell-программиста. Суть проекта: native advertisement [0] (на сайте есть ролик), на хаскеле в нём написан веб-сервер, раздающий рекламу (js-файл), а также разные вспомагательные сервисы. Также часть сервисов написана на Python/Flask, но ими будут заниматься отдельные люди.

О зарплатном диапазоне не знаю. Могу предположить диапазон $30-$60 долларов в час в зависимости от опыта и навыков (при работе через oDesk), но лучше узнавать у самого работодателя. Находится он, кстати, в США, потому существует не очень удобная разница во времени. В остальном, как по мне — работа прекрасна.

Если кто-то заинтересован — шлите мне свои детали на k-bx@k-bx.com вместе со ссылками на гитхаб, резюме и прочие вспомагательные материалы. Спасибо!

[0]: thoughtleadr.com

О да! Песочницы кабала в случае неразрешения конфликтов предлагают выставить `--max-backjumps`, и это даже работает! В результате отложил план по обновлению библиотек на еще попозже (может, вместе с переездом на ghc 7.6).

Мда. Долго мучался (с попыткой описать типы внутри своих where-функций), пока не нашёл ScopedTypeVariables, как ответ на свои мучения. Всё же, считаю, что они долджны быть частью стандарта и в книжках описываться. Интуиция для них, как по мне, и так вполне сильная.