-
Notifications
You must be signed in to change notification settings - Fork 36
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
Singletons of singletons #366
Comments
We will fix Trac #12564 sooner or later. Perhaps we should keep |
I certainly hope so! But my understanding is that it's blocked on Trac #14119, which is a monstrously large refactoring that doesn't appear to have made much progress since the ticket was opened a year ago.
That's one option, I suppose. It might also be the case that there's a better way to make a |
It turns out that there you can work around Trac #12564 here if you're willing to apply some elbow grease. The trick is not to define newtype WrappedSing (a :: k) = WrapSing (Sing a)
newtype SWrappedSing :: forall k (a :: k). WrappedSing a -> Type where
SWrapSing :: forall k (a :: k) (ws :: WrappedSing a).
Sing a -> SWrappedSing ws
type instance Sing = SWrappedSing With this, we can tweak the definition of data SSigma :: forall s (t :: s ~> Type). Sigma s t -> Type where
(:%&:) :: forall s t (fst :: s) (sfst :: Sing fst) (snd :: t @@ fst).
Sing (WrapSing sfst) -> Sing snd -> SSigma (sfst :&: snd :: Sigma s t) Notice that the first field is now of type example :: IO ()
example =
let f :: Sing (SFalse :&: True :: Sigma Bool (ConstSym1 Bool))
f = SWrapSing SFalse :%&: STrue
in print $ projSigma2 f And it works! The downside of this hack is that instead of being able to just use |
For the sake of posterity, I'd like to also make mention of an alternative way to solve the singletons-of-singletons problem that doesn't make use of type family ToSing (a :: k) :: Sing a Here are some examples of data SBool :: Bool -> Type where
SFalse :: SBool False
STrue :: SBool True
type instance Sing = SBool
$(pure [])
type instance ToSing False = SFalse
type instance ToSing True = STrue
data SList :: forall a. [a] -> Type where
SNil :: SList '[]
SCons :: Sing x -> Sing xs -> SList (x:xs)
type instance Sing = SList
$(pure [])
type instance ToSing '[] = SNil
type instance ToSing (x:xs) = SCons (ToSing x) (ToSing xs) (Yes, those Now, any time you feel the need to reach for a singleton of a singleton, you can instead use data SSigma :: forall s (t :: s ~> Type). Sigma s t -> Type where
(:%&:) :: forall s t (fst :: s) (snd :: t @@ fst).
Sing fst -> Sing snd -> SSigma (ToSing fst :&: snd :: Sigma s t)
example :: IO ()
example =
let f :: Sing (SFalse :&: True :: Sigma Bool (ConstSym1 Bool))
f = SFalse :%&: STrue
in print $ projSigma2 f Notice that we no longer need to wrap the first field of At this point, you're probably wondering: what's the catch? From a quick glance,
For these reasons, I'm still of the belief that |
* `SSigma`, the singleton type for `Sigma`, has been added. This fixes #366. * A `Show` instance has been added for `Sigma` (and `SSigma`) by using copious amounts of quantified constraints. The behavior of these instances closely resembles the `Show` implementation for `DPair` in Idris' standard library: https://github.com/idris-lang/Idris-dev/blob/dbe521ff74189df85121abe454f86894de7fd75c/libs/prelude/Prelude/Show.idr#L195-L196 * New functions `fstSigma` and `sndSigma` (as well as their type-level counterparts) have been added. To avoid being a duplicate of `fstSigma`, `projSigma1` has been redefined to have a new type signature that uses continuation-passing style, much like its cousin `projSigma2`. * New functions `currySigma` and `uncurrySigma` have been added. This fixes #359 and supersedes #360.
* `SSigma`, the singleton type for `Sigma`, has been added. This fixes #366. * A `Show` instance has been added for `Sigma` (and `SSigma`) by using copious amounts of quantified constraints. The behavior of these instances closely resembles the `Show` implementation for `DPair` in Idris' standard library: https://github.com/idris-lang/Idris-dev/blob/dbe521ff74189df85121abe454f86894de7fd75c/libs/prelude/Prelude/Show.idr#L195-L196 * New functions `fstSigma` and `sndSigma` (as well as their type-level counterparts) have been added. To avoid being a duplicate of `fstSigma`, `projSigma1` has been redefined to have a new type signature that uses continuation-passing style, much like its cousin `projSigma2`. * New functions `currySigma` and `uncurrySigma` have been added. This fixes #359 and supersedes #360.
I'm forced to take back what I said in #366 (comment): {-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Foo where
import Control.Monad.Trans.Class
import Data.Singletons.TH
import Data.Singletons.TH.Options
$(withOptions defaultOptions{genSingKindInsts = False} $
singletons $ lift [d|
data Nat = Z | S Nat
data Vec n a where
VNil :: Vec 'Z a
(:::) :: a -> Vec n a -> Vec ('S n) a
|])
vReplicate :: SNat n -> a -> Vec n a
vReplicate SZ _ = VNil
vReplicate (SS n) x = x ::: vReplicate n x What would a singled version of type VReplicate :: SNat n -> a -> Vec n a
type family VReplicate sn x where
VReplicate SZ _ = VNil
VReplicate (SS n) x = x ::: VReplicate n x Things get trickier when we try to define sVReplicate :: forall n a (sn :: SNat n) (x :: a).
Sing ('WrapSing sn) -> Sing x -> Sing (VReplicate sn x)
sVReplicate (SWrapSing SZ) _ = SVNil
-- sVReplicate (SWrapSing (SS sn)) sx = ... This fails to typecheck with:
Eek! It turns out that matching on How can we repair type ToSing :: forall k. forall (a :: k) -> Sing a
type family ToSing a
type WrappedSing :: k -> Type
newtype WrappedSing a where
WrapSing :: forall k (a :: k). Sing a -> WrappedSing a
type SWrappedSing :: forall k (a :: k). WrappedSing a -> Type
data SWrappedSing ws where
SWrapSing :: forall k (a :: k). Sing a -> SWrappedSing (WrapSing (ToSing a))
type instance Sing = SWrappedSing Notice that type ToSingNat :: forall (n :: Nat) -> SNat n
type family ToSingNat n where
ToSingNat Z = SZ
ToSingNat (S n) = SS (ToSingNat n)
type instance ToSing n = ToSingNat n Finally, we can define a working version of sVReplicate :: forall n a (sn :: SNat n) (x :: a).
Sing ('WrapSing sn) -> Sing x -> Sing (VReplicate sn x)
sVReplicate (SWrapSing SZ) _ = SVNil
sVReplicate (SWrapSing (SS sn)) sx = sx :%:: sVReplicate (SWrapSing sn) sx It works! Or does it? We have lost some things along the way:
Something is not quite right with the current design. I'm not quite sure what the right fix is yet, but in the meantime, I'll reopen this issue to invite further discussion. |
This is slightly tangential to the discussion at hand, but assuming that we do introduce a type FromSing :: forall k (a :: k). Sing a -> k
type FromSing (x :: Sing a) = a This is an interesting observation that I hadn't seen made anywhere before. The closest thing I found was @goldfirere's earlier prototypes of |
OK, back to the discussion at hand. Another factor to consider in the singletons-of-singletons debate is that in certain situations, you don't need singletons-of-singletons at all. For instance, #460 (comment) observes that given the function below: vReplicate :: SNat n -> a -> Vec n a
vReplicate SZ _ = VNil
vReplicate (SS n) x = x ::: vReplicate n x You can successfully single it without ever needing anything like type VReplicate :: forall a. forall n -> a -> Vec n a
type family VReplicate n a where
VReplicate 'Z _ = 'VNil
VReplicate ('S n) x = x '::: VReplicate n x
sVReplicate :: SNat n -> Sing (x :: a) -> SVec (VReplicate n x)
sVReplicate SZ _ = SVNil
sVReplicate (SS n) x = x :%:: sVReplicate n x Perhaps this can offer a more appealing alternative to type Sigma :: forall s -> (s ~> Type) -> Type
data Sigma s t where
(:&:) :: forall s t fst. Sing (fst :: s) -> t @@ fst -> Sigma s t If we wanted to promote this without defining a singleton for type PSigma :: forall s -> (s ~> Type) -> Type
data PSigma s t where
(:^&:) :: forall s t. forall (fst :: s) -> t @@ fst -> PSigma s t Unfortunately, this won't work, since today's GHC does not permit visible dependent quantification in data constructor types. Until the day arrives where this is possible, there is still a need for a way to single other singletons. |
Can you single a singleton? Currently the answer is "no", since there's no
Sing
instances forSing
s. But should there be? Recently, I found myself wanting something like this when trying to write aSing
instance forSigma
. As a refresher, here's the definition ofSigma
:Here's what a
Sing
instance forSigma
ought to look like:However, notice that we have a field of type
Sing (sfst :: Sing fst)
—a singleton of a singleton! Thus, while you can define aSing
instance forSigma
, it's currently impossible to use in practice, since you can't reasonably construct things of typeSing (sfst :: Sing fst)
.I used to think that in order to make this work, I had to define instances like this:
This is unfortunate, since:
Sing
instances in use today. Plus, if we ever needed a singleton of a singleton of a singleton (e.g.,SSSFalse
), then we'd need to triple the number ofSing
instances in use, and so on.However, there is another way forward here. You can define a single instance (no pun intended) for all
Sing
s like this:That's it! Now you can profitably use the
Sing
instance forSigma
like so:As you can see in that code snippet, one nice advantage of this is that we can define
projSigma2
in essentially the same way that real dependently typed languages do (instead of the Church-style elimination definition that we're forced to use currently).Now that I've extolled the virtues of this idea, it's time to mention the drawbacks. In particular, this whole trick relies on the fact that
Sing
is currently a data family. However, if we were to makeSing
a type family, as proposed in #318, then we would run into GHC limitations. To see why, consider theSing
-as-a-type-family equivalent of theSing
instance forSing
s:If you compile this, then GHC will give up:
Blast, foiled by Trac #12564 once again. Unfortunately, I don't know how to work around this, which means that if we switch
Sing
over to become a type family, then this idea is imperiled.The text was updated successfully, but these errors were encountered: