-
-
Save halgari/f431b2d1094e4ec1e933969969489854 to your computer and use it in GitHub Desktop.
The question was asked why I (as a programmer who prefers dynamic languages) don't consider static types "worth it". Here | |
is a short list of what I would need from a type system for it to be truely useful to me: | |
1) Full type inference. I would really prefer to be able to write: | |
(defn concat-names [person] | |
(assoc person :full-name (str (:first-name person) | |
(:second-name person)))) | |
And have the compiler know that whatever type required and produced from this function was acceptible as long as the | |
input type had :first-name and :second-name and the output had :full-name. | |
2) Value types in structures would need to be nominal, not structural. I really don't care if I get a tuple of | |
[Int32, String], I very rarely care what the machine level types are, what I really want is [ProductID, ProductName], | |
what the underlying types are doesn't really matter to me. | |
3) Collections of named value types (call them structs, or classes, or whatever) would need to be inclusively typed, | |
not exclusive. That is to say I sould be able to say: | |
(defstruct Person [first-name last-name]) | |
The "is-a" check for this class/struct would then need to work on any type that included the first-name and last-name | |
values. Including any classes/structs that included other parameters. In the `concat-name` function above, the input | |
and the output of that function would both return true for (is-a? Person x), since both the inputs and the outputs | |
include the correct values. | |
Perhaps then we should say that key/value structures are structurally typed (but allow for extra items), while other | |
types are nominally typed. | |
4) This could get messy very quickly, so it would be important to have all these structural members be namespaced as | |
well. :first-name isn't good enough, we would need to be able to specify the name as :person.name/first and | |
:person.name/last. Thus I could later add a :product/name and it would not conflict with any other definition of :name. | |
5) Now I would still need basic set logic for these types so I could say (deftype Teacher (union Person Employee)) | |
So, why do I need all this? Well a lot of work I do has to do with very messy business logic. Situations were a | |
company wants to convert their receipt system, trading engine, or something of that nature into computer code. Often | |
this data is very complex and hard to define. Situations where a importer may bring in 100 fields but we only care | |
about 10. Thus it's often important to require that certain data match a model, but allow for extra data to flow | |
through a system without much effort. In addition, the problem with nominal k/v types is that I often find myself | |
converting between two types simply because function A requires a DBPerson, and function B requires a UIPerson. If | |
the keys and values are the same, they should flow through, while still keeping me from saying PersonName = ProductID. | |
All this would also need to require a minimal amount of typing from me, I can't spend time writing converters from | |
DBPerson to UIPerson, and the conversion of one of these types to the other shouldn't take a lot of compute power since | |
I may be performing millions of these conversions every second. | |
In the end, no static type system I've seen provides all this for me. Some come close, but none are perfect. So I | |
stick with dynamic languages (Clojure if you haven't guessed), and leverage Spec (http://clojure.org/about/spec) which | |
not only provides validation for me where I want it, but also offers QuickCheck like generative testing, and destructuring. | |
Perhaps someday someone will provide all that for me, with a REPL, since I really need that, but until then I'll stick | |
with data-driven dynamic Clojure. | |
Thanks for taking the time to read this, hopefully it was helpful. | |
Timothy Baldridge |
TypeScript is structural
Advance types introduced a while back in Typescript can do the job.
https://www.typescriptlang.org/docs/handbook/advanced-types.html
Type aliases would be fine, as long as they don't type check against other aliases with the same base type. So if I have:
(deftype ProductID Int32)
(deftype PersonID Int32)
A tuple of (ProductID) should not implicitly convert to a tuple of (PersonID).Doesn't that go against the full type inference you want? How is the type system to know that this
Int32
is aProductID
but thatInt32
is aPersonID
?
By abstracting machine representation away from the name of a type. The fact that ProductID and PersonID are both integers should not have any impact on the correctness of a program. The fact that I'm saying ProductID x = personid
does as I'm coercing one type into another.
@halgari TypeScript provides most of what you're looking for btw.