clojure-russia 2018-12-30

к обсуждению спеки - там есть мультиспека еще, можно попробовать ее и req-un если очень хочется извратиться

но я не уверен что стоит

я уже схему смотрю 😃 спасибо за ссылку на refined

видимо стоило это сделать как только 1.9 альфа вышла 😃

сейчас добавили возможность удалять спеки из регистра

думаю как-то покрутить, попробовать схема-стайл сделать

а чем удаление поможет?

типа

{:user/name string?
 :user/email string?}

так если ты из реестра удалил, как проверка будет работать?

или в какой-то контест заворачивать?

ну оно в ф-ии проверке будет регать, потом проверять, потом дерегать

ну это так

just for fun

а реестр это что?

(defonce ^:private registry-ref (atom {}))

а можно как-то приватную переменную сделать публичной?

если да, тогда переменную можно сделать динамической

и не иметь проблем с параллельным доступом

реестр спек

глобальная штука

(require '[clojure.spec.alpha :as s])

(let [reg #'s/registry-ref]
  (.setDynamic reg true)
  (assert (.isDynamic reg))
  (with-bindings {reg (atom {})}
    (clojure.spec.alpha/registry)
    #_(clojure.spec.alpha/def :foo/:foo int?)
    #_(clojure.spec.alpha/valid? :foo/foo 1)))                      

только это не работает

видимо, потому что defonce

та не, добавлять спеку в глобальный реест с подхаченным ns и потом удалять

короче вряд ли будет работать

просто покрутить хочется от нечего делать

@fmnoise а как в схеме сделать and, т.е. проверить одну схему, а затем другую?

в смысле?

делаешь просто and и проверяешь обе схемы

эксперименты с реестром закончились манки-патчингом) https://gist.github.com/fmnoise/56fdd0951f593e668f4a5e40dc771577

в смысле and?

(def a {:a s/Int}) (def b( {b s/Int}) (def z (and a b)) ???

так смерж просто

это ж мапки

а если они вложенные

а елси у них один ключ?

в смысле один ключ?)

(def a {:a s/Int}) (def a’ {:a (s/pred #{1 2 3}))

Int тогда избыточный

второй схемы хватит

там надо мозги перестроить от спеки

что подчас тяжело

другая философия совсем

допустим, у меня есть сущность

и у нее есть разные сосотяния

причем состояние не одно поле

ну вот там начинается веселое

а набор полей

а толку?

если он ошибку не выдаст подробную

это просто предикат проверить

там с ошибками так себе

там можно добавить свой типа символ

который будет означать ошибку

'does-not-looks-good

типа такого

это сама спека делает?

или расширение?

что именно?

символ добавить

вот как тут в примере

ты сам добавляешь

а, я думал это что-то вроде `{:a {^:name ’symbol} s/Int}

оно тебе его просто в валидации возвращает типа как код ошибки

типа осмысленное сообщение об ошибке можно сделать

ребята развили эту же идею и с guards в refined https://speakerdeck.com/kachayev/keep-your-data-safe-with-refined-types?slide=62

вообще у самой схемы composability очень слабое место

у spec еще хуже 😃

нет, у spec лучше

и следующий слайд

так как из фукнции вернуть спеку?

верне вернуть то можно

но толку

в смысле из функции

я про то, что схема которая сложнее чем просто мапка это вещь в себе

ну типа (fn [] (s/keys ....)

добавить\убрать что-то в нее не получится

оно вернет

например когда ты в constrained завернул что-то, потом ты не сможешь сделать assoc или dissoc

а без constrained она очень простая

и большинство задач не решает

кстати вот эта фишка и была довольной сильной мотивацией при создании schema-refined

мы намучились тогда со схемой прилично

что-то мне подсказывает, что я буду все руками делать 😃

короче идеального решения нет

все ждали что спека его принесет

но спека не о валидации вообще

а почему {(s/optional-key :foo) s/Keywordа не {:foo (s/optioinal-key ....}?

так вроде не нужно изобретать schema-tools, чтобы делать select, assoc и прочее

спроси авторов

которые уже давно забросили ее

Видимо, нужно разделять форму данных и валидацию данных под конретную бизнес операцию. Рыжиков в подкасте говорил, что в FHIR большинство полей опциональные

ну вот да

спека про форму

схема хз про что

но спеку я “сломал”

вчерашним примером

на java это можно сделать

на спеке - для ключей с неймспейсом - нет

{:publication/id 1
 :publication/translation {:publication.translation/title "article"
                           :article.translation/content "some content"}
 :article/image-url "http://..."}

пока это единственное непонятное место в спеке

так а еще раз, что у тебя не получилось?

посмотри java код

есть публикация и статья

есть перевод публикации и перевод статьи

первые - родители для вторых

и у статьи может быть переопределенный метод, возвращающий перевод статьи вместо перевода публикации

т.е. в clojure это как-то так:

{:publication/id 1
 :publication/translation {:publication.translation/title "article"}}

это публикация

и у нее есть “метод” :publication/translation

и я хочу его расширить

новыми ключами

и ключи должны быть с неймспейсом

для req-un можно сделать

интересно именно для req

в смысле расширить*

ты один раз определил и вот оно

смотри

есть (s/def :foo (s/keys :req [::a ::b]))

а есть его расширение (s/def ::foo' (s/merge ::foo (s/keys :req [::c}))

и это работает только на первом уровне

а для вложенных не работает

т.е. единсввенное что я могу сделать:

{:publication/id 1
 :article/translation {:publication.translation/title "article"
                       :article.translation/content "foo"}}

вместо

{:publication/id 1
 :publication/translation {:publication.translation/title "article"
                           :article.translation/content "foo"}}

т.е. есть фукнция, работающая с публикацией

и ее переводами

и я не могу ей передать статью

т.к. у нее переводы не в том ключе лежат

так статью не можешь или публикацию?

функция принимает публикацию

но статья - это расширение публикации

и фукнкция должна работать с ней

получается ключ :article/translation, а должен быть :publication/translation

ну сделай на первом уровне через :opt [:article/translation :publication/translation]

публикация не знает своих потомков

и отдельно проверяй что какой-то из них точно должен быть

потомки знают публикацию

потом появится галерея

и еще чего-нибудь

по факту это циклическая зависимость

не понял про потомков

у тебя мапка с id и translation

только они с неймспейсами

ну в мапке у тебя или publication неймспейс или article

должен быть только publication у всех

(defn title [publication]
  (-> publication :publication/transalation :publication/title))

скорми ей статью

так вообще тогда проблемы не вижу

а где начинается article?

у тебя во внутренней мапке либо :article.translation/content либо :publication.translation/content?

или как?

(ns publication.translation)

(s/def ::title string?)
(s/def ::translation (s/keys :req [::title]))

(ns publication
  (:require [publication.translation :as translation]))

(s/def ::id int?)
(s/def ::publication (s/keys :req [::id ::translation/translation]))

(ns article.translation
  (:require [publication.translation :as base]))

(s/def ::content string?)
(s/def ::translation (s/merge ::base/translation (s/keys :req [::content])))

(ns article
  (:require
   [publication :as base]
   [publication.translation :as base.translation]
   [article.translation :as translation]))

(s/def ::image-url string?)
(s/def ::article (s/merge ::base/publication
                          (s/keys :req [::image-url])
                          ;;;;;
                          (s/keys :req [::translation/translation :as ::base/translation])))

> у тебя во внутренней мапке либо :article.translation/content либо :publication.translation/content?

{:publication/id 1
 :publication/translation {:publication.translation/title "article"}}

{:publication/id 1
 :publication/translation {:publication.translation/title "article"
                           :article.translation/content "foo"}
 :article/image-url "http://"}

{:publication/id 1
 :publication/translation {:publication.translation/title "article"
                           :gallery.translation/author "foo"}
 :gallery/image-urls ["http://"]}

или не понятно?

ну тут надо просто определить все что только может быть под ключем :publication/translation

publication.translation/title я вижу req

на зависимости посмотри

остальноe opt

не может publication.translation зависеть от article.tranlation

будет цикл

и это не расширяемо

ну тут зависимостей не может быть

т.е. это невозможно в спеке сделать

тут напихал ключей, написал ф-ю которая проверяет их дополнительно

ну или добавлять тип и делать через мультиспеку

я б добавил в :publication/translation :publication.translation/type

в общем, в спеке это не делается для этих данных тут нужен какой-то (s/merge-in)

и зависимо от него уже через мультиметод решал

да, но этот тип должен быть в верхней мапе по логике

ну и что

т.е. данные под спеку подгонять? 😃

наверное наоборот должно быть

я не понял тип у публикации или у перевода?

{:publication/id 1
 :publication/translation {:publication.translation/title "article"
                           :article.translation/content "foo"}
 :article/image-url "http://"
 :publication/type :article}

о, тип у публикации и от него зависят ключи перевода

{:publication/id 1
 :publication/translation {:publication.translation/title "article"
                           :publication.translation/type :article
                           :article.translation/content "foo"}
 :article/image-url "http://"}

так не правильно

это простой случай, переводов может быть много

но это не рассматриваем

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

чтобы спека работала

(s/def ::article (s/merge (s/merge-in ::base/publication
                                      [::base.translation/translation]
                                      (s/keys :req [::translation/content]))
                          (s/keys :req [::image-url])))
                          

вот что-то такое нужно

попробую поподробнее расписать и в #clojure-spec вбросить

вагную тебе там скажут у тебя кривая схема данных)

кривые данные?

короче да

тут можно порешать 2 ключами с типами

может можно как-то и без типа, а привязаться к наличию специального атрибута

например article.translation/content или gallery/image-url

типа если есть gallery/image-url значит это галерея

если есть article.translation/content значит это артикл

но это опять же применение типа, просто базирующееся на том что некие ключи соответствуют одному типу, а некоторые другому

плюс в том что мультиспека позволяет диспатчить как угодно

вот пример похожий на твой

(s/def ::base (s/keys :req [:pub/title]))
(s/def ::image (s/merge ::base (s/keys :req [:img/url :img/alt])))
(s/def ::article (s/merge ::base (s/keys :req [:article/title :article/author])))

(def types {:img ::image
            :article ::article})

(defn trans-type [m]
  (types (some (-> types keys set) (->> m keys (mapv (comp keyword namespace))))))

(defmulti t identity)

(defmethod t :default [v] (trans-type v))

(s/def ::t (s/multi-spec t :translation.type))