Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions src/Nattish.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Nattish
( Nattish (Zeroy, Succy)
)
where
import Foreign.Ptr (WordPtr)
import Unsafe.Coerce (unsafeCoerce)
#if __GLASGOW_HASKELL__ >= 800
import Data.Kind (Type)
Expand All @@ -42,20 +43,20 @@ data Nattish :: k -> (k -> k) -> k -> * where
Zeroy :: Nattish zero succ zero
Succy :: !(Nattish zero succ n) -> Nattish zero succ (succ n)

toWord :: Nattish zero succ n -> Word
toWord = go 0
toWordPtr :: Nattish zero succ n -> WordPtr
toWordPtr = go 0
where
go :: Word -> Nattish zero succ n -> Word
go :: WordPtr -> Nattish zero succ n -> WordPtr
go !acc Zeroy = acc
go !acc (Succy n) = go (acc + 1) n

instance Show (Nattish zero succ n) where
showsPrec p n = showParen (p > 10) $
showString "Nattish " . showsPrec 11 (toWord n)
showString "Nattish " . showsPrec 11 (toWordPtr n)
#else

type Nattish :: forall k. k -> (k -> k) -> k -> Type
newtype Nattish zero succ n = Nattish Word
newtype Nattish zero succ n = Nattish WordPtr -- See [Note: WordPtr]
deriving (Show)
type role Nattish nominal nominal nominal

Expand All @@ -81,4 +82,30 @@ pattern Succy n <- (check -> ResSucc n)

{-# COMPLETE Zeroy, Succy #-}

-- [Note: WordPtr]
--
-- Why WordPtr and not Word? We want to be extremely certain that it does not
-- overflow, because that would lead to a type safety problem (accessing a Succ
-- object as a value). At first, I thought we couldn't possibly have to worry
-- about that—after all, the Nattish never exceeds the *logarithm* of the queue
-- size. But then I remembered that it's possible to make quite absurdly large
-- queues by repeated concatenation. Imagine if a Word is 32 bits, but a
-- pointer is 64 bits. Then something like
--
-- stimes (2^(2^33)) (singleton () ())
--
-- might actually be possible to construct in a relatively reasonable amount of
-- time and an achievable (if somewhat ridiculous) amount of memory. If this
-- were then traversed with traverseWithKeyU or foldMapWithKeyU from right to
-- left (using a lazy left fold, for example), we could overflow. Disaster!
-- Fortunately, we have WordPtr, which is equivalent to uintptr_t, and
-- therefore big enough to hold the logarithm of the size of any queue
-- represented in memory, even with maximal sharing.
--
-- Now, according to a comment on
-- https://hackage.haskell.org/package/base-4.18.0.0/docs/src/Foreign.Ptr.html#WordPtr
-- GHC actually guarantees that a Word is the same size as a pointer, so we
-- actually *could* just use Word, but I think WordPtr makes it *utterly clear*
-- that we're safe here.
Comment on lines +105 to +109
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What? We use WordPtr because it could be bigger than Word, but actually they're always the same size? What's the point then? I think using WordPtr only makes it more confusing, since usually noone uses a pointer as a number type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On GHC they're the same. If someone ports to another implementation, that might not be.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're dead set against this, I can go back to Word and explain why it's okay on GHC. That just commits us even more heavily to GHC specifically, rather than to just a compiler with similar power.


#endif