New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type-level literals as a separate language extension (under review) #536
base: master
Are you sure you want to change the base?
Conversation
Seems entirely plausible and uncontroversial to me. |
With GHC-9.2.4 just import GHC.TypeLits (Nat)
import Data.Kind (Type)
data Vector :: Nat -> Type -> Type where requires For some reason GHC-9.4.1/9.4.2 doesn't, which seems to be a bug to me (I cannot find anything in https://downloads.haskell.org/~ghc/9.4.1/docs/users_guide/9.4.1-notes.html justifying why this is now accepted with just Especially if above is not an intended change and indeed a bug, it doesn't look you can write much programs with just |
I guess that now that kinds are types, promotion doesn't do anything to the type -- it's just data constructors being promoted to type constructors. That's certainly how it looks in the code, though the User's Guide still talks about kinds in places. |
But if kinds are types, why would data Vector :: Natural -> Type -> Type where
Nil :: Vector 0 a
Cons :: a -> Vector n a -> Vector (n+1) a be ok with just data Foo :: Bool -> Type where
Foo :: Foo True
NotFoo :: Foo False will require "more powerful" Does #106 doesn't specify whether |
#106 specifies that the constructors introduced by a As for the first question, that is indeed what is proposed here. It would be useful, because then one could use literals at the type level without giving up namespace control. |
@RossPaterson I see, thanks for the explanations. To me it seems that implementing this proposal would be much more valuable when #106 is implemented. |
Indeed. I have an implementation of #106 that just needs a bit of finishing, and that was the motivation for this proposal. |
|
||
data Vector :: Natural -> Type -> Type where | ||
Nil :: Vector 0 a | ||
Cons :: a -> Vector n a -> Vector (n+1) a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cons :: a -> Vector n a -> Vector (n+1) a | |
Cons :: a -> Vector n a -> Vector (n + 1) a |
Just a style change, but usually operators have spaces around them.
I still don't understand the semantics of this even with #106. Are we going to use Morally, I think literals desugar to some "secret" data constructors. This is especially true if/when for Dependent Haskell we make type level literals use the same overloading as regular ones. So while I do think it's fine to enable the syntax without enabling the data kind semantics, I feel like every use of that new syntax (except for maybe |
If there was a way to do something like |
I think the technical effect of this proposal is quite clear, and already described in detail in the "Type-Level Literals" section of the User's Guide. (As for #106, I believe this is the same philosophical objection to allowing literals like |
This proposal appears to be entirely about namespaces, just like #106. Q: How is promoting Both #106 and this proposal attempt to carve out a language subset where type-level programming is possible but terms and types are kept separate. Personally, I would find this subset too limiting and keep reaching for the full power of |
Summary: the only objection was a philosophical one to allowing literals like @nomeata - I believe this proposal is ready for review. |
I've made a recommendation: https://mail.haskell.org/pipermail/ghc-steering-committee/2023-March/003165.html The proposal is now under committee review. |
This proposal strikes me as an attempt to have GHC offer support for a particular stylistic choice. (It's like #270 in this regard.) As our language grows -- and we programmers become increasingly conditioned to rely on our tools -- this kind of feature request makes a lot of sense. However, I don't see the need for it to lead to a language extension. Instead, if GHC had a warning for a constructor used in a type, users could choose to set that to Werror and achieve the goals of this proposal, no? And actually, GHC already has such a warning! It's called Question for the crowd: Does my A little background: I believe that each language extension imposes a tax on our ecosystem, in that each extension doubles the number of languages we call Haskell. Haskellers have to learn how to read all of these languages, even if they write in only some of them. I claim (without evidence) that each language extension thus increases the cognitive burden of using Haskell (even if only slightly), and thus we should be parsimonious about them. (If anyone is tracking, this stance of mine has evolved over time; I was more sanguine about extensions in the past.) |
It's a choice to use a subset of an extension, like In this case the type-level literals subset does not interact with the data constructor promotion provided by the rest (though they have similar purposes). I assume it is for this reason that they are already documented in separate sections of the User's Guide. The warning approach may be logical when starting from |
If I someone from the crowd may provide a sliver of evidence from personal experience ... What makes the cognitive burden is the extra syntax and semantics -- whether that's two pieces of syntax under a single extension, or under two extensions, it's the same burden. What's particularly burdensome is different semantics with no syntactic clue: There's further burden because not all extensions are simply orthogonal or simply a widening of an existing extension. Example: FunDeps interact very badly with Overlapping instances. It's great to have the per-instance So for this particular proposal, it would help me reading code to know whether it's using only |
In that particular case: no, because
A fair point. My stance is also informed by my general opinion that we've gotten language growth and evolution a little bit wrong. I think all extensions that just enable new syntax (without changing the meaning of any existing programs) should be warnings. (This can be made fully backward compatible by having the existing extensions just twiddle warning flags.) The "extension" idea makes sense if we think about some common core of Haskell shared across compilers and defined in a standard. But that's not the world we're in. Instead, we have a compiler capable of dealing with quite a large language, where users restrict themselves to subsets of the large language by turning off language extensions. GHC uses all of its power to accept user programs, stopping only late-ish in the process when it discovers that a language extension is disabled. So I'd be happy for all of My end goal here is to try to reduce complexity by stating we have one Haskell. Users are free to restrict themselves to a subset of that language -- and GHC should actively support such subsetting. But it's still one language. |
I think *all* extensions that just enable new syntax (without changing
the meaning of any existing programs) should be warnings.
Yes, I think that is a perfectly defensible position. But if we do it,
let's be consistent about it.
A proposal, perhaps?
Simon
…On Sun, 19 Mar 2023 at 20:58, Richard Eisenberg ***@***.***> wrote:
@RossPaterson <https://github.com/RossPaterson>
It's a choice to use a subset of an extension, like GADTSyntax vs GADTs.
Would a warning be a workable alternative in that case?
In that particular case: no, because GADTs implies MonoLocalBinds which
alters the type inference algorithm. But in general, yes.
The warning approach may be logical when starting from DataKinds, but it
seems conceptually complicated when coming from the other direction. To
understand a module with TypeLevelLiterals, one would need only to
consult the type-level literals section of the *User's Guide*. There is
no need to consult the data constructor promotion section to learn about a
feature one didn't intend to use, and how to turn accidental uses of it
into warnings.
A fair point. My stance is also informed by my general opinion that we've
gotten language growth and evolution a little bit wrong. I think *all*
extensions that just enable new syntax (without changing the meaning of any
existing programs) should be warnings. (This can be made fully backward
compatible by having the existing extensions just twiddle warning flags.)
The "extension" idea makes sense if we think about some common core of
Haskell shared across compilers and defined in a standard. But that's not
the world we're in. Instead, we have a compiler capable of dealing with
quite a large language, where users restrict themselves to subsets of the
large language by turning off language extensions. GHC uses all of its
power to accept user programs, stopping only late-ish in the process when
it discovers that a language extension is disabled.
So I'd be happy for all of DataKinds to be a warning -- or indeed two
warnings, one for uses of a constructor in a type, and the other for uses
of a literal in a type. These warnings can be errors by default, and we can
clarify in the user's guide that a certain section applies only when a
warning is off.
My end goal here is to try to reduce complexity by stating we have one
Haskell. Users are free to restrict themselves to a subset of that language
-- and GHC should actively support such subsetting. But it's still one
language.
—
Reply to this email directly, view it on GitHub
<#536 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEOY63MOBTLVLXA74DLAH3W45XQFANCNFSM6AAAAAAQD4SQPI>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
In due course -- I'm essentially arguing my point of view in the parallel conversation about understanding what an extension means. I don't think a proposal pushing this one point will be all that useful until the committee can come to some agreement about why we have an extension system and how it should evolve. |
Yes but are you suggesting that we simply park this proposal until that larger conversation converges? |
[I appreciate we're still holding our breath for Richard to adumbrate some approach.] Reduce complexity for the compiler maintainers? Or for 'the crowd' wanting to write programs in Haskell? Yes the compiler has to cope with the interactions of any/all extensions. Even if I restrict the code I write to a narrow dialect, I won't necessarily know/understand what extensions are used in modules I import -- indeed they might deliberately hide their implementation. Using GHC is already problematic for those learning Haskell. Most of the educational materials introduce an unrealistically out-of-date version of Haskell; then explain extensions (via the Do those outside the circle of GHC maintainers think we have "one Haskell"? For myself, I think FunDeps are a different Haskell vs Type Families; I think I might find [**] The |
I want to defend the Haskell introduced by most educational materials. Yes, it's a long way behind GHC, but it is a subset that is fairly precisely specified, mostly stable, usable for lots of applications, and an achievable base camp for learning the newer features. Without it, GHC Haskell would be too daunting. Once you've reached this base, it's helpful to be able to turn on features incrementally as you explore. Different users will be interested in different extensions. Addition seems more workable than subtraction here. I don't think the delay in implementing |
I do not believe this is necessarily true. A hypothetical GHC that does not have language pragmas would be in the same boat as every other language wrt "advanced" features: either you use a feature, or you do not. Having to preface your file (or For instance, typescript has some pretty advanced type features, especially by web standards. I do not believe anyone, including beginners, would be helped by requiring users to write Of course, having new features unconditionally available can degrade the user experience when they worsen error messages or change the semantics of an existing program. Certainly the latter should be guarded behind a flag, and probably the former too (though the real solution is to fix the error messages). Thus I agree that the extension mechanism makes sense in some circumstances, but it also imposes a cost, and I think it is perfectly reasonable to consider a world where new feature do not necessarily have to be guarded behind an extension. |
@tbidne (those are important points for Richard's 'proposal perhaps' -- when we get to it. I'll try not to clutter this
Sadly, there are some features of Haskell that come without distinctive syntax. The only way the compiler can tell whether this is an intended "advanced" piece of code vs a newbie's error is from the flag setting. (
I don't see 'every other language' as trying to be all of a flagship for learning a still fairly rare programming paradigm (Functional Programming), an experimental testbed for advanced type theory, an industrial-strength compiler for high-assurance computing. (Could a newbie reasonably just jump into typescript? Or would they be expected to cut their teeth first on javascript?) On the "experimental testbed", an extension documented as not-yet-stable means the design might change in future releases, in ways that make previously-accepted code invalid. In theory the whole feature might get withdrawn (although this has never happened in practice). So your Addit: hehe you might be hoist by your own petard with that example. Typescript docs tell me 'Intersection types' are no longer a thing/"This page has been deprecated". Looks like the |
Looking at the motivating example in #106,
So with It seems (to be precise, I'm at GHC 8.10, and using an
Yes, I thought there was a strong move (somewhere) towards that. And that
I think what Ross is proposing here is that it's legitimate to insist promoted data constructors not be in scope at all -- not even if ticked. And having just reminded myself with this exercise/my poor monkey brain that they're rather fiddly, I agree they're something to be kept away from newbies. |
The intersection concept still exists, though your point regarding deprecation / experimentation is well-taken. |
Reflecting a bit on this, and re-reading the whole thread
On balance I now think that the (modest) pain isn't worth the (extremely modest) gain. I have seen no one saying "this change would really make my life better". |
@simonpj as I've said before I would really find this change useful, because when combined with #106, it gives me a subset of the type language that can express all the things that I need (i.e., explicitly defined kinds from #106, and type level literals from this proposal). Personally, I have never found the design of data T = MyInt | MyBool
type MyInt = 'MyInt
type MyBool = 'ByBool
data TRepr :: T -> Type where
MyIntRepr :: TRepr MyInt
MyBoolRepr :: TRepr MyBool Note that the type data T = MyInt | MyBool
data TRepr :: T -> Type where
MyInt :: TRepr MyInt
MyBool :: TRepr MyBool For an actual example from our code base, have a look here: https://github.com/GaloisInc/crucible/blob/master/crucible/src/Lang/Crucible/Types.hs Now, of course, thanks to Ross we have #106 implemented, so I could just write the code in the nicer form above and I am really happy about it! It just seems quite unfortunate, that if I want to index my types with type level literals, I have to turn on |
Thanks @yav that's really helpful. We have one person at least who positively wants this. Can you help me a bit more: Since we now have |
The cost of enabling a feature one isn't using is a loss from the error space. My coding mistake, e.g. getting names mixed up and using a data constructor where I meant to use a type name, is no longer a simple compiler error; now it is reported as an erroneous use of a feature I haven't learnt yet, or perhaps accepted as an accidental and unintended correct use of that feature. |
@yav I can hardly see the point of the extra declarations in your example data T = MyInt | MyBool
type MyInt = 'MyInt
type MyBool = 'ByBool
data TRepr :: T -> Type where
MyIntRepr :: TRepr MyInt
MyBoolRepr :: TRepr MyBool Why write data T = MyInt | MyBool
data TRepr :: T -> Type where
MyIntRepr :: TRepr MyInt
MyBoolRepr :: TRepr MyBool Now it's the same size as your example that uses type data T = MyInt | MyBool
data TRepr :: T -> Type where
MyInt :: TRepr MyInt
MyBool :: TRepr MyBool With the only difference being that the distinction between As far as I'm concerned, using a distinct name for But suppose you are unwilling to use a separate name for In other words, the motivation behind this proposal is to improve error messages in code that heavily relies on punning. Is that a correct summary? If so, could it be incorporated into the "Motivation" section of the proposal, with examples of unwanted error messages and desired error messages? Also, the "Alternatives" section should mention that a pun-free naming convention is, in fact, an alternative (it doesn't imply it's a good one, but it's there) |
I don't think punning is the issue. Suppose I'm using a type like
and I get the names mixed up and use the data constructor where I meant to use the type name:
Without |
Thanks for clarifying. Yes, I can see how error messages are different with In any case, the Motivation section should be very clear on the benefits of splitting off Furthermore, is it about error messages only or are there other benefits? And would it be possible to improve error messages without reorganizing extension flags? |
This is a proposal to introduce a language extension
TypeLevelLiterals
(implied byDataKinds
) to turn on the features in the Type-Level Literals section of the User's Guide without necessarily turning on datatype promotion.Rendered