Using 'contains?' on bracketed vs non-bracketed property values

I’m curious about the behavior of contains? with block properties. Here’s a short advanced query:

#+BEGIN_QUERY
{
:title [:h3 "Property type: 'project'"]
:query [:find (pull ?b [*])
:where
   [?b :block/page ?p]
   [?p :block/properties ?prop]
   [(get ?prop :type) ?type]
   [(contains? ?type "project")]
]
}
#+END_QUERY

Given pages or blocks with type:: [[project]] or type:: project, the query only returns blocks with the double-bracketed value ([[project]]).

If I change the [(contains? ?type "project")] to [(= "project" ?type)], the query returns blocks with or without double-brackets only blocks without double brackets, which makes sense.

But the behavior of contains? in this case is confusing to me.

On a side note, are there compelling reasons to always use double brackets when defining properties?

1 Like

Contains uses a list.
So it is does this list contain this value.

When using double brackets you create a list of references in format #{"page name" "page name 2"}. For when you use for example type:: [[page name]] [[page name 2]]

This is then your input for contains.
So it is [(contains? #{"page name" "page name 2"} "page name")]
That will be a yes.
The equals sign is exact. Therefore, [(= #{"page name" "page name 2"} "page name")] will be a no.

In reverse your property value would not be a list. It would be a single value. This doesn’t work with contains, but does work with equals. Granted that both inputs are exactly the same.

Reasons to always use brackets:

  • makes a good linked reference. The link doesn’t also show up under unlinked references. (See: Linked reference considered unlinked at the same time?)
  • can always use multiple values in a property, without having to define it as comma separated page references in the config.
  • makes sure you use the correct page name as it will use the lookup function

I’ve opted to always just use double brackets. Also to just save myself the confusion.

1 Like

Thanks for explaining. So I’m getting the impression that in Logseq, contains? in combination with properties, is always searching for a list of references, not strings, and therefore only returning matching property values if they are double-bracketed. Do I have that right?

Thanks for the reasons you stated. Sounds like a good policy, and good to know about the unlinked references quirk/feature you mentioned. Thanks again for your generous help here and elsewhere! :smiley:

That’s the effect, but that’s not exactly what is going on.
Contains always uses a list. Contains is a clojure function, so it is not Logseq specific.
Logseq will create a list of the property value the moment it is a reference. This list will have 1 or more values, based on how many references you added.

You can force Logseq to give you the actual string value by using :block/properties-text-values
So if property:: [[page 1]] [[page 2]] makes #{"page 1" "page 2"} normally, it will make [[page 1]] [[page 2]] instead.

If you don’t use double brackets, but define the property to behave as the tags property does. (Through config). Then it will make that value list as well, but the text-values variation will not have the double brackets.

To get back to my reasoning. I’d rather consciously make the decision to have something be a link through the use of double brackets.

Clojure documentation: contains? - clojure.core | ClojureDocs - Community-Powered Clojure Documentation and Examples

3 Likes

Thank you! I understand better now.

One point of confusion for me is the difference between [(contains? ?type "project")] and [(contains? #{"project"} ?type)]:

#+BEGIN_QUERY
{
:title [:h3 "Property type: 'project'"]
:query [:find (pull ?b [*])
:where
   [?b :block/page ?p]
   [?p :block/properties ?prop]
   [(get ?prop :type) ?type]
   [(contains? #{"project"} ?type)] ; returns only `project`, not `[[project]]`
   ;[(contains? ?type "project")] ; returns only `[[project]]`, not `project`
]
}
#+END_QUERY

Please excuse me if I’m asking you to repeat something you’ve already explained. I’m just not getting that part.

As per the documentation, the syntax is: (contains? coll key).
In other words we are testing a collection against a key.

[(contains? ?type "project")]

  • the collection is ?type
  • the key is project
  • does the key project appear in the collection ?type?
  • when ?type is #{"project"} then yes
  • when ?type is “project” then no, as ?type is not a collection we can test against

[(contains? #{"project"} ?type)]

  • the reverse of the above really.
  • when ?type is a collection, it is not a key to use for the check. Or rather it becomes the literal key "#{"project"}" which doesn’t occur in the set, as the set only contains “project”.
  • #{} is the notation for defining a set.
1 Like

Thanks, that clarifies it. I appreciate it!