Unnamed Types - What Am I Doing Wrong?

205 views
Skip to first unread message

Kevin Kleinfelter

unread,
Jul 25, 2017, 9:52:41 PM7/25/17
to Clojure
I ran into the 'refactoring an unnamed type' problem.  I'd like to know how experienced Clojurists avoid it.

I've got an account record/structure.  It has things like an account name, account number, etc.  I started off storing it in a vector, because it had just two elements.  Account name was (first v).  Account number was (second v).  And that worked up to a point.  Over time, it has acquired enough pieces and rules that I really need to change its implementation.  I need to refactor it.

When it was only a few hundred lines long, in a couple of files, I could examine each line.  Now that it's a dozen files and several thousand lines, I just don't have the attention span.

In a language with named types, I could search for AccountRecord.  I could thoroughly find all the places I used it and refactor it.  Or I could change the name of the type to tAccountRecord, and the compiler would identify all the places where I used it (with error messages).

In an OO language, I'd be accessing all of its pieces via getters and setters, and I wouldn't have to find all of the places where I used it, because the implementation would be a black box.

But in a language with unnamed types, it's just a vector and I've just got first and second and nth to search for.  That's going to find lots of irrelevant stuff.  It's enough to make me pine for Java and a refactoring IDE.  =:-o

So how do developers who work with un-typed (or un-named type) languages avoid this sort of problem?  Or, failing to avoid it, how do they clean up afterward?
tnx

Gary Trakhman

unread,
Jul 25, 2017, 9:59:31 PM7/25/17
to clo...@googlegroups.com
Maps with named keys work much better than vectors/lists for heterogenuous data.  I've recently taken up OCaml as a very well-typed language, and it's basically analogous to records vs tuples tradeoffs there.

Clojure.spec can help at a larger scale.

You can do the same encapsulation as Java by writing accessors in a namespace, it sounds to me like you didn't abstract it out early enough.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+unsubscribe@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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Daniel Compton

unread,
Jul 25, 2017, 10:03:45 PM7/25/17
to Clojure
Hi Kevin

For something like an account, a more idiomatic way to model this in Clojure might be with maps:

{:account-name "John Smith"
 :account-number "125-1290"}

or

{:account/name "John Smith"
 :account/number "125-1290"}

If you then want to refactor your usage/naming of map keys then you can search for usages of the keyword (Cursive has this built-in, I'm not sure about other editors).

Clojure also has records which you can use to model your system. They are similar to maps, but have some extra features. Alex Miller has a good explanation about the tradeoffs between records and maps, and there's more discussion here, and here.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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

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+u...@googlegroups.com.

Peter Hull

unread,
Jul 26, 2017, 1:06:45 PM7/26/17
to Clojure

On Wednesday, 26 July 2017 03:03:45 UTC+1, Daniel Compton wrote:
For something like an account, a more idiomatic way to model this in Clojure might be with maps:

If I understand Kevin's post correctly, he's already planning to replace his vectors with maps, and he is asking if there's a way to make the change without too much pain and without missing anything.
 
I wouldn't count myself as 'experienced' so I don't know if there is an answer, but I have definitely felt that pain. If you had a good test coverage you could just make the first pass change and then keep iterating until the tests pass. Once Clojure 1.9 is out then (I believe) specs will be a better way to verify that all your data types are consistent and correct.

Hopefully someone else will have a better answer...

Pete

James Gatannah

unread,
Jul 27, 2017, 1:24:10 AM7/27/17
to Clojure
I feel your pain. I don't have an answer, but the basic point convinced me to write this: http://gatannahfiles.blogspot.com/2017/07/moving-away-from-guard-rails.html


Sean Corfield

unread,
Jul 27, 2017, 2:04:11 AM7/27/17
to clo...@googlegroups.com

This is awesome… made me laugh out loud!

 

Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

Matching Socks

unread,
Jul 27, 2017, 6:02:29 AM7/27/17
to Clojure
The attention-span constraint gives this challenge the aspect of a migration.  Consider it that way and solutions emerge.  In particular - attend to the interfaces between functions, and do whatever you like inside the functions.  You could adjust the program incrementally, whenever convenient. During the transition your program would remain operable.  There is more than one way to do this.  

In any case you would prepare by writing array-to-map and map-to-array translator functions (with very short names).  Then you could modernize a function by converting its arguments to maps on their way in, and converting outputs to arrays on their way out.  Eventually your program would contain a ton of a->m and m->a calls and you could decide whether you had the attention span to finish the job and get rid of them.  

A twist on this technique, if attention span permits, would involve less noise but more work at the outset:  adapt every function, all at once, with an adapter of its inputs, m?->a because initially all functions want arrays.  Then there will be no need to instrument the functions' outputs.  As you convert each function to desire maps instead of arrays, switch its m?->a to a?->m. Here the translators have question-marks because they should pass-through the parameter without changing it if it's already the right shape.  When every function has been switched over to a?->m, all the arrays will have vanished into the history books and you may remove the adapters.

Didier

unread,
Jul 29, 2017, 3:52:11 AM7/29/17
to Clojure
I feel your pain, but you kinda shot yourself in the foot from the get go. What you did is the same as if you had decided to use a Java List<String> to store your Account info.


It explains the idiomatic way to deal with application domain information, such as Account, Employees, etc. Spoiler, use records, or at least a map.

Now having said that, given a project across many namespaces, you will still have some of the problems of not having type declarations. Like if a function takes the record, but the argument is not called account, but say x instead, you would need to dig back to understand what this operates on. Spec could change that though.

Colin Yates

unread,
Jul 29, 2017, 5:52:29 AM7/29/17
to clo...@googlegroups.com
Maps and name spaced keys would be my recommendation for domain entities. Namespace keys because they help in isolating usages.

I thought there would be be many benefits to using records, particularly around protocols but I haven't felt the loss.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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

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+unsubscribe@googlegroups.com.

Didier

unread,
Jul 29, 2017, 2:51:43 PM7/29/17
to Clojure
> I thought there would be be many benefits to using records, particularly around protocols but I haven't felt the loss.

I like having the constructor ready made, and the extra documentation they provide on which key it has. Though spec remediates the latter a bit.

Other then that, they're faster, but that's not why I use them.

Reply all
Reply to author
Forward
0 new messages