Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
What I want from a Type System
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
@frenchy64

This comment has been minimized.

Copy link

commented Dec 3, 2016

RE: 2) Are you saying you need type alias support, and perhaps for the global type inference to infer the name for you?

@halgari

This comment has been minimized.

Copy link
Owner Author

commented Dec 3, 2016

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).

@changlinli

This comment has been minimized.

Copy link

commented May 30, 2017

I stumbled across this while on a random trawl through the interwebs. It looks like Purescript or Elm (basically any language with record/row types) fits the bill for you and you might want to give it a shot.

  1. Yes for both Purescript and Elm
  2. type ProductID = MkProductID Int and then hide the constructor (which is MkProductID).
  3. Extensible records in both Purescript and Elm address this
  4. Yep happens by default in both
  5. Certainly at the very least unions are supported just by composing the records as you would any other type. The Elm link has a direct example of this. Note that order of the types does not matter in that example (as it shouldn't).

Many other statically typed languages have row/record types as third-party libraries as well, but the quality of those differs by ecosystem.

@yairchu

This comment has been minimized.

Copy link

commented Jun 21, 2019

  1. 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.

I think that a structural type system with named fields also solves this. The value type inside may be structural, but the fields will be named "ProductID", "ProductName".

@madhavajay

This comment has been minimized.

Copy link

commented Jul 1, 2019

I wonder how much of what you want is achievable with "Key Paths" in Swift.
https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift

@barrucadu

This comment has been minimized.

Copy link

commented Jul 1, 2019

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 a ProductID but that Int32 is a PersonID?

@mavu

This comment has been minimized.

Copy link

commented Jul 1, 2019

I'd be interested to know your opinion of the Crystal type system.

@sdegutis

This comment has been minimized.

Copy link

commented Jul 1, 2019

(Semi-formatted version:)

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

@sdegutis

This comment has been minimized.

Copy link

commented Jul 1, 2019

@halgari TypeScript provides most of what you're looking for btw.

@williamoliveira

This comment has been minimized.

Copy link

commented Jul 1, 2019

TypeScript is structural

@corporatepiyush

This comment has been minimized.

Copy link

commented Jul 1, 2019

Advance types introduced a while back in Typescript can do the job.

https://www.typescriptlang.org/docs/handbook/advanced-types.html

@halgari

This comment has been minimized.

Copy link
Owner Author

commented Jul 1, 2019

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 a ProductID but that Int32 is a PersonID?

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.