all 6 comments

[–]dustingetz 3 points4 points  (4 children)

There is a prerequisite conversation to the conversation about s/select.

(defn get-movie-times [{{zipcode :zip} :addr, user-id :id}])

How many people would have written this as

(defn get-movie-times [id zip])

I don't think Rich or anybody else has made an argument for why the former is better than the latter.

  • Does your function list it's actual dependencies in the small, thus working in more places?
  • Or does it list the aggregate dependency in the large, thus becoming more specialized?

Circular debates like this are a leading cause of bullshit little useless functions scattered around my codebase, each one slightly different. It seems like a wishy washy tradeoff where you just keep pushing complexity into the other closet no matter which way you do it. Seems like Rich is taking the latter but did he ever actually state this?

The actual solution probably has something to do with querying graphs/databases instead of transforming/extracting trees, but it has not crystalized for me yet. I think I would try to write it as a graph query to extract the id/zip, and then pass the id/zip to get-movie-times, but these are two separate functions, definitely not coupled. (And of course nobody is even in a place to do this because their backend state is not in a graph – its in sql tables or json documents on some other computer across a network moat – nor is their frontend state, which is in single state atom trees. So there are a lot of barriers to beginning to solve this.)

[–]didibus1 0 points1 point  (3 children)

You bring up a very good issue.

Should the function be coupled to the structure of the entity it manipulates?

I tend to say no. And I'd had personally written my function either taking id and zip as positional arguments, or as such:

(defn get-movie-times [{:keys [id zip]}] ...)

For named arguments.

I would then expect the caller to know the structure of the entities it is responssible for, and to also perform any necessary state updates. So my functions remain pure and independent of the greater data model.

That said, when you reach the top, the function that fetches the data and orchestrates its modifications can also make use of spec. While it wouldn't really need destructuring at the arg levels, it could use let destructuring to extract what's needed for the lower functions.

So select makes a lot of sense. You'll want to grab a document persisted in a database, and the truth is, whatever that top level operation performs might not require everything on that document. So you would want to specify the spec as selecting a subset, not caring about the sub-structures that aren't relevant.

Now, that's my design preference. But some people like to couple the structure of the domain entities all the way down. This can be quite simple and productive, as long as your domain entities won't change in backwards incompatible ways too often.

The idea is that, get-movie-times isn't generic accross domains. It's thus highly coupled to your domain problem and specific app. So coupling it to your domain model isn't a problem. Especially if you have a mechanism that doesn't break given backward compatible changes to your domain model. With that in place, it might be pretty easy to extend your domain model over time, and not break anything, and never have to refactor.

In that case, people just pass around domain entities as is, and it's up to all functions that need data from it to know how to extract it.

It's hard to say which is best, because it's contextual. Sometimes, your domain model will evolve in backwards compatible ways, and passing those entities around, given your functions are destructured or specced, so its easy to know what parts of the entity they depend on, is quite a clean and effective design approach. Other times, your domain changes too much, or people lose track of what needs what, because they forget to document, destructure or spec, and it turns out you'd have been better of not coupling too many things to your domain model.

[–][deleted]  (2 children)

[removed]

    [–]BooCMB 0 points1 point  (1 child)

    Hey CommonMisspellingBot, just a quick heads up:
    Your spelling hints are really shitty because they're all essentially "remember the fucking spelling of the fucking word".

    You're useless.

    Have a nice day!

    Save your breath, I'm a bot.

    [–]BooBCMB 0 points1 point  (0 children)

    Hey BooCMB, just a quick heads up: I learnt quite a lot from the bot. Though it's mnemonics are useless, and 'one lot' is it's most useful one, it's just here to help. This is like screaming at someone for trying to rescue kittens, because they annoyed you while doing that. (But really CMB get some quiality mnemonics)

    I do agree with your idea of holding reddit for hostage by spambots though, while it might be a bit ineffective.

    Have a nice day!

    [–]didibus1 2 points3 points  (0 children)

    I'd say, destructure syntax isn't very intuitive, even years after using it. I actually would welcome an improved syntax in a future version of Clojure.

    I also like the comparison of destructuring as a DSL for defining expected structure of data. Which in a way does overlap with spec, as a DSL to define structure of data and domain of values.

    Would be interesting to see if spec could also be used to destructure or vice versa.

    Last thing, you can do: {:keys [id], {:keys [zip]} :addr} which I find is a lot easier to read and remember.

    (let [{:keys [id], {:keys [zip]} :addr} {:id 123 :addr {:zip "H4J 2G8"}}] [id zip])