• Haskell aeson хочу уметь передавать дефолтные значения из IO в parseJSON aeson'овский, типа так: lpaste.net
    на стаковерфлов есть что-то совсем дикое и с дженериками, и, вроде, не совсем то: stackoverflow.com
    подскажите куда копать позязя.
    ♡ recommended by @ndtimofeev

Replies (91)

  • @Anatolych, Можешь более подробно объяснить чего ты хочешь?
  • @Anatolych, Такие инстансы просто не надо писать. Если тебе так нужна такая функция, то не пользуйся классом совсем.
  • @ndtimofeev, проставлять значение по умолчанию в json/yaml из IO (например, значения в конфиге нет, а из env я его взять могу)
  • @Anatolych, Присоединяюсь, к /1

    если ты хочешь, что-то вроде:

    foo = unsafePerformIO $ newIORef 0
    {-# NOINLINE foo #-}

    data A = A Int

    instance FromJSON A where
    parseJSON = withObject "a" $ \v -> do
    A <$> v :? "value" .= (unsafePerformIO $ readIORef foo)

    то адекватно так сделать нельзя, но есть нормальные решения
  • @Anatolych, то сделай поля, где могут быть значения по умолчанию типа (Maybe a) и потом (defaultConfig <> parseJSON json)
  • @qnikst, т.е. как на стековерфлоу по ссылочке?
  • @Anatolych, Девочки^Wинстансы налево, файлопарсилки направо. Пишешь инстанс без всяких IO — им там не место и это невозожно протайпчекать. Потом внутри IO вызываешь decode и делаешь свое грязное дело. Пример:
    gist.github.com
    (не проверял, возможны мелкие косяки)
  • @Anatolych, общая идея та же. Но там ответ на немного другой вопрос.
    Здесь newtype городить не нужно, так же как не нужно и IO.
  • @qnikst, человек другое спрашивает вроде, а ты ему врата в ад показываешь
  • @rufuse, Ну эта идея понятна, конечно, но тут 2 конфига — умолчальный и тот который может зафейлиться при чтении (я правильно понял же, да?) — это немного уже другой случай.
    Мне не конфиг дефолтный нужен, мне дефолтное значение из IO хочется.
  • @Anatolych, Тогда это вопрос не json парсера. Пробуешь читать фейлабельный, иначе — дефолтный. Рисуется эксепшенами, например, тут зависит от того как ты это логить и репортить хочешь.
  • @rufuse, вот что-то типа желаемого, но оно не работает (c liftIO всмысле)
    stackoverflow.com
  • @Anatolych, Первое: насколько я понял то что ты хочешь делается при помощи unsafePerformIO.

    Второе: ты хочешь что-то плохое. За такой инстанс тебя будут заслуженно бить. Возможно даже ногами. Возможно чтение конфига стоит сделать частью ввода вывода и не мешать с парсингом.
  • @Anatolych, там бред сивой кобылы в вопросе
  • @rufuse, bpaste.net

    нет, ты
  • @ndtimofeev, я про это и толкую выше
  • @ndtimofeev, так я и не хочу мешать — я ж хочу передать туда уже голый FilePath без всякого IO (но вот до этого он из IO берется, да)
  • @Anatolych, смотри примеры в /15 вроде все покрывается
  • @qnikst, ну вариант то рабочий, но имхо ты только человека путаешь
  • @rufuse, сорри, я думал это на другое ответ, там то действительно врата в ад, причем в особо печальную из его частей..
  • @rufuse, плюс он в качестве дефолта хочет effectful вычисление, тут не прокатит
  • @Anatolych, Тогда пиши отдельный парсер в который в качестве параметра будешь передавать свой FilePath.
  • @rufuse, ну, что мешает под unsafePerformIO запихнуть effectful выражение, это как раз просто, а вот передать ему параметры уже только через какую глобальную переменную
  • @rufuse, ну так-то да, а как лучше-то сделать? мерджить дефолтный конфиг с тем, что прочиталось и подставлять вместо Nothing значения из дефолтного?
  • @qnikst, впрочем я написал, как это должно выглядеть на самом деле, даже с примерами :)
  • @Anatolych, каким образом мне нужно сформулировать пост, чтобы ты заметил ссылку в комментарии /15, прошёл по ней и посмотрел, как сделано? ;)
  • @qnikst, Подытожу так — технически согласен, этически в данной ситуации — нет, т.к. оверкилльно, а человек явно путается в хацкельном коде. :)
  • @qnikst, я посмотрел. там опять про два конфига. их как-то, может, мерджить можно чтоб не сильно устать?
  • @Anatolych, сейчас нарыгаю тебе пример на глаз, а то сколько можно то уже
  • @Anatolych, я не знаю какой конфиг должен быть, чтобы он сильно устал. Для операции выполяющейся за линейное время от количества полей.. При том, что из-за ленивости не ненужные значения вычисляться не будут.
  • @rufuse, это в любом случае неприемливо, втаскивать в чистую операцию глобальный мутабельный стейт это слишком сильно. Я, конечно, представляю, как его сделать относительно безопасным, но это не то, что стоит рассказывать не в качестве страшилок :).

    Или мержить с дефолтным конфигом или парсить в Value и делать эффективную операцию, которая будет конструировать выражение.
  • @rufuse, вот тебе еще гист на глаз gist.github.com
    то что надо?
  • @rufuse, починил косяк, поэфпячьте
  • @rufuse, ну ок. это все понятно. ну вот прочитался конфиг без ошибок, но вместо homeDir там Nothing, допустим. чо делать? проверять во всех местах чтоль есть он там или нет, лазить в окружение (IO, ага), так?
  • @qnikst, так.. не совсем верно, сейчас попробую написать как надо..
  • @Anatolych, Во-первых откуда там Nothing? Во-вторых в каких всех местах?
  • @Anatolych, то ли я во второй раз не понял твой вопрос, то ли там Nothing взять неоткуда
  • @ndtimofeev, ну не указал там кто-нить настроечку, а она Maybe. кароче. выкину-ка я нахер эти дефолтные настройки. буду сразу лепить конфиг из окружения при установке.
  • @Anatolych, Вот для этого, ты и передаёшь свою дефолтную настройку в парсер. Если он при разборе не находит нужного значения, то ставит ту которую ты передал.
  • @ndtimofeev, тут есть минус, если все поля в дефолтном конфиге это `threadDelay 10000000 >> return stuff` то этот конфиг будет вычислен, даже если не нужен, и чета я туплю и не понимаю как сделать аккуратно, чтобы он не вычислялся
  • @qnikst, вообще не понял о чем ты сейчас
  • @rufuse, ну вот читал ща yaml c указанным ключом но пустым значением — как раз дает Nothing, как и ожидалось.
  • @Anatolych, Ну так если есть значение — читаешь этот конфиг, нет значения — читаешь дефолтный. В чем проблема то? Еще проще чем выше.
  • @Anatolych, Внутри парсера проверяешь вернули ли тебе значение, если нет — подставляешь дефолтное из аргумента.
  • @ndtimofeev, дык по моей ссылочке все так и есть по (.!=) как раз именно значение проставляется дефолтное
  • @Anatolych, Бинго!
  • @ndtimofeev, а как мне его передать-то в parseJSON?
  • @Anatolych, Никак. Он не для этого. Напиши отдельный парсер.
  • @Anatolych, а нахрена его в parseJSON передавать?
  • @rufuse, а как еще?
  • @Anatolych, выше как минимум три гиста на тему "как еще?"
  • @ndtimofeev, ну вот и нахера тогда оно такое?
  • @Anatolych, а что по твоему инстанс FromJSON делает, а?
  • @rufuse, Допустим у тебя дефолтное окружение содается как Config <$> sendAQueryToAReplicatedLog "give me A" <*> sendAQueryToReplicatedLog "give me B". И ты не хочешь выполнять эти IO действия если они тебе не нужны, мой вариант через <> тут не сработает
  • @rufuse, твои гисты все про "не читается конфиг — подсунем дефолтный конфиг" — это чота не совсем то
  • @Anatolych, как минимум 2 из трех конфигов ты прочитать не сумел :(
  • @Anatolych, я умываю руки
  • @qnikst, s/конфигов/гистов/g
  • @qnikst, Ты занимаешься преждевременной оптимизацией? Можно пихнуть таки чтение дефолтного значение в unsafePerformIO. Можно параметризовать Config типами спорных значения. Вернуть из парсера Config (Maybe FilePath) и затем из него сделать Config FilePath. Но тут как бы не о том речь. Чувак то ли пытается нас троллить, то ли не врубается вообще ни во что. Научи его писать просто. Писать сложно он прекрасно научится сам.
  • @qnikst, А, я думал ты на другое ответил. Есть минусы юзанья из жаббера.
  • @Anatolych, Очевидным образом оно (если ты про FromJSON) для разбора валидных документов фиксированной формы.
  • @Anatolych, А что тогда то?
  • @ndtimofeev, оно и читает валидно: Maybe FilePath очевидным образом может быть Nothing. и на это идет замена по умолчанию (.!=)
    и оно даже работает с обычным захардкоженным String
  • @ndtimofeev, Ну да, FromJSON это односторонний маппинг валидного json документа на твой тип. Всё. Остальное — логика твоей программы. Про это дофига кусков кода выше. Если у тебя какая-то другая логика в голове — перепиши, все необходимые примитивы в примерах выше даже указаны.
  • @qnikst, наша песня хороша, начинай сначала.
    fromJSON это чистая функция by-design, почему это хорошо и как это используется это отдельный разговор. Поэтому IO действия ты туда можешь вставить только адскими хаками приведенными в /4.

    Есть два варианта как сделать:

    1. создать "дефолтный конфиг" и потом с помощью Monoid дозаполнить то, что ты напарсил дефолтным конфигом. Тут есть 1 минус, дефолтный конфиг тебе надо создать полностью.

    2. после парсенья пройтись по структуре и если встречаешь Nothing — то выполнить своё IO, которое дозаполнит поле.

    Вариант 1 можно свести по эффективности к 2, но это тема отдельного разговора.
  • @Anatolych, Замечательно, теперь передай в свой парсер (который не имеет никакого отношения к FromJSON) ту строку которую ты хочешь туда вставить и у тебя будет всё хорошо.
  • @ndtimofeev, Так вот в этом и заключался собсна вопрос. Куда и как передать строку.
  • @Anatolych, А в этом и заключались ответы выше, ороборос укусил себя за хвост.
  • @rufuse, я вот вижу только один загадочный гист с использованием (<>) — но я чота до этого не дорос пока. остальные ответы — не то. даже по ссылке на so в теме — и то ближе.
  • @Anatolych, Что именно не то?
  • @ndtimofeev, полностью дефолтный конфиг предлагается
  • @Anatolych, Не понял. Ты хочешь полностью дефолтный конфиг или нет?
  • @Anatolych, нет, код с <> делает дефолтными только те поля, которые не заданы в конфиге
  • @qnikst, ну вот этот (<>) он вообще откуда?

    λ→ :search (<>)
    Searching for: <>
    Data.Text.Internal.Builder.Functions (<>) :: Builder -> Builder -> Builder
    Language.Haskell.TH.PprLib (<>) :: Doc -> Doc -> Doc
    Text.PrettyPrint.HughesPJ (<>) :: Doc -> Doc -> Doc
    Text.PrettyPrint (<>) :: Doc -> Doc -> Doc
  • @ndtimofeev, я хочу дефолтные значения.
  • @Anatolych, Это mappend для Monoid'а.
  • @Anatolych, (<>) = mappend
  • @Anatolych, Тебе уже сказали, передай дефолтные значения в парсер.
  • @ndtimofeev, ну блин. как?)
  • @Anatolych, Пишешь функцию configParser defaultValue = …
  • @Anatolych, gist.github.com

    расписал понятнее, с большим числом примеров и явным описанием высказанных мной подходов
  • @qnikst, спасибо.
  • @Anatolych, если остаются какие-то вопросы или непонятно как это соотнести с тем, каким вы видите правильный путь — то спрашивайте
  • @qnikst, ну вот интересно чем ваш вариант лучше этого: stackoverflow.com
  • @Anatolych, тем, что он не лучше и не хуже, а другой?
    там предлагается сделать тайпкласс, в котором для каждого типа 'a' можно указать дефолтное значение. Т.е. каждому типу соотвествует одно и только одно значение, даже если обойти это через newtype-ы, то все равно эти значения являются статическими и определяются на этапе компиляции.
    И тут значения не `Maybe a`, а `a`

    У меня решается проблема, как заполнить неопределенные поля в рантайме, например, для структуры `A (Maybe Int) (Maybe Int)`, в моём случае я могу сделать каждому из Int разные значения если они не указаны, но тут уже всяко остаётся Maybe. Более того, значения для заполнения можно конструировать как "заранее" построив дефолтную структуру (пример 5), так и после, только для тех полей которые нужны (пример 6). И если хочется, то свести вариант с Maybe к варианту без Maybe (toFinal).
  • @qnikst, ну это здорово все, наверное. а что можно еще почитать\посмотреть чтоб такое понимать с полпинка (-не пополнить ряды неосиляторов-)

    читал lyah, rwh, смотрел мейера с fp101x, кубенского ФП
  • @Anatolych, не знаю даже что конретное подсказать, на ruhaskell.org список книг ресурсов вроде висел, изредка можно посматривать I wish I know about haskell если автор его не окончательно удалил
  • @Anatolych, да, но это так периодически смотреть разбираться и интересным, изучать работать — посмотреть снова (поймешь уже больше) и т.д.
  • @qnikst, ок. спасибо.