Skip to content
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

Provide a primitive to show types as Symbols #164

Closed
wants to merge 4 commits into from

Conversation

isovector
Copy link
Contributor

@isovector isovector commented Aug 24, 2018

ShowType has an annoyingly specific kind: k -> ErrorMessage. If we had something similar whose kind were instead k -> Symbol, we could use it in many more places.

Rendered

@treeowl
Copy link
Contributor

treeowl commented Aug 25, 2018

My first thought was that yes, of course we should do something like this! Then I realized that one could easily construct a program that only compiles if GHC formats a type in a particular way. Hrmm.

Then my biggest concern hit. TypeError ('ShowType t) is really quite magical: it can show polymorphic types. This is a lot of the power it has over showing a TypeRep, I think. If you have a ShowType type family, how exactly will inference work? That sounds complicated!

@goldfirere
Copy link
Contributor

@treeowl, I don't quite see what the complicated inference case would be.

My concern is more around @treeowl 's first concern, about compilation depending on GHC's choice for rendering types.

I'm also lost at the proposal's "it will emit a derived constraint KnownSymbol (ShowTypeSymbol a)". What does that mean in practice?

@treeowl
Copy link
Contributor

treeowl commented Aug 28, 2018

@goldfirere, I don't have a fully worked example. My concern is that someone may try to show a type that has metavariables in it. What's supposed to happen then? If the result depends on details of the type checking process, that sounds much too wild to me. What if the result of showing the type somehow determines the values of one or more of those variables? If that's possible, it sounds twisted.

@goldfirere
Copy link
Contributor

I'm assuming that ShowTypeSymbol reduces only when it encounters a concrete type. That is ShowTypeSymbol [a] would be stuck until we learn what a is.

@treeowl
Copy link
Contributor

treeowl commented Aug 28, 2018

@goldfirere, that seems like an eminently reasonable restriction. It is, however, quite different from what TypeError can support. This mechanism could not be used to implement the 'ShowType case of TypeError.

@goldfirere
Copy link
Contributor

Good point -- this new feature couldn't be used to implement today's TypeError. Note that the problem is just one of type inference, if ShowTypeSymbol a reduces to a different symbol than ShowTypeSymbol Int, we've lost type safety.

@isovector
Copy link
Contributor Author

@treeowl @goldfirere thanks for your comments. Would you be more supportive of the proposal if it came with a specification for how the type were printed, an if ShowTypeSymbol worked only for concrete types?

@treeowl
Copy link
Contributor

treeowl commented Aug 29, 2018

@isovector, that would certainly help. You'll also either have to ban higher-rank types or specify precisely how to deal with them. I think you should also remove the claim that this can be used to implement TypeError; otherwise we'd have to cripple the latter.

@isovector
Copy link
Contributor Author

isovector commented Aug 31, 2018

@treeowl: the requested changes have been made. PTAL when you have a chance. Thanks to @Lysxia for helping me with the correct behavior for this specification.

@parsonsmatt
Copy link
Contributor

I would greatly appreciate this feature. I've often wanted to have a type-level set backed by a sorted type-level list, and have been unable to do so as Type's aren't comparable (while Symbols are). Being able to automatically translate between the two would allow me to write that library.

@int-index
Copy link
Contributor

int-index commented Sep 5, 2018

@parsonsmatt No, you would erroneously equate types which have equal names but come from different modules/packages. What you want is type-level fingerprinting à la Typeable.

@isovector
Copy link
Contributor Author

@nomeata ready for committee eyes. Thanks!!

@nomeata nomeata added the Pending committee review The committee needs to evaluate the proposal and make a decision label Sep 7, 2018
@goldfirere
Copy link
Contributor

goldfirere commented Sep 23, 2018 via email

@Lysxia
Copy link
Contributor

Lysxia commented Sep 26, 2018

Replying to point 2: Shouldn't changing a type synonym be considered a breaking change? Are you thinking about a more complex situation?

@treeowl
Copy link
Contributor

treeowl commented Sep 26, 2018

I agree with @Lysxia. Type synonyms aren't a proper abstraction mechanism anyway.

@treeowl
Copy link
Contributor

treeowl commented Sep 26, 2018

@goldfirere, I think we want ShowType to show the unqualified name. But we can add primitives to get and show the module and package names.

@isovector
Copy link
Contributor Author

isovector commented Sep 26, 2018

  1. Yes, this can all be implemented in user land minus the magic for built-in datakinds. That being said, many of the type instance rules given in the proposal overlap, so it wouldn't be as easy as just giving instances for type constructors and importing a module for the rest.

  2. I agree that ShowType should look through type synonyms. It also seems to me that changing a type synonym should be considered a breaking change, but the PVP specification is rather quiet on this front.

  3. It seems like we should always show an unqualified name. Getting package and module names could be given by some other primitive of kind k -> (Symbol, Symbol). Having access to these would help @parsonsmatt's earlier example. I suspect the majority of use-cases for ShowTypeSymbol wouldn't care about this information, but I'm willing to add it to the proposal.

@Lysxia
Copy link
Contributor

Lysxia commented Sep 26, 2018

What if instead of ShowTypeSymbol, GHC provides a primitive for "atomic types" (Maybe, [], as opposed to Maybe (), [Int]).

type family TypeInfoOf (a :: k) :: TypeInfo

-- Compiler-generated
type instance TypeInfoOf Maybe = 'NormalData "base" "GHC.Maybe" "Maybe"
type instance TypeInfoOf (:*:) = 'InfixData ('Infixr 6) "base" "GHC.Generics" ":*:"
-- etc.

data TypeInfo
  = NormalData Symbol Symbol Symbol
  | InfixData ...
  | Symbol Symbol
  | Nat Nat
  -- ... special cases for tuples, []?

With a magic instance for Nats, maybe also for Symbols.

Given only TypeInfoOf, it seems feasible to define multiple versions of ShowTypeSymbol in libraries, in particular both one that shows unqualified names, and another with fully package-qualified names.

I think we can already approximate that using Generic, but there are no instances for GADTs.


ShowType has an annoyingly specific kind: k -> ErrorMessage. If we had something similar whose kind were instead k -> Symbol, we could use it in many more places.

ShowType is unnecessarily restricted to only being useable in custom type error contexts. Slightly changing its kind would allow it to be used in many more places.

The proposal suggests that it generalizes the current ShowType, but as @treeowl mentioned earlier, ShowType does quite a bit of magic that justifies restricting it to error messages only. The difference seems worth mentioning in the actual proposal.

@int-index
Copy link
Contributor

I support @Lysxia idea of TypeInfo, and I'd like it to include Fingerprint which would be equal to the one in TypeRep. This would make it possible to sort types and define type-level sets (as @parsonsmatt mentioned).

@isovector
Copy link
Contributor Author

isovector commented Sep 27, 2018

I spent a good chunk of yesterday playing around with TypeInfo, and it does indeed seem to solve the use-cases better than my original proposal. I'll try to update it in the next few days.

edited a few times: @int-index there's a bit of a problem with adding Fingerprints, namely that my understanding of @Lysxia's suggestion is that it pushes actually doing anything with TypeInfos to library code. As a result, we can get Fingerprints for type constructors, but crucially not for arbitrary types. If you have any suggestions about how to make this work, I'd be all ears!

@goldfirere
Copy link
Contributor

Thanks for the clarifications @isovector. Yes, I was just wrong about my point (2) on type synonyms -- sorry!

I would be ready to make my recommendation to the committee here, but it seems like more revisions are in the works. I'll wait until things settle down again.

@goldfirere goldfirere removed the Pending committee review The committee needs to evaluate the proposal and make a decision label Oct 9, 2018
@goldfirere
Copy link
Contributor

As this has planned updates, I've removed the "under review" label. Please resubmit when ready.

@isovector
Copy link
Contributor Author

isovector commented Oct 22, 2018

Based on @Lysxia's suggestion, I've come up with the following formulation. Wondering if anyone has feedback on this before I go through the work of updating the proposal proper.

{-# LANGUAGE DataKinds    #-}
{-# LANGUAGE PolyKinds    #-}
{-# LANGUAGE TypeFamilies #-}

module Proposal where

import           GHC.Generics (FixityI (..), Associativity (..))
import qualified GHC.TypeLits as TL

data Datatype = Datatype
  { datatypeName   :: TL.Symbol
  , datatypeFixity :: FixityI
  , moduleName     :: TL.Symbol
  , packageName    :: TL.Symbol
  , isNewtype      :: Bool
  }

data Constructor = Constructor
  { conName     :: TL.Symbol
  , conFixity   :: FixityI
  , conDatatype :: Datatype
  }

data TypeInfo
  = Data     Datatype
  | Promoted Constructor
  | Symbol   TL.Symbol
  | Nat      TL.Nat

type family TyRep (t :: k) :: TypeInfo
type family ShowSymbol (t :: TL.Symbol) :: TL.Symbol
type family ShowNat    (t :: TL.Nat)    :: TL.Symbol

In particular, the proposal would be to provide the bottom three type families as GHC primitives, and relegate the work of showing more complicated types to be built out of these in library code.

@int-index
Copy link
Contributor

@isovector How are you going to represent Either Int here? Your TypeInfo is missing constructor application, App TypeInfo TypeInfo.

I suggest following the structure of TypeRep:
https://hackage.haskell.org/package/base-4.12.0.0/docs/src/Data.Typeable.Internal.html#TypeRep

@goldfirere
Copy link
Contributor

I understand that you can skip type families here, but what about data families? Classes? Functions? I worry that this will spiral out of control and end up replicating the existing features of generics...

@jvanbruegge
Copy link

I want to add that I would really like to see GHCi using this type family for :type, so you can provide easier to look at type printing. Maybe the current behavior could be moved to :type! to see behind the scenes

@Lysxia
Copy link
Contributor

Lysxia commented Nov 1, 2018

@jvanbruegge that seems unlikely since ShowType will only resolve to a concrete string for fully instantiated, monomorphic types, whereas :type should handle the whole of Haskell's type language, including quantifiers and type synonyms (it seems undesirable to have them expanded by default, which ShowType would most likely do).

@bravit bravit removed the Proposal label Dec 3, 2018
@isovector
Copy link
Contributor Author

Closing this for personal reasons---if anyone else would like to take up the mantle, that'd be cool!

@isovector isovector closed this Dec 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants