Fork me on GitHub
#clojure-spec
<
2018-04-17
>
d._.b00:04:55

I have a few transforms that are related in my application.

{:aH "abc"} ;;phase-1
{:aH :what-x-means} ;;phase-2
{:a-h :what-x-needs-to-be-for-an-external-app} ;;phase-3
What would be cool is if I could somehow link all of these specs together, and control generation. So, an initial spec for phase 1 would refer to phase 2, and phase 2's spec would refer to phase 3.

d._.b00:04:16

maybe this is more of a controlled generation question?

d._.b00:04:50

maybe conform/unform is what im looking for here?

d._.b00:04:01

where a named conformer would be the transform between two phases, so I could (s/def ::phase-1->phase-2 (s/conformer ...)) (s/conform ::phase-1->phase-2 {:aH "abc"})

bbrinck00:04:16

@d._.b Hm, hard to say without seeing the code, but I suspect it’d be easier to just write transform functions with a transformation library and use the specs to validate each transform is working

bbrinck00:04:26

Generally speaking, using a conformer to transform data can cause issues https://groups.google.com/d/msg/clojure/Tdb3ksDeVnU/LAdniiZNAgAJ

d._.b01:04:45

@bbrinck yes, this makes sense to me. it is fine for me to write the transform functions and validate the transform, but for some of these specs, the difference is very small. For instance, "person": {:name "bob"} => {:name (get {"bob" :fun-bob} "bob")} => {:nick-name "fun-bob"}. It would be nice if I could link the specs as an ordering, and then choose which phase I want to generate.

d._.b01:04:20

from an initial state or spec

bbrinck01:04:00

@d._.b Yep, I can see how this would be convenient. I can’t speak for spec maintainers, but from what I’ve read so far, they have consistently indicated they want to stay away from transformation (and leave this to other libraries - both libraries that are spec-aware or those that are spec-agnostic)

bbrinck01:04:56

I can certainly see the use, but this can’t be done in spec today and I doubt it will be added 🙂 . https://github.com/nathanmarz/specter might be a good fit here

andy.fingerhut02:04:00

Not sure if it is something targeted at what you are hoping to do, but late in the Google group discussion linked above is a mention of the project spec-coerce, which the author says "instead of making conforms that do coercion, it uses the spec and a separated registry to conform the value (similar to what spec does to find the generators for a given spec)". https://github.com/wilkerlucio/spec-coerce

ikitommi05:04:17

@d._.b tested spec-driven data expansion some time ago, via conforming. Not sure if that’s an good idea, but here’s an example: https://www.metosin.fi/blog/clojure-spec-as-a-runtime-transformation-engine/#data-macros

ikitommi06:04:32

would it be a good idea to add support for spec coercion in the core? or support for generic walking with coercion as one application for walking.

Alex Miller (Clojure team)11:04:25

Generic walking yes, coercion maybe

ikitommi12:04:05

I renamed CLJ-2251 to “Generic Spec Walking for clojure.spec”. Is something like that coming? Would you be interested in a demo / patch of that?

Alex Miller (Clojure team)12:04:11

It’s something we’ve talked about but not particularly looking for a patch.

d._.b06:04:17

Thanks for those recommendations.

d._.b06:04:57

When an entity like “foo” exists in numerous, ordered forms, it feels superfluous to name intermediate specs, and edges on “types”. I don’t want that extra, unnecessary complexity in my programs, so I’m looking for a way to define a “foo” as a succession of small transformations, where the phases (minor transforms) are named loosely, abstractly. One place to define all of the successions of miniature transforms is better than enshrining stronger, named entities. The names wind up feeling forced.

d._.b06:04:28

:thing/upcased-name feels like extra nonsense if it’s a short-lived intermediate.

d._.b06:04:09

Coalescing specs into logical, ordered groupings feels valuable if only to avoid the naming of tiny-delta intermediates.

mathpunk07:04:41

I think I'm getting the hang of test/check and custom generators, but I'm getting this oddball response from one of my fdef's: https://github.com/mathpunk/simplexity/blob/complexes/src/simplexity/core.clj#L70

mathpunk07:04:53

NB: Every simplex is a complex, but not every complex is a simplex.

bbrinck14:04:10

The problem seems to be with your :fn spec on dim. The reason the examples work e.g. (dim (complex [])) is that they don’t run the :fn code

bbrinck14:04:39

I deleted the :fn spec on dim and now (test/check dim)` returns with no errors

bbrinck14:04:33

It looks like the issue with the :fn is that :ret does not contain the value, it contains the conformed value e.g. not -1, but rather [:empty -1]

bbrinck14:04:31

I think this will avoid the exceptions, although the definition of dim still fails the :fn test in some cases:

(s/fdef dim
  :args (s/cat :complex ::complex)
  :ret (s/or :nonempty nat-int?
  :empty #(= -1 %))
   :fn #(and (number? (-> % :ret last))
                                (= (size (-> % :args :complex))
                                (+ (-> % :ret last) 1))))

bbrinck14:04:56

FWIW, if you want to test out your fn spec outside of check try https://github.com/jeaye/orchestra

mathpunk17:04:39

@bbrinck Ah ha! I forgot about conformance.... aaaagain. And orchestra looks useful, much obliged

👍 4
mathpunk07:04:26

What's really baking my noodle is, there are a few things I can think of that might just be plain broke in the way I'm writing this, but, it would be broke with MUCH smaller examples than I seem to be getting as examples of failing results

mathpunk20:04:06

I'm trying to define different specs for different implementations of a protocol, which I don't know if it's even possible. This code passes my example tests, but when I try and test/check any of the functions, I just get an empty sequence: https://github.com/mathpunk/simplexity/blob/complexes/src/simplexity/simplex.clj

mathpunk20:04:06

The problem I'm trying to solve is: A simplex is a special case of a complex. It would be nice to construct a simplex by going (simplex [0 1 2]) and a general complex by going (complex [[0 1 2] [2 3] [4 5 6]])

mathpunk20:04:57

That is, construct a simplex from a collection of vertices, and a complex from a collection of (maximal) simplexes

mathpunk20:04:37

I'm open to other ways of accomplishing this goal

bbrinck20:04:35

I haven’t tried it myself, but the advice I’ve heard is that protocols should be internal implementation details and you should spec wrapper functions, which become the public-facing API.

bbrinck20:04:45

I could be misremembering the advice 😉

bbrinck20:04:16

In this case, you’d have a spec for the wrapper function. I’m not sure that it’d need to be different though - if complex is the general case, could the work in terms of complex? I’m not sure

bbrinck20:04:27

You could certainly create a generator that creates either simplex or complex values though, and use that

bbrinck20:04:45

Try wrapping dim in another function e.g. (defn dim1 [x] (dim x)), then write spec for that. test/check works for me on dim1 (poorly named, of course 🙂 )

Alex Miller (Clojure team)20:04:46

You can’t spec protocol functions

kenny21:04:22

Why is the predicate function in the below error message :clojure.spec.alpha/unknown?

(s/assert nat-int? -1)
ExceptionInfo Spec assertion failed
val: -1 fails predicate: :clojure.spec.alpha/unknown
  clojure.core/ex-info (core.clj:4739)

kenny21:04:07

I need to wrap with s/spec?

(s/assert (s/spec nat-int?) -1)
ExceptionInfo Spec assertion failed
val: -1 fails predicate: nat-int?
  clojure.core/ex-info (core.clj:4739)
That seems asymmetrical with the rest of Spec's API -- you can pass a Spec kw, predicate, or Spec obj.

Alex Miller (Clojure team)21:04:25

it’s a bug - there is a pending patch for it

4
Alex Miller (Clojure team)21:04:45

wrapping with s/spec bypasses it