3

Let's take Haskell as an example, as it gets the closest to what I'm about to describe of the languages I know.

A type, Int for example, can be viewed as the set of all possible values (of that type). Why is it that we only get to work with very specific sets? Int, Double, etc... and not with all their subsets in the type system.

I would love a language where we can define arbitrary types like Int greater than 5. Are there examples of such languages? If not, why not?

Syd Kerckhove
  • 783
  • 5
  • 19
  • 3
    *Arbitrary* subsets are not decidable. A cassic example is the subset of positive integers specified by "is an encoding of a program that terminates in finite time for all inputs" (this is a variant of the halting problem). If you wanted that subset as a type, the compiler can't even decide whether a program with full type annotations is correct. –  Apr 06 '15 at 13:20
  • Does it suffice to replace `arbitrary subsets` with `decidable` or `easily-computable` subsets? – Syd Kerckhove Apr 06 '15 at 13:21
  • 1
    It may suffice to make type checking decidabe. But the result will still be as complicated as dependent types (in fact, there's probably a dependent type theory that expresses exactly those types). –  Apr 06 '15 at 13:25

3 Answers3

8

You are looking for Dependent types. Idris, Agda and Coq are well known in this category.

Sibi
  • 47,472
  • 16
  • 95
  • 163
  • Are those used in practice? If not, why not? – Syd Kerckhove Apr 06 '15 at 13:13
  • 5
    @Syd: Try programming something in one of those languages. It's not, like, unfeasible, but you start thinking so much about proofs that you hardly get to the actual programming part anymore... at least that was my experience so far, though I consider myself rather tenacious in making everything mathematically beautiful etc.. That would be why they're hardly used in practice. Perhaps dependent type should actually be used more, but I doubt Agda, Idris or the like will make it in the forseeable future. Coq has a head-start; and Haskell itself gets ever more “dependent” with GHC extensions. – leftaroundabout Apr 06 '15 at 13:19
  • 3
    There are smaller systems than full dependent types that do what's desired here. Refinement types a la Liquid Haskell, for instance. – Carl Apr 06 '15 at 13:22
  • @Carl: I think that deserves to be an answer of its own. – Joachim Breitner Apr 06 '15 at 13:54
  • 1
    @leftaroundabout, have you read [this](http://stackoverflow.com/a/13241158/3237465)? If you use a dependently typed language and don't need dependent types for solving some problem, just do not use them. You can turn off a termination checker, use `Type:Type` (in Agda) (Coq and Idris have typical ambiguity, which is almost invisible for a user), postulate `undefined` and do whatever other stuff to get just a usual functional language, only quite more powerful. – effectfully Apr 06 '15 at 14:54
  • 1
    On the other hand it's a [hasochism](https://personal.cis.strath.ac.uk/conor.mcbride/pub/hasochism.pdf) to enforce certain invariants in a poorly typed language like Haskell. Besides, Agda also has brilliant module system, type classes machinery and reflection opportunities. Agda is a bit impractical at the moment, but IMO Idris should replace Haskell. Just have a look at the coolest [effects](https://www.cs.st-andrews.ac.uk/~eb/drafts/effects.pdf) system ever. – effectfully Apr 06 '15 at 14:55
  • 1
    Joining user3237465 here. Having proper dependent types ceteris paribus subtracts nothing from usability, performance, maintainability, ease of learning the language, etc.. Current dependent languages are obscure in software development for unrelated reasons. Also, supporting GHC-level verification capabilities is much simpler with a full-dependent core language than with GHC's creaking many-times-extended core. – András Kovács Apr 06 '15 at 15:39
  • 1
    @AndrásKovács: yeah. I suppose most programmers give just up when, after several attempts to find a backend that actually gives “something that actually does something!” and quite a while of compiling stuff called `Relation.Nullary.Decidable`, their Hello World Agda program drives the compiler into consuming all memory and hangs the system... – leftaroundabout Apr 07 '15 at 00:14
  • FTR, it wasn't my purpose to give dependent types negative advertisement. I have now given Idris a shot, and was delighted to find out it really feels much more lightweight than Agda or Coq, allows compiling simple programs to actual executables with no extra hurdles at all, and overall looks very familiar for a Haskeller – but at the same time it allows value-level magic in types, which would be really much uglier in Haskell. — Again, that's nothing said against Agda (which includes many ideas that I find sound really amazing), but is just too unwieldy to be attractive to most programmers. – leftaroundabout Apr 09 '15 at 13:31
2

You can actually mostly define that in Haskell because it's basically an Int plus some semantics. For example, you have to decide what you're going to do with subtractions that go beneath the threshold, like what (-) 6 7 gives. (A common convention with Peano arithmetic is to give 0 -- so in this case it would return 6, the least value of the system.) You also need to choose whether you're going to error on a fromInteger 3 or else whether you're going to store, say, newtype IntGT5 = IntGT5 (Maybe Int) instead of newtype IntGT5 = IntGT5 Int. You have to write all of the typeclass instances yourself, and once you've done that you'll have an appropriate type.

If you've got an abiding interest in this problem, two things to pay attention to are liquid types and subtyping. Let me tell you a little about the latter.

Alan Kay invented OOP to be something different than what it is (he wanted every program to be written as a network of communicating computers), but what it turned out to be in the modern world is basically a way to do complex subtyping. "Duck typing", for example, is about creating an "intersection type" of a bunch of really general types (like `things with a "waddle" method, things with a "quack" method) which other types are subtypes of. So subtyping is very naturally OOP.

The thesis I've linked you to points out another interesting thing about subtyping: you can use subtypes to erase the distinction between type and value. Of course, making types into values does not need subtyping; it is e.g. already the case in the Python language, where you can call type(x) to get an object which represents the type of the object. But the interesting thing is that as subtypes go, you can just define a type 3 :: Int (3 of kind Int) which is the type of all Ints which are equal to 3. It is in essence a unit/void type which is a subtype of a bigger class. By blurring the distinction between 3 of kind Int and 3 of type Int you get every value being a type as well. You could then do something JSON-like with this approach, where {x: Int, y: 3 :: Int} is a type of objects containing two properties x and y where x is any Int and y is the integer 3 :: Int.

CR Drost
  • 9,637
  • 1
  • 25
  • 36
0

The Ada language supports ranged numeric types. I can't really comment intelligently on this language, but I've been led to understand by folks who know it that the range checks are enforced by runtime checks inserted by the compiler.

Relational database theory also has concepts that tie to this. The set of values that an attribute can take is its domain, which is a function of its data type and the constraints on it. Actual RDBMSs often do not implement these concepts in their full generality, though.

Dependent types are a much more general system that can encode this sort of constraint, and many others. My capsule explanation of dependent types is like this: they are a way of designing a programming language that contains proofs as first-class values. A proof is a value that, by virtue its existence and the rules that it obeys, guarantees the truth of a proposition (a.k.a. "sentence," "statement," etc.).

So under dependent types, you can have a type x > 5 whose values are proofs that x is larger than five. If you have an actual value of this type, then it really is true that x is larger than five, and thus the code where this proof is in scope may safely proceed under that assumption. You can package up the number and the proof together using a dependent sum type that we can notate as ∃x : Int. x > 5, whose values are pairs such that:

  1. The first element is an integer x
  2. The second element is a proof of x > 5
Luis Casillas
  • 29,802
  • 7
  • 49
  • 102