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
Unlifted Datatypes #265
Unlifted Datatypes #265
Conversation
55eb3e3
to
55c86b4
Compare
Thanks!
More syntactic overhead, but also more composable! I don’t need At first glance, this makes me lean much more towards the |
Ah, but this allows you to implement |
Yes, the only real drawback compared to implementing |
I assume that we want to |
A bit off topic: I hope someday every data type can have an associate unboxed version connected by a type family (let's call it
Then |
What do you mean by cross-kind? It's just unpacking the regular constructor fields like before, that's entirely orthogonal to the semantic concern of there being no bottom as an inhabitant of that constructor's type. Unless you mean the implicit unpacking of constructors like
There are plans for having levity polymorphic (unlike the current meaning of levity polymorphism, which is more like representational/shape polymorphism) data types. See https://gitlab.haskell.org/ghc/ghc/wikis/unlifted-data-types#proposal-b3-levity-polymorphic-data-types for a design I would aim at. |
Generally I like the idea here, and I don't think it'd be all that hard to implement. We already have unlifted data types like On syntax, a postfix kind signature is rather a quiet signal for such an important change. I'd suggest using a prefix keyword:
(This "keyword" would have significance only after |
Well I'd want data Strict :: Type -> TYPE 'UnliftedRep where
Strict :: {-# UNPACK #-} !a -> Strict a data UPairStrict :: Type -> Type -> TYPE 'UnliftedRep where
UPairStrict :: {-# UNPACK #-} !a -> {-# UNPACK #-} !b -> SPair a b to work somewhat similarly. The second cannot be conceived of as a one-way coercion. @simonpj But see how https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0098-unlifted-newtypes.rst#id4 points the way to levity-polymorphic newtypes. I think the kind signature is more in tune with that. |
@Ericson2314 If I understand correctly, then I don't think what you want to have is possible. You can't unpack polymorphic fields, at least not without specialisation of the data type, at that's certainly out of scope for this proposal. To exemplify, what is the static offset of the
This is probably most similar in spirit to turning a lifted data type with a single strict boxed field (So as |
The proposal mentions that we could have an unlifted (aside from this I'm strongly in favour of the proposal) |
@simonmar Yes, currently we have no means of encoding the necessary levity polymorphism. See https://gitlab.haskell.org/ghc/ghc/wikis/unlifted-data-types#proposal-b3-levity-polymorphic-data-types in that regard. I think levity polymorphism (i.e. lifted vs. unlifted, but still boxed pointer to GC'd heap, as opposed to the current interpretation, which is more like representation/shape polymorphism) should be discussed in a subsequent proposal. Or maybe it should be part of this proposal, I've no idea what the guidelines are wrt. incrementality of GHC proposals. But I'd rather implement it in separate steps. |
@sgraf812 Right, I completely forgot about in normal cases need a concrete constructor to unpack, so no polymorphism there. But perhaps we do anything special with Back to my original, question I assume plain: data Strict :: TYPE 'UnliftedRep where
Strict :: {-# UNPACK #-} !Concrete -> Strict a data UPairStrict :: TYPE 'UnliftedRep where
UPairStrict :: {-# UNPACK #-} !Concrete0 -> {-# UNPACK #-} !Concrete1 -> SPair a b works fine? I do really really want "template" / "monomorphized" polymorphism, but I think should be separate proposal. There is much to decide about that alone. |
Not knowing about this proposal I just added #268. I noticed that we already have support for unlifted types with just one constructor via newtype and that it can be extended to more types with possible compiler simplification by implementing case analysis in two ways, one for |
Coercing between unlifted and lifted rep are not actually essential to the proposal. Levity polymorphic coercions might be interesting to have in Core to support a zero-cost I don't see how implementing case analysis in two ways leads to compiler simplification. Note that |
What are the semantics of Strict?
…On Wed, 4 Sep 2019, 14:53 Sebastian Graf, ***@***.***> wrote:
Coercing between unlifted and lifted rep are not actually essential to the
proposal. Levity polymorphic coercions might be interesting to have in Core
to support a zero-cost Strict data type, but that's irrelevant to Haskell
as a language. It *might* become relevant later on when we want to have
levity polymorphism (in the sense of lifted vs. unlifted, not boxed vs.
unboxed) in the surface language.
I don't see how implementing case analysis in two ways leads to compiler
simplification. Note that case already works on terms of unlifted rep, so
my proposal wouldn't actually imply a change to semantics at all.
Everything is already in place! We just offer the needed vocabulary to the
programmer to define her own unlifted data types.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#265?email_source=notifications&email_token=AHZ4ZZLHIAIPYR2NNVKZDDLQH64UDA5CNFSM4IPJ5H62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD53UOBI#issuecomment-527910661>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AHZ4ZZJKYQDCMVQOPKBN5LLQH64UDANCNFSM4IPJ5H6Q>
.
|
Hmm. I felt a strong urge to disagree but couldn't come up with an example. So perhaps eliminating indirections for types like
Yes, it should just unbox the fields, as today.
The best bet is probably a |
This is the definition. Its semantics follow straight-forward from the semantics of unlifted expressions (i.e. everything unlifted is evaluated by default, because that's the only way to make sure there is no bottom anywhere). See also #257. |
can I pattern match on
what is How do you force types with two constructors? The attempt to force will be |
Oh I see you can pattern match as above, the tree example is missing parentheses but I'm still curious about the matter of |
I updated the Change Specification to be more formal. There's an open TODO discussion concerning syntax, so I expect this thread to be on fire very soon 🔥 |
Great proposal! I wish it at least sketched the path to the zero-cost The worker-wrapper example is what motivates me write this. Here's the status quo example from the proposal.
To me it's a bit surprising that the type of |
I have just realised that in this accepted, and implemented proposal we have done something entirely new. Consider this (from https://gitlab.haskell.org/ghc/ghc/-/issues/20204)
The kind signature for Even if it is adjacent, the fact that it's unlifted is quite subtle... you have to look to the right of the arrows, and then through the distant (and perhaps imported) type synonym I'm not very happy with a distant kind signature having such a profound effect on the semantics of the data type. Indeed in my comment above I suggested a keyword
to signal that it's an unlifted data type. But then I went AWOL and didn't pursue the matter. I don't know why I was so negligent. So this post is to ask: does anyone else think this is bizarre? I'm inclined to make a proposal to add the keyword, but I thought I'd test the waters first. |
See the 5ish comments starting at #265 (comment) for why we decided to omit the keyword. |
Thanks. I'd missed them (I think GitHub didn't load them). But still that seems way short of a "decision". More a few remarks from a handful of people. I'm very bothered about this, and I feel negligent having let it go by. (I'd be perfectly happy allowing |
Instead of adding a keyword, this could be another case where modifiers could come in handy? As in, -- Existing type already exposed via GHC.Exts:
-- | Whether a boxed type is lifted or unlifted.
data Levity = Lifted | Unlifted %Unlifted
data SMaybe a = SJust !a | SNothing Edit: Below the above-mentioned 5ish comments, Simon mentioned taking into account levity polymorphism (#265 (comment)). It's not clear to me at the moment how one might make this work with either the keyword or modifier approach, but incorporating this into the modifier approach somehow does seem like it would be easier. Also, if we use keywords or modifiers for unlifted datatypes, presumably the same would have to be allowed for unlifted newtypes. |
Don't we already have similar behavior with normal functions and type classes: x :: Double
-- x :: Int
-- lots of code
x = 1
main = print x Will print I don't think this is very strange. |
FWIW I'm very surprised to learn that type signatures can be separated from definitions like this! |
I'm sympathetic to Simon's concern here, but I'm not as bothered: I don't feel the need to include a new keyword. On the other hand, I'd be happy with a modifier (thanks for the idea @JakobBruenker), along with a |
I might also add that as tooling gets better, the textual data type definition becomes less and less relevant while understanding code. Heck, even if I hover over a type like
Note the I'm not against a keyword or modifier if people find the current behavior confusing, but I still want to point out that keywords won't help to define a levity-polymorphic data type. You are still stuck with a kind signature (standalone or not) in that case. And then you have the same problem as before. Worse, actually, because now a type parameter determines levity of the data type. So you might find that one use site behaves as unlifted while the other as lifted. Point being: There's no way out of confusion other than having reliable tooling, which we already have. |
I agree with @sgraf812's observation. More generally: as our language gets more intricate, our tools need to keep apace. This is why e.g. Agda and Idris are out ahead of Haskell with tooling. Those languages are more intricate, and so the tooling must be up to the task. For a long time, Haskell has limped along with sub-par tooling, because, well, I don't know why. As the tooling catches up, though, we can new flexibility in design -- for example, by making things less obvious in the code text if they can be clarified by tools. @simonpj: you prodded this conversation to start. How do you feel about this all now? |
Ugh. When I started this conversation had forgotten about
Now That does leave me a bit stuck. A keyword isn't going to work. The only viable alterantive is to insist that if the return kind isn't
I forget -- was that possibility discussed? (I don't think we have a way to declare a return kind in H98 syntax, do we?) I'm still clinging to the idea that we should be able to infer a kind from a declaration; a signature should make it less polymorphic, but not more. (And yes I know that polymorphic recursion breaks this, but not badly.) Any other views? |
That is exactly the problem. I find H98 quite nice for declaring simple data types such as Another gripe I'd have with it is that Again, I'm not strongly against disallowing SAKS, but it seems awfully surprising not to allow them from a design perspective. Especially given that they work for all lifted data types. Also to me separating the SAKS from the data type declaration is a code smell to begin with (wasn't syntactic distance the reason you gave in #265 (comment)?). Arguably, the mix up in https://gitlab.haskell.org/ghc/ghc/-/issues/20204 could have happened just as well for a CUSK. I think our eyes merely aren't trained to give SAKS the attention they deserve yet. |
I am absolutely not suggesting that we disallow a standalone kind signature! I'm only suggesting that you can infer the kind from the declaration. It may be narrowed by the SAKS. With unlifted newtypes you can infer the signature, right?
I can see that is unlifted without looking at its signature. |
You can write this: type T :: TYPE (BoxedRep r) -> TYPE (BoxedRep r)
newtype T a = T a |
Ah yes. So in my straw-man idea, that would be illegal because looking at the newtype decl alone we'd default to lifted. If you want something more general you'd have to decorate the newtype decl itself. But maybe I am a lone voice. |
Perhaps. But you've been at this for some time and have had a modicum of success, so it's a voice we should listen to. What about my modifier story? type N :: TYPE (BoxedRep l) -> TYPE (BoxedRep l)
%HasUnusualKind
newtype N a = MkN a The My own preference is not to have |
But why would you invent new modifiers rather than use what we already have: kind signatures? |
So you would be fine with a warning, rather than an error? |
Feels weird to introduce something that goes against the general trend of moving away from CUSKs and towards SAKs, no? |
I'm not arguing for a complete user supplied kind (CUSK). The CUSK story was principally to suport polymorphic recursion (user manual entry). That's not at all waht I am talking about here. I'm only talking about defaulting of the return kind of a data type: to be other than |
Do we agree, though, that it's already what the proposal says? |
No, I don't think so. See my original post above, in which we find that
In fact the proposal says "Specifying a kind signature is the only way to declare an unlifted or levity-polymorphic data type." Is that really true? If I say
surely I get an unlifted type? My suggestion is the opposite of the one in the quote above. I'm suggesting that
so that type inference on the declaration will yield the right kind. (A standalone kind signature might specialise that kind, just as for term level declarations.) |
This has a kind signature. So I think it doesn't make the statement from the proposal incorrect.
I, for one, find this requirement rather weird. It would be the only place in the language where we make this sort of requirement. Despite the fact that specifying a toplevel type signature does change a whole lot of things for other kind of declarations. For one thing, it changes the typechecking algorithm from inferring mode to checking mode. Or, closer to my heart (and to what's happening in this proposal), it lets one define a linear function rather than an unrestricted function. This effect at a distance has never been a problem (in great part because people are fairly careful not to put the signature far away from the declaration; in fact, I'm pretty sure many Haskellers are not even aware that it's a possibility). I don't believe that it will be in this case either. I'm personally in favour of leaving the proposal in this state. |
I agree, and it afflicts We need a syntactic way to group [**] a type(-of-type) decl with its binding (and other stuff). Some ideas that don't clash with current syntax:
(The last is stolen from BCPL's 'very binding semicolon'. It actually seems to be currently valid Haskell syntax, with apparently the same meaning as a single semicolon in the same position/allowing the following decl to be indented as far as you like; or same as no semicolon at all without indenting. Hmm I like that/might start using it anyway.) The semantics is to be as currently/proposed here if these were each stand-alone decls. (So For this proposal, if the three decls were given separately stand-alone: infer the usual type (usual liftedness) for [**] another place we could use grouping is for |
I believe this should have an Implemented label. (Should there be an explicit policy that random passers-by may ping someone to add missing labels? |
Thanks! You guessed the policy correctly; where should it be made explicit so that you would have seen it? Feel free to propose a PR |
@sgraf812 I just found out that the motivation in this proposal is not that strong, because you can get the same speedup by simply making the recursive fields of data IntSet
= Branch !IntSet !Int !IntSet
| Leaf Perhaps the motivation should focus more on avoiding the need to put bang patterns everywhere and that we now have bona fide algebraic data types. |
I believe this is mostly a result of tag inference, something that hadn't been merged yet when this proposal was written. There are also some edge cases where tag inference doesn't help (things involving boot files, functions not being W/Wed and others) so I think the spirit of the motivation still holds. Even if the exact example isn't as striking anymore. |
The proposal has been accepted; the following discussion is mostly of historic interest.
Rendered