Thanks for the detailed response! I like the idea of using destructuring to be more specific about what data a function expects. I also think the pre/post conditions with spec would mostly solve my problem. I think I'm still getting used to spec's syntax, which feels fairly verbose to me when defining nested structures. But maybe it will make more sense with time.
BTW, I posted this question at https://www.reddit.com/r/Clojure/comments/s90udy/python_dataclass_equivalent/ as well, where it has sparked a good deal of discussion. On Thu, Jan 20, 2022 at 9:39 PM James Reeves <jree...@weavejester.com> wrote: > Type hints in Clojure have a different purpose to those in Python. In > Clojure, type hints are only a mechanism to avoid reflection; their use is > solely to improve performance. > > So the question "Can I use spec as a type hint?" is actually asking "Will > the compiler use specs to avoid reflection?", to which the answer is no. > However Spec, and other equivalent libraries, can be used for runtime type > checking in Clojure. > > Where you'd use a dataclass in Python, you'd generally use a map in > Clojure. This requires a little explanation because of the different ways > Clojure and Python support data modelling. > > Suppose Python you have a dataclass: > > @dataclassclass InventoryItem: > name: str > unit_price: float > quantity_on_hand: int = 0 > > def total_cost(self) -> float: > return self.unit_price * self.quantity_on_hand > > > Then the *minimal* equivalent Clojure code is simply: > > (defn total-cost [{:keys [unit-price quantity-on-hand]}] > (* unit-price quantity-on-hand)) > > The Clojure example lacks explicit type checking, but that may not be > necessary. In Clojure, we can incrementally add more specific checks as > necessary. For example, we could add a precondition to check the inputs: > > (defn total-cost [{:keys [unit-price quantity-on-hand]}] > {:pre [(float? unit-price) (int? quantity-on-hand)]} > (* unit-price quantity-on-hand)) > > Clojure spec allows checks to be taken further, with the caveat that > keywords must be namespaced. Spec is more granular than classes, as it's > interested in single key/value pairs, rather than grouped properties as in > a class or struct. With spec, we might declare: > > (s/def :inventory.item/name string?) > (s/def :inventory.item/unit-price float?) > (s/def :inventory.item/quantity-on-hand int?) > > (defn total-cost [{:inventory.item/keys [unit-price quantity-on-hand]}] > (* unit-price quantity-on-hand)) > > This doesn't automatically perform type checks, as they may not be > necessary. We can add type checks to a function with > *clojure.spec.test.alpha/instrument*, or add them as a precondition using > *clojure.spec.alpha/valid?*. > > The validation required depends on the origin of the data. Suppose the > inventory item comes from a database with its own schema. In which case, we > may be reasonably certain that the types are correct and there's no need > for additional confirmation. > > Or suppose instead that we read the inventory item from an external source > we may not trust. In that case, we'd want to validate it as input, and > reject it if the data is invalid. But after we've validated it, we can pass > it around internally without further checks. > > Perhaps we're worried instead about human error. In this case, we might > turn on checking during development and testing, but remove it during > production when we're more interested in performance. This is the broad > use-case for Spec's *instrument* function. > > Clojure's takes a more nuanced, and possibly unique approach to data, > compared to other languages. Understanding how Clojure views data is > understanding Clojure as a language. > > On Fri, 21 Jan 2022, at 3:22 AM, Kovas Palunas wrote: > > Hi all, > > Coming from python, I use dataclasses > <https://docs.python.org/3/library/dataclasses.html> a lot to tie complex > collections of data together. I like using them in combination with type > hints so that it's clearer to me at a glance what kind of data a function > is processing. When my data starts to look like a dict of dicts of lists, > I turn to dataclasses to help my simplify. > > So far in my Clojure journey, I've stumbled across two features that could > help me do something similar. First are datatypes > <https://clojure.org/reference/datatypes> (deftype, defrecord), and > second is using maps with spec <https://clojure.org/guides/spec>. Based > on my reading, using spec to define types seems like a really flexible > system that does built in testing for me. I was surprised to read that > specs can't quite be treated like types though: > https://ask.clojure.org/index.php/10464/can-i-use-spec-as-type-hint. > Maybe the intention is to use the :pre and :post keys in a function's > options as type hints? > > I'm curious if I'm thinking along the right lines here, or if there are > other language features I could be looking at. Also curious if anyone has > suggestions for books or articles that explore data typing options in > Clojure. > > Thanks, > > - Kovas > > > -- > You received this message because you are subscribed to the Google > Groups "Clojure" group. > To post to this group, send email to clojure@googlegroups.com > Note that posts from new members are moderated - please be patient with > your first post. > To unsubscribe from this group, send email to > clojure+unsubscr...@googlegroups.com > For more options, visit this group at > http://groups.google.com/group/clojure?hl=en > --- > You received this message because you are subscribed to the Google Groups > "Clojure" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to clojure+unsubscr...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/clojure/a5718e8b-71a6-40d5-b1da-ca2cdb10a71en%40googlegroups.com > <https://groups.google.com/d/msgid/clojure/a5718e8b-71a6-40d5-b1da-ca2cdb10a71en%40googlegroups.com?utm_medium=email&utm_source=footer> > . > > > -- > James Reeves > booleanknot.com > > > -- > You received this message because you are subscribed to the Google > Groups "Clojure" group. > To post to this group, send email to clojure@googlegroups.com > Note that posts from new members are moderated - please be patient with > your first post. > To unsubscribe from this group, send email to > clojure+unsubscr...@googlegroups.com > For more options, visit this group at > http://groups.google.com/group/clojure?hl=en > --- > You received this message because you are subscribed to a topic in the > Google Groups "Clojure" group. > To unsubscribe from this topic, visit > https://groups.google.com/d/topic/clojure/GcUqCXbh-DI/unsubscribe. > To unsubscribe from this group and all its topics, send an email to > clojure+unsubscr...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/clojure/da5de1bf-a466-4b78-898a-84d9b6217a53%40www.fastmail.com > <https://groups.google.com/d/msgid/clojure/da5de1bf-a466-4b78-898a-84d9b6217a53%40www.fastmail.com?utm_medium=email&utm_source=footer> > . > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/CAPzck%3DdnoTCJNfJ12osDtN-fGf4HQc2N7fCv-dav2s39dXLMnQ%40mail.gmail.com.