diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,13 @@
 # Revision history for haskell-lsp-types
 
+## 0.20.0.0
+
+* Don't log errors for '$/' notifications (@jinwoo)
+* Force utf8 encoding when writing vfs temp files (@jneira)
+* Store a hash in a NormalizedUri (@mpickering)
+* Move "Semigroup WorkspaceEdit" instance (@sheaf)
+* Fix vfs line endings (@jneira)
+
 ## 0.19.0.0 -- 2019-12-14
 
 * Fix vfs line endings (@jneira)
diff --git a/haskell-lsp-types.cabal b/haskell-lsp-types.cabal
--- a/haskell-lsp-types.cabal
+++ b/haskell-lsp-types.cabal
@@ -1,5 +1,5 @@
 name:                haskell-lsp-types
-version:             0.19.0.0
+version:             0.20.0.0
 synopsis:            Haskell library for the Microsoft Language Server Protocol, data types
 
 description:         An implementation of the types to allow language implementors to
diff --git a/src/Language/Haskell/LSP/Types/Uri.hs b/src/Language/Haskell/LSP/Types/Uri.hs
--- a/src/Language/Haskell/LSP/Types/Uri.hs
+++ b/src/Language/Haskell/LSP/Types/Uri.hs
@@ -22,17 +22,27 @@
 -- | When URIs are supposed to be used as keys, it is important to normalize
 -- the percent encoding in the URI since URIs that only differ
 -- when it comes to the percent-encoding should be treated as equivalent.
-newtype NormalizedUri = NormalizedUri Text
-  deriving (Eq,Ord,Read,Show,Generic,Hashable)
+--
+-- If you care about performance then you should use a hash map. The keys
+-- are cached in order to make hashing very fast.
+data NormalizedUri = NormalizedUri !Int !Text
+  deriving (Read,Show,Generic, Eq)
 
+-- Slow but compares paths alphabetically as you would expect.
+instance Ord NormalizedUri where
+  compare (NormalizedUri _ u1) (NormalizedUri _ u2) = compare u1 u2
+
+instance Hashable NormalizedUri where
+  hash (NormalizedUri h _) = h
+
 toNormalizedUri :: Uri -> NormalizedUri
-toNormalizedUri uri =
-    NormalizedUri $ T.pack $ escapeURIString isUnescapedInURI $ unEscapeString $ T.unpack t
+toNormalizedUri uri = NormalizedUri (hash norm) norm
   where (Uri t) = maybe uri filePathToUri (uriToFilePath uri)
         -- To ensure all `Uri`s have the file path like the created ones by `filePathToUri`
+        norm = T.pack $ escapeURIString isUnescapedInURI $ unEscapeString $ T.unpack t
 
 fromNormalizedUri :: NormalizedUri -> Uri
-fromNormalizedUri (NormalizedUri t) = Uri t
+fromNormalizedUri (NormalizedUri _ t) = Uri t
 
 fileScheme :: String
 fileScheme = "file:"
diff --git a/src/Language/Haskell/LSP/Types/WorkspaceEdit.hs b/src/Language/Haskell/LSP/Types/WorkspaceEdit.hs
--- a/src/Language/Haskell/LSP/Types/WorkspaceEdit.hs
+++ b/src/Language/Haskell/LSP/Types/WorkspaceEdit.hs
@@ -153,12 +153,12 @@
   mempty = WorkspaceEdit Nothing Nothing
   mappend (WorkspaceEdit a b) (WorkspaceEdit c d) = WorkspaceEdit (a <> c) (b <> d)
 
-deriveJSON lspOptions ''WorkspaceEdit
-
 #if __GLASGOW_HASKELL__ >= 804
 instance Semigroup WorkspaceEdit where
   (<>) = mappend
 #endif
+
+deriveJSON lspOptions ''WorkspaceEdit
 
 -- ---------------------------------------------------------------------
 
