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.

Reply via email to