Fork me on GitHub
#clojure-spec
<
2017-08-16
>
didibus21:08:06

Question: Is there a way I can find the spec of a data-structure at runtime? I know data-structure are not tagged with their spec, but can you like run a structure through all specs and get the spec it validates back?

gfredericks21:08:06

I bet it'll pass any?

gfredericks21:08:14

though I guess that's not in the registry

gfredericks21:08:40

but there could be a lot of keywords in the registry that just have any? as their spec

didibus21:08:09

Well, I guess it would need to be smarter, like return the strictess spec that it validates or something. Not sure how that could work

joshjones21:08:35

what specifically do you want to accomplish?

gfredericks21:08:30

specs like (s/keys :opt [...]) would validate unrelated maps

didibus21:08:33

I want conditional logic based on the "type" of my data.

joshjones21:08:11

have you considered using metadata?

didibus21:08:43

I thought about it, and then I wondered if there's a way to have spec validate that some data-stucture has particular meta on it

joshjones21:08:44

spec can validate most anything … if you can write a function that tests whether or not something is “valid”, you can use it with spec

didibus21:08:24

Hum, ya I guess that's true, forgot its just predicates. I was looking for some prefab like s/meta, but I can just build my own

gfredericks21:08:26

I'd just make it explicit if you can {:type :something, ...the rest of the data...}

didibus21:08:09

Ya, I thought I might need to go down that path. It just forces everything to now operate over the data in a special way though.

didibus21:08:42

I'll try tagging my data with the spec keyword

didibus21:08:46

in their meta

didibus21:08:50

see if that works

joshjones21:08:18

i think @gfredericks meant not using metadata at all, but just using a map with a :type keyword. it seems to me you don’t may not really need spec, or metadata, but maybe just a good ol’ multimethod

didibus21:08:31

I always worry about using meta like this, because I feel a lot of functions in Clojure don't handle meta very well, like if they modify the data they can return a copy with the meta lost

didibus21:08:07

@joshjones Ya, I got it. But then everything is a map. Say I have a set, I can't union it directly anymore for example, I have to unwrap and re-wrap manually. Maybe there's a monad I can use to help with this though

joshjones21:08:14

you don’t need to make it a map to use a multimethod to implement different logic (at the function level) based on your data — but i don’t have any specifics so i’m not quite sure what you really want

joshjones21:08:19

if you can write a function that can determine what “type” of data a particular input is, that’s your dispatch function — but again, maybe you don’t even want function-level conditional logic, so just guessing

didibus21:08:01

My problem is I guess the structure and values of my data aren't unique enough. So really I want my data-structures to have a name or a tag of some sort. So say I could distinguish between two vector of ints, because one is an age vector, and one is a height vector.

didibus21:08:28

Then based on if the vector is of age or of height, I want to do something different

didibus21:08:32

adding a metadata tag to my vectors would be one way, but like I said, I know some functions over vectors don't preserve meta. I could wrap my vectors in a map and have {:tag :age :value [12 23 34]}, but then for every operation over the vector I have to first unwrap the vector, pass it to the function expecting a vector, then wrap it back again.

didibus21:08:01

For some reason, spec always fools me in thinking it can do this, because of the name you give to specs, I assume that data of that spec will have that name on them too, but its not the case.

didibus21:08:17

It would be great if all data-structure could have a pointer to a spec, like that's what I would want. So then given a vector, you could do (spec [1 2 3]) and it would return the spec for [1 2 3]. Unfortunately, it works the other way around.

seancorfield21:08:27

Your problem there is that your raw data (a vector of numbers) isn't meaningful on its own...

seancorfield21:08:55

(as you said "...not unique enough...")

seancorfield21:08:06

Metadata never seems to be a powerful enough solution -- except where it is attached to a Var or to code (that a macro can process). Metadata on values seems outside of the problem space imagined in its design...

didibus22:08:11

Hum, that's a good rule of thumb for metadata.

joshjones22:08:30

based purely on data alone, how would you know if a vector is a vector of ages, or heights? my guess is, you don’t really know, so you need to tag it as such somehow. even if you did what you imagined, (spec [1 2 3]), the spec that you’re utilizing has to know somehow what you say the “type” is … the simplest solution is to use a map. there’s overhead in any solution, whether that’s explicitly declaring a type when it’s created and tailoring functions to only operate on those types, or whether it’s doing what you’ve suggested. you can’t get around the overhead of differentiating data

didibus22:08:32

I guess I'm not sure what's the best way right now to do that tagging. Basically, I want to add semantic meaning on my data-structures, but I'd like to retain the ability to use all of clojure's functions that operate over that structure. You would think metadata is exactly for this, but not really. Like doing ^:age [1 2 3] would be very natural. So you've got data structured in a vector, and you're giving it semantic meaning by saying it represents the concept of age. So what are other options I have?

joshjones22:08:47

what is the actual source of these vectors? where are they coming from?

joshjones22:08:45

sometimes people will have a function return different shapes/types of data, and the advice is usually to have a function return a consistent shape/type. in the same way, if your data comes from a source which can itself be split such that when you receive data, you already know what it looks like, this is an option. for example, at some source level you must know whether this data represents ages or heights. so, depending on your design and requirements, you may be able to ask for only data that represents ages, and then operate with the knowledge that you have ages. ditto for heights.

seancorfield22:08:38

If you have a TaggedValue record that has a tag and a value then you could have functions that accepted functions and turned them into tag-preserving operations (and could verify consistency of tags in multiple arguments too).

Alex Miller (Clojure team)19:08:52

fyi, there is already tagged-literal and tagged-literal? that use clojure.lang.TaggedLiteral. Those instances also respond to :tag and :value keyword lookups.

Alex Miller (Clojure team)19:08:32

and print as tagged literals

seancorfield21:08:51

Responds to :tag and :form -- I tried :value and got nil. Definitely useful. I did not know about that @U064X3EF3 Thank you!

Alex Miller (Clojure team)21:08:49

it’s used by reader conditionals in the case where you read and keep the conditionals, but don’t have a tagged literal to represent a tagged literal version. it would ideally be the fallback reader if you didn’t find a reader rather than the error you get now - you can install that yourself though.

seancorfield22:08:18

But you're straying off into a hundred different types (with one function each) instead of one type with a hundred functions. So it's beginning to sound non-idiomatic.