Konrad Jamrozik
Konrad Jamrozik

Reputation: 3556

Is there a way to create a constrained data type in Clojure?

As an example, a string that contains only a valid email address, as defined by some regex.

If a field of this type would be a part of a more complex data structure, or would be used as a function parameter, or used in any other context, the client code would be able to assume the field is a string containing a valid email address. Thus, no checks like "valid?" should be ever necessary, so approach of domaintypes would not work.

In Haskell this could be accomplished by a smart constructor (section 1.2) and in Java by ensuring the type is immutable (all setters private) and by adding a check in the constructor that throws a RuntimeException if the string used to create the type doesn't contain a valid email address.

If this is impossible in plain Clojure, I would like to see an example implementation in some well known extensions of the language, like Typed Clojure.

Upvotes: 1

Views: 208

Answers (1)

JustAnotherCurious
JustAnotherCurious

Reputation: 2240

Ok, maybe, I understand now a question and I formulate in the comment my thoughts not really well. So I try to suggest an admissible solution to your question and then I try to explain some ideas I tried to tell in the comment.

1) There is a gen-class that generates compiled bytecode for a class and you can set constructor for the class there.

2) You can create a record with defrecord in some namespace that is private by convention in your project, then you create another namespace with public api and define your factory function here. So the user of your public namespace will be able to call only public functions of your public namespace. (Of course, he can call also private ones, but with some another code)

3) You can just define a function like make-email that will return a map. So you didn't specify your data structure anywhere.

4) You can just document your code where you will warn people to use the factory function for construction.

But! In Java if your code requires some interface, then it's user problem to give to your code the valid interface implementation. So if you write even a little bit general code in Java you already has lost the property of the valid email string. This stuff with interfaces is because Java is statically typed language.

Clojure is, in general, dynamically typed, so the user, in general, should be able to pass arbitrary data structure to arbitrary function without any type problems in compile time and it's his fault if he pass the wrong data. That makes, for example, this thing possible: You create a record and create a factory (constructor) function. And you expect a record to be passed in your code. But the user can pass a map with the same keys as your record fields names and the code will work.

So, in general, if you want the user of your code to be responsible for passing a required typed in dynamically typed language, then it cost nothing for user to be responsible for constructing it in a correct way that you provide to him.

Another solutions are: User just write tests. You can specify in your api functions :pre and :post conditions to check the structure. You can use typed clojure with the ideas I wrote above. And you can use some additional declarative libraries, like that was mentioned in the first comment of @Thumbnail.

P.S. I'm not a clojure professional, so I could easily miss some better solutions.

Upvotes: 1

Related Questions