Dependent Type Systems, take 2 #1200
Replies: 4 comments 23 replies
-
I am a relative new-comer to Idris, and its sibling languages. I am sharing my outsider perspective, which is closer to that of a C++ or Rust developer than that of a Haskell developer. I have been working with Idris for the past year out of sheer curiosity. I would agree with @dabrahams' criteria for considering a language dependently typed as the most crucial. Though to that I would add the following, in case the implication isn't clear:
I hope this post sets the stage for a more fruitful discussion. Short ExamplesBasic Contrived examples: in hylo-style syntax. (Apologies if what follows isn't quite right, happy to fix if corrected):
The canonical vector append example:
I would add to this is that there is actually no need for Idris-specific notionsJust to clarify what Idris2's feature set actually looks like.
As a Theorem ProverIt works via the curry-howard correspondence, and you're limited to constructive logic, insofar as I understand such things. A type is read as a proposition, and a function body that type-checks is considered a proof. In Idris, proofs are usually erased. A simple example of a proposition in Idris2 is It's a topic I am still actively studying. Hopefully someone more knowledgeable will chime in. Real-World Use CasesMy new favorite toy is: https://github.com/stefan-hoeck/idris2-sqlite3, which can satically check that queries conform to the database schema, correctly track types across joins, etc. Still a work in progress, but seems quite promising. In the mean time, https://github.com/stefan-hoeck/idris2-pack-db/blob/main/STATUS.md Is the database for Idris2's unofficial package manager, which has some good examples. They fall into some broad categories:
Other flavors of dependent types
ConclusionThis is getting long, so I'll leave it to future comments and other posters to elaborate on these topics, in particular how Idris2's features dovetail with each other, and what it might mean for hylo. |
Beta Was this translation helpful? Give feedback.
-
I just spent a little time looking through TDDI, and found an early example really concerning. Listing 1.4 begins: StringOrInt : Bool -> Type
StringOrInt x = case x of
True => Int
False => String
getStringOrInt : (x : Bool) -> StringOrInt x
getStringOrInt x = case x of
True => 94
False => "Ninety four" Now think about how to type-check To me, it's fundamental that definitions can be type-checked based on the declarations they use, without looking at the corresponding definitions. So this is an example of something I would definitely not want to support in Hylo. |
Beta Was this translation helpful? Give feedback.
-
*Note*: Sorry, made the mistake of replying via email and the formatting got utterly mangled, so I've had to manually fix this.
In Idris, HList is an inductive datatype.
Presumably that means HList<A, B, C> can be decomposed into A and HList<B,
C> or something? I assumed as much, but what's the relevance of that fact?
So, to be clear, it's more like `HList<[A, B, C]>`, there's an "index" of type `List<Type>`. Perhaps it's not as relevant as I first thought. But you're correct. It "decomposes".
What Idris cares about at compile time is some notion of syntactic
equality of which my understanding is still a bit shaky.
Presumably that means HList<s.map(ColumnType)> != HList<t.map(ColumnType)>
even if s == t, and that you'd need to rely on a runtime check to coerce
one into another?
Not necessarily. It depends on what `s` and `t` are, and what is known about them in the context of the expression. In some cases, `s` and `t` will indeed unify. In other cases they will not and Idris will throw up its hands. In any case, it's based on some form of local reasoning. There isn't a need for runtime checks or coercion.
it's a "structural" pattern match, with primitive recursion.
...
I've not heard the term structural recursion before but what I find
<https://craftofcoding.wordpress.com/2021/05/18/recursion-structural-versus-generative/>
when I look it up looks like it's orthogonal to what I understand
“compile-time evaluation” to mean. You could have both together or neither.
So I'm really unclear on what you're saying here.
I probably am getting a bit sloppy here. I have seen this terminology used online. I think you're technically correct.
I guess what I should be saying is something like: in Hylo, you'll have "compile-time evaluation" that allows for things like constant folding and evaluation of functions at compile-time.
Idris has "unification by normalization" or "unification by evaluation". But this doesn't necessarily mean that type-level terms get fully applied during type checking. Idris will start to normalize type terms until they unify, or don't.
> Where can I find some examples of non-trivial Hylo that's representative of
> your vision?
Nowhere, sadly, especially when it comes to things that brush up against
the issues of dependent typing. Most of what you'll see in our talks is
about the object and mutation models, which are basically orthogonal to
these issues. I'm glad you're raising them, because it's getting to the
point where we need to nail down the details.
Looking forward to it.
What I can tell you about our vision is:
- We intend to have generic value parameters, so Buffer<String, 10> is
a thing.
- Once a generic definition has passed typechecking independent of
specific generic parameters, we will never report type errors from within
that definition.
I think I see what your concerns revolve around. And now that I know what to look for, I'll keep my eye out for that sort of behavior in Idris.
- The complete set of constraints to prove any generic typechecks is
therefore required as part of its declaration.
I think Idris essentially also requires this, but I see your follow-up message and maybe I was wrong about this.
- We intend to be able to express things like the concatenation and
decomposition of buffers and HLists (which we call tuples). Combined
with the previous bullets that means constraints like "the length of this
tuple is nonzero" and “the length of this buffer is the sum of the lengths
of these other two“ need to be expressible.
Well, that does sound exciting!
Small point of clarification: Idris has a separate notion of tuples. And they are also inductive and heterogenous. But they don't have the list index. So, just to be clear, HList != tuples in Idris.
I can't speak for @kyouko-taiga <https://github.com/kyouko-taiga> , who
may have other concerns, but where I'm hesitant to go “full Idris” it
mostly has to do with the syntactic equivalence you're referring to above,
IIUC:
1. Given that we have mutation, syntactic equivalence is clearly not
sufficient for type equality because the meaning of Buffer<n, Int>
depends on when you capture n.
I would expect something like this to work:
```
fun makeBuffer(n: Size) -> Buffer<n * 2, Int> {
var x = Buffer<n * 2, Int> // syntactic equality is enough here
... initialize buffer ...
return x
}
```
I don't expect this to work, and I'd be rather impressed if it did.
```
fun makeBuffer(n: Size) -> Buffer<n * 2, Int> {
n *= 2
var x = Buffer<n, Int> // probably a type error here
...initialize buffer...
return x
}
```
1. What are the limits of our symbolic evaluation capabilities,
including our ability to reason about relationships between partially
evaluated expressions? One of the simplest examples might be, given a
generic parameter N, do we know that N + 7 > N + 5? Maybe we need to
be able to know *that*, but there's going to be a limit somewhere. One
step up from there: N.adding(7) > N.adding(5), given an appropriate
method on Int.
What idris does here again is "structural", because at the type level Idris doesn't use `Int`, but `Nat` -- which is a Peano number. So, all Idris needs here is pattern matching, recursion, and a few theorems, and that's sufficient for types like `Vect n a` in Idris. I doubt that's how you want to do this in Hylo.
1. It's a large design space and I'm keen to limit our capabilities to
a useful subset of the possiblities that doesn't overly complicate the
design and implementation.
So I sense that where you're at is: You feel like you've done your design work on Hylo, and are ready to dive into the rest of the implementation. But you are taking a "last look around" to make sure you don't want dependent types. I feel like it's now or never. If you close the door on dependent types now, you'll have a hard time getting them into the language later if you change your mind.
If you allow for the syntax now, I think you can still restrict usage to the cases you want to support. It's just that some things that would have been syntax errors before get caught in a subsequent pass over the parse tree. But then, at least, you're free to gradually relax the rules as the language evolves, without introducing new syntax.
|
Beta Was this translation helpful? Give feedback.
-
My understanding, from reading what @emdash says about |
Beta Was this translation helpful? Give feedback.
-
I'm opening this thread so we can have a more-focused discussion about dependent type systems than the one that arose in #1126, which suffered quite a bit because we failed to frame the discussion in the right way and ask the right questions.
To be clear, we do not consider a type system to be dependent merely by virtue of the fact that numbers (and perhaps other non-type values) can participate in types, as in
FixedSizeBuffer<String, 4>
. We plan to support that capability—and all the fun use cases that ensue, such as dimensional analysis—in Hylo as a matter of course. What we mean by "dependent type system" is defined by this capability:A function exploiting that capability might be written like this in Hylo:
Notice that
n
is passed as a runtime parameter tomakeBuffer
but becomes part of the type ofmakeBuffer
's result,FixedSizeBuffer<String, n>
.We are not interested in arguing over this definition of “dependent type system”; please feel free to use the term “Hylo-dependent type system“ to describe the above capability instead if you disagree with our definition.
This capability is one of the distinguishing features of languages like idris2. We are skeptical that supporting this capability in Hylo would be worth its complexity cost, in part because we are not aware of any compelling use cases. But we realize we are not well-versed in what you can do with it, so we'd like to hear about those use cases, and in particular (but not exclusively) how they might be used to build automated proof systems, as @GunpowderGuy suggested was possible.
Thanks everyone!
Beta Was this translation helpful? Give feedback.
All reactions