git.iko.soy/rowtype-yoga/purescript-docs-search
on master help sign in
Refactor `Docs.Search.Config`
commit73a6c092a0377408b7390d6d8fb71fb56e1ced42 authorklntsky <klntsky@gmail.com> dateMon 3 Aug 2020, 18:35 (6 years ago) parent8630488
13 files changed+132−128
src/Docs/Search/App.purs+10−9 src/Docs/Search/App/SearchResults.purs+3−3 src/Docs/Search/App/Sidebar.purs+4−4 src/Docs/Search/BrowserEngine.purs+4−4 src/Docs/Search/Config.purs+74−76 src/Docs/Search/IndexBuilder.purs+7−7 src/Docs/Search/Main.purs+4−4 src/Docs/Search/Meta.purs+3−3 src/Docs/Search/ModuleIndex.purs+2−2 src/Docs/Search/PackageIndex.purs+2−2 src/Docs/Search/TypeIndex.purs+4−4 src/Docs/Search/TypeQuery.purs+9−9 src/Docs/Search/Types.purs+6−1
src/Docs/Search/App.purs+10−9
@@ -1,6 +1,15 @@
11-- | This is the main module of the client-side Halogen app.
22module Docs.Search.App where
33
4+import Docs.Search.App.SearchField as SearchField
5+import Docs.Search.App.SearchResults as SearchResults
6+import Docs.Search.App.Sidebar as Sidebar
7+import Docs.Search.Config as Config
8+import Docs.Search.Extra (whenJust)
9+import Docs.Search.ModuleIndex as ModuleIndex
10+import Docs.Search.PackageIndex as PackageIndex
11+import Docs.Search.Meta as Meta
12+
413import Prelude
514
615import Control.Alt (alt)
@@ -9,14 +18,6 @@ import Data.Maybe (Maybe(..))
918import Data.Newtype (wrap)
1019import Data.Tuple (Tuple(..))
1120import Data.Tuple.Nested ((/\))
12−import Docs.Search.App.SearchField as SearchField
13−import Docs.Search.App.SearchResults as SearchResults
14−import Docs.Search.App.Sidebar as Sidebar
15−import Docs.Search.Config (config)
16−import Docs.Search.Extra (whenJust)
17−import Docs.Search.ModuleIndex as ModuleIndex
18−import Docs.Search.PackageIndex as PackageIndex
19−import Docs.Search.Meta as Meta
2021import Effect (Effect)
2122import Effect.Aff (launchAff_)
2223import Halogen as H
@@ -195,7 +196,7 @@ insertVersionInfo doc = do
195196 Element.setAttribute "href" "https://github.com/spacchetti/purescript-docs-search" linkElement
196197 Element.setAttribute "target" "_blank" linkElement
197198 linkText <- Document.createTextNode ("docs-search") doc <#> Text.toNode
198− suffix <- Document.createTextNode (" " <> config.version) doc <#> Text.toNode
199+ suffix <- Document.createTextNode (" " <> Config.version) doc <#> Text.toNode
199200 void $ Node.appendChild prefix versionInfo
200201 void $ Node.appendChild linkNode versionInfo
201202 void $ Node.appendChild linkText linkNode
src/Docs/Search/App/SearchResults.purs+3−3
@@ -3,7 +3,7 @@ module Docs.Search.App.SearchResults where
33
44import Docs.Search.App.SearchField (SearchFieldMessage(..))
55import Docs.Search.BrowserEngine (PartialIndex, browserSearchEngine)
6−import Docs.Search.Config (config)
6+import Docs.Search.Config as Config
77import Docs.Search.Declarations (DeclLevel(..), declLevelToHashAnchor)
88import Docs.Search.DocsJson (DataDeclType(..))
99import Docs.Search.Engine (Result(..), packageInfoToString)
@@ -83,7 +83,7 @@ mkComponent initialEngineState contents markdownIt { localPackageName } =
8383 , results: []
8484 , input: ""
8585 , contents
86− , resultsCount: config.resultsCount
86+ , resultsCount: Config.resultsCount
8787 , mode: Off
8888 , markdownIt
8989 , localPackageName
@@ -116,7 +116,7 @@ handleQuery (MessageFromSearchField (InputUpdated input_) next) = do
116116 H.modify_ (_ { mode = Off })
117117 showPageContents
118118 else do
119− H.modify_ (_ { mode = Loading, resultsCount = config.resultsCount })
119+ H.modify_ (_ { mode = Loading, resultsCount = Config.resultsCount })
120120
121121 void $ H.fork do
122122 { index, results } <- H.liftAff $
src/Docs/Search/App/Sidebar.purs+4−4
@@ -1,6 +1,6 @@
11module Docs.Search.App.Sidebar where
22
3−import Docs.Search.Config (config)
3+import Docs.Search.Config as Config
44import Docs.Search.ModuleIndex (PackedModuleIndex)
55import Docs.Search.Types (ModuleName, PackageName(..))
66
@@ -85,8 +85,8 @@ handleAction (ToggleGrouping groupingMode) = do
8585 localStorage <- Window.localStorage window
8686
8787 if groupingMode == DontGroup
88− then Storage.setItem config.groupModulesItem "false" localStorage
89− else Storage.removeItem config.groupModulesItem localStorage
88+ then Storage.setItem Config.groupModulesItem "false" localStorage
89+ else Storage.removeItem Config.groupModulesItem localStorage
9090
9191
9292handleQuery
@@ -156,7 +156,7 @@ loadGroupingModeFromLocalStorage :: Effect GroupingMode
156156loadGroupingModeFromLocalStorage = do
157157 window <- HTML.window
158158 localStorage <- Window.localStorage window
159− mbDontGroupModules <- Storage.getItem config.groupModulesItem localStorage
159+ mbDontGroupModules <- Storage.getItem Config.groupModulesItem localStorage
160160 pure $ if isJust mbDontGroupModules then DontGroup else GroupByPackage
161161
162162
src/Docs/Search/BrowserEngine.purs+4−4
@@ -1,7 +1,7 @@
11-- | A search engine that is used in the browser.
22module Docs.Search.BrowserEngine where
33
4−import Docs.Search.Config (config)
4+import Docs.Search.Config as Config
55import Docs.Search.PackageIndex (queryPackageIndex)
66import Docs.Search.Engine (Engine, EngineState, Index)
77import Docs.Search.SearchResult (SearchResult)
@@ -68,7 +68,7 @@ query index@(PartialIndex indexMap) input = do
6868
6969
7070 eiPartJson <-
71− try $ toAffE $ loadIndex_ partId $ config.mkIndexPartLoadPath partId
71+ try $ toAffE $ loadIndex_ partId $ Config.mkIndexPartLoadPath partId
7272
7373 let
7474 mbNewTrie :: Maybe (Trie Char (List SearchResult))
@@ -121,9 +121,9 @@ browserSearchEngine =
121121-- | Find in which part of the index this path can be found.
122122getPartId :: List Char -> Int
123123getPartId (a : b : _) =
124− (Char.toCharCode a + Char.toCharCode b) `mod` config.numberOfIndexParts
124+ (Char.toCharCode a + Char.toCharCode b) `mod` Config.numberOfIndexParts
125125getPartId (a : _) =
126− Char.toCharCode a `mod` config.numberOfIndexParts
126+ Char.toCharCode a `mod` Config.numberOfIndexParts
127127getPartId _ = 0
128128
129129
src/Docs/Search/Config.purs+74−76
@@ -2,81 +2,79 @@ module Docs.Search.Config where
22
33import Prelude
44
5−import Docs.Search.Types (PackageName(..))
6−
7−-- | Some magic constants.
8−config ::
9− { version :: String
10− , declIndexDirectory :: String
11− , mkIndexPartLoadPath :: Int -> String
12− , mkIndexPartPath :: Int -> String
13− , moduleIndexPath :: String
14− , moduleIndexLoadPath :: String
15− , metaPath :: String
16− , metaLoadPath :: String
17− , metaItem :: String
18− , groupModulesItem :: String
19− , packageInfoPath :: String
20− , packageInfoLoadPath :: String
21− , mkShapeScriptPath :: String -> String
22− , numberOfIndexParts :: Int
23− , outputDirectory :: String
24− , penalties :: { excessiveConstraint :: Int
25− , generalize :: Int
26− , instantiate :: Int
27− , match :: Int
28− , matchConstraint :: Int
29− , missingConstraint :: Int
30− , rowsMismatch :: Int
31− , typeVars :: Int
32− }
33− , requiredDirectories :: Array String
34− , resultsCount :: Int
35− , typeIndexDirectory :: String
36− , defaultPackageName :: PackageName
5+import Docs.Search.Types
6+
7+version :: String
8+version = "0.0.9"
9+
10+mkShapeScriptPath :: String -> String
11+mkShapeScriptPath shape = "./index/types/" <> shape <> ".js"
12+
13+-- | In how many parts the index should be splitted?
14+numberOfIndexParts :: Int
15+numberOfIndexParts = 50
16+
17+mkIndexPartPath :: Int -> String
18+mkIndexPartPath partId = "html/index/declarations/" <> show partId <> ".js"
19+
20+mkIndexPartLoadPath :: Int -> String
21+mkIndexPartLoadPath partId = "./index/declarations/" <> show partId <> ".js"
22+
23+moduleIndexPath :: String
24+moduleIndexPath = "generated-docs/html/index/modules.js"
25+
26+-- | Used to load mode index to the browser scope.
27+moduleIndexLoadPath :: String
28+moduleIndexLoadPath = "./index/modules.js"
29+
30+typeIndexDirectory :: String
31+typeIndexDirectory = "generated-docs/html/index/types"
32+
33+metaPath :: String
34+metaPath = "generated-docs/html/index/meta.js"
35+
36+metaLoadPath :: URL
37+metaLoadPath = URL "./index/meta.js"
38+
39+metaItem :: String
40+metaItem = "DocsSearchMeta"
41+
42+-- | localStorage key to save sidebar checkbox value to.
43+groupModulesItem :: String
44+groupModulesItem = "PureScriptDocsSearchGroupModules"
45+
46+packageInfoPath :: String
47+packageInfoPath = "generated-docs/html/index/packages.js"
48+
49+packageInfoLoadPath :: String
50+packageInfoLoadPath = "./index/packages.js"
51+
52+-- | How many results to show by default?
53+resultsCount :: Int
54+resultsCount = 25
55+
56+-- | Penalties used to determine how "far" a type query is from a given type.
57+-- See Docs.Search.TypeQuery
58+penalties ::
59+ { excessiveConstraint :: Int
60+ , generalize :: Int
61+ , instantiate :: Int
62+ , match :: Int
63+ , matchConstraint :: Int
64+ , missingConstraint :: Int
65+ , rowsMismatch :: Int
66+ , typeVars :: Int
3767 }
38−config =
39− { version: "0.0.9"
40− , outputDirectory: "output"
41− , requiredDirectories:
42− [ "generated-docs"
43− , "generated-docs/html"
44− , "output"
45− ]
46− -- ^ Directories required by IndexBuilder
47− , declIndexDirectory: "generated-docs/html/index/declarations"
48− , typeIndexDirectory: "generated-docs/html/index/types"
49− , mkShapeScriptPath:
50− \shape -> "./index/types/" <> shape <> ".js"
51− , numberOfIndexParts: 50
52− -- ^ In how many parts the index should be splitted?
53− , mkIndexPartPath:
54− \(partId :: Int) -> "html/index/declarations/" <> show partId <> ".js"
55− , mkIndexPartLoadPath:
56− \(partId :: Int) -> "./index/declarations/" <> show partId <> ".js"
57− , moduleIndexPath: "generated-docs/html/index/modules.js"
58− , moduleIndexLoadPath: "./index/modules.js"
59− -- ^ Used to load mode index to the browser scope.
60− , metaPath: "generated-docs/html/index/meta.js"
61− , metaLoadPath: "./index/meta.js"
62− , metaItem: "DocsSearchMeta"
63− , groupModulesItem: "PureScriptDocsSearchGroupModules"
64− -- ^ localStorage key to save sidebar checkbox value to.
65− , packageInfoPath: "generated-docs/html/index/packages.js"
66− , packageInfoLoadPath: "./index/packages.js"
67− -- ^ Used to load package index to the browser scope.
68− , resultsCount: 25
69− -- ^ How many results to show by default?
70− , penalties: { typeVars: 2
71− , match: 2
72− , matchConstraint: 1
73− , instantiate: 2
74− , generalize: 2
75− , rowsMismatch: 3
76− , missingConstraint: 1
77− , excessiveConstraint: 1
78− }
79− -- ^ Penalties used to determine how "far" a type query is from a given type.
80− -- See Docs.Search.TypeQuery
81− , defaultPackageName: PackageName "<local package>"
68+penalties =
69+ { typeVars: 2
70+ , match: 2
71+ , matchConstraint: 1
72+ , instantiate: 2
73+ , generalize: 2
74+ , rowsMismatch: 3
75+ , missingConstraint: 1
76+ , excessiveConstraint: 1
8277 }
78+
79+defaultPackageName :: PackageName
80+defaultPackageName = PackageName "<local package>"
src/Docs/Search/IndexBuilder.purs+7−7
@@ -1,7 +1,7 @@
11module Docs.Search.IndexBuilder where
22
33import Docs.Search.BrowserEngine (getPartId)
4−import Docs.Search.Config (config)
4+import Docs.Search.Config as Config
55import Docs.Search.Declarations (Declarations(..), mkDeclarations)
66import Docs.Search.DocsJson (DocsJson)
77import Docs.Search.Extra ((>#>))
@@ -209,7 +209,7 @@ decodeBowerJsons { bowerFiles } = do
209209writeTypeIndex :: Config -> TypeIndex -> Aff Unit
210210writeTypeIndex { generatedDocs } typeIndex =
211211 for_ entries \(Tuple typeShape results) -> do
212− writeTextFile UTF8 (config.typeIndexDirectory <> "/" <> typeShape <> ".js")
212+ writeTextFile UTF8 (Config.typeIndexDirectory <> "/" <> typeShape <> ".js")
213213 (mkHeader typeShape <> stringify (encodeJson results))
214214 where
215215 mkHeader typeShape =
@@ -222,7 +222,7 @@ writeTypeIndex { generatedDocs } typeIndex =
222222writePackageInfo :: PackageInfo -> Aff Unit
223223writePackageInfo packageInfo = do
224224
225− writeTextFile UTF8 config.packageInfoPath $
225+ writeTextFile UTF8 Config.packageInfoPath $
226226 header <> stringify (encodeJson packageInfo)
227227
228228 where
@@ -231,17 +231,17 @@ writePackageInfo packageInfo = do
231231
232232writeModuleIndex :: PackedModuleIndex -> Aff Unit
233233writeModuleIndex moduleIndex = do
234− writeTextFile UTF8 config.moduleIndexPath $
234+ writeTextFile UTF8 Config.moduleIndexPath $
235235 header <> stringify (encodeJson moduleIndex)
236236 where
237237 header = "window.DocsSearchModuleIndex = "
238238
239239writeMeta :: Meta -> Aff Unit
240240writeMeta meta = do
241− writeTextFile UTF8 config.metaPath $
241+ writeTextFile UTF8 Config.metaPath $
242242 header <> stringify (encodeJson meta)
243243 where
244− header = "window." <> config.metaItem <> " = "
244+ header = "window." <> Config.metaItem <> " = "
245245
246246-- | Get a mapping from index parts to index contents.
247247getIndex :: Declarations -> Map Int (Array (Tuple String (Array SearchResult)))
@@ -285,7 +285,7 @@ writeIndex { generatedDocs } = getIndex >>> \resultsMap -> do
285285 "// This file was generated by purescript-docs-search.\n" <>
286286 "window.DocsSearchIndex[\"" <> show indexPartId <> "\"] = "
287287
288− writeTextFile UTF8 (generatedDocs <> config.mkIndexPartPath indexPartId) $
288+ writeTextFile UTF8 (generatedDocs <> Config.mkIndexPartPath indexPartId) $
289289 header <> stringify (encodeJson results)
290290
291291
src/Docs/Search/Main.purs+4−4
@@ -5,7 +5,7 @@ import Prelude
55
66import Docs.Search.IndexBuilder as IndexBuilder
77import Docs.Search.Interactive as Interactive
8−import Docs.Search.Config (config)
8+import Docs.Search.Config as Config
99import Docs.Search.Types (PackageName(..))
1010
1111import Data.Generic.Rep (class Generic)
@@ -27,13 +27,13 @@ main = do
2727 args <- getArgs
2828 let defaultCommands = Search { docsFiles: defaultDocsFiles
2929 , bowerFiles: defaultBowerFiles
30− , packageName: config.defaultPackageName
30+ , packageName: Config.defaultPackageName
3131 }
3232
3333 case fromMaybe defaultCommands args of
3434 BuildIndex cfg -> IndexBuilder.run cfg
3535 Search cfg -> Interactive.run cfg
36− Version -> log config.version
36+ Version -> log Config.version
3737
3838
3939getArgs :: Effect (Maybe Commands)
@@ -143,7 +143,7 @@ packageNameOption =
143143 PackageName <$> strOption
144144 ( long "package-name"
145145 <> metavar "PACKAGE"
146− <> value (unwrap $ config.defaultPackageName)
146+ <> value (unwrap Config.defaultPackageName)
147147 )
148148
149149
src/Docs/Search/Meta.purs+3−3
@@ -1,6 +1,6 @@
11module Docs.Search.Meta where
22
3−import Docs.Search.Config (config)
3+import Docs.Search.Config as Config
44import Docs.Search.Loader as Loader
55import Docs.Search.Types (GlobalIdentifier(..), PackageName)
66
@@ -16,6 +16,6 @@ type Meta =
1616
1717load :: Aff Meta
1818load =
19− Loader.load (GlobalIdentifier config.metaItem) config.metaLoadPath
19+ Loader.load (GlobalIdentifier Config.metaItem) Config.metaLoadPath
2020 `catchError` const (pure defaultMeta)
21− where defaultMeta = { localPackageName: config.defaultPackageName }
21+ where defaultMeta = { localPackageName: Config.defaultPackageName }
src/Docs/Search/ModuleIndex.purs+2−2
@@ -1,6 +1,6 @@
11module Docs.Search.ModuleIndex where
22
3−import Docs.Search.Config (config)
3+import Docs.Search.Config as Config
44import Docs.Search.Declarations (Declarations(..))
55import Docs.Search.SearchResult (SearchResult(..))
66import Docs.Search.Types (ModuleName, PackageName, PackageInfo(..), PackageScore)
@@ -107,7 +107,7 @@ mkPackedModuleIndex (Declarations trie) =
107107
108108loadModuleIndex :: Aff PackedModuleIndex
109109loadModuleIndex = do
110− json <- toAffE $ load config.moduleIndexLoadPath
110+ json <- toAffE $ load Config.moduleIndexLoadPath
111111 pure $ fromMaybe mempty $ hush $ decodeJson json
112112
113113
src/Docs/Search/PackageIndex.purs+2−2
@@ -1,6 +1,6 @@
11module Docs.Search.PackageIndex where
22
3−import Docs.Search.Config (config)
3+import Docs.Search.Config as Config
44import Docs.Search.Extra (stringToList)
55import Docs.Search.Score (Scores, getPackageScoreForPackageName, normalizePackageName)
66import Docs.Search.Types (PackageName, RawPackageName(..), PackageScore)
@@ -73,7 +73,7 @@ mkScoresFromPackageIndex =
7373
7474loadPackageIndex :: Aff PackageIndex
7575loadPackageIndex = do
76− json <- toAffE (load config.packageInfoLoadPath)
76+ json <- toAffE (load Config.packageInfoLoadPath)
7777 let packageInfo = fromMaybe mempty $ hush $ decodeJson json
7878 pure $ mkPackageIndex packageInfo
7979
src/Docs/Search/TypeIndex.purs+4−4
@@ -1,7 +1,7 @@
11-- | Partial type index, can be loaded on demand in the browser.
22module Docs.Search.TypeIndex where
33
4−import Docs.Search.Config (config)
4+import Docs.Search.Config as Config
55import Docs.Search.Declarations (resultsForDeclaration)
66import Docs.Search.DocsJson (DocsJson(..))
77import Docs.Search.Score (Scores)
@@ -21,7 +21,7 @@ import Data.Either (hush)
2121import Data.Map (Map)
2222import Data.Map as Map
2323import Data.Maybe (Maybe(..), fromMaybe', isJust)
24−import Data.Newtype (class Newtype, unwrap, wrap)
24+import Data.Newtype (class Newtype, over)
2525import Effect (Effect)
2626import Effect.Aff (Aff, try)
2727
@@ -80,7 +80,7 @@ lookup key index@(TypeIndex map) =
8080 case Map.lookup key map of
8181 Just results -> pure { index, results: Array.fold results }
8282 Nothing -> do
83− eiJson <- try (toAffE (lookup_ key $ config.mkShapeScriptPath key))
83+ eiJson <- try (toAffE (lookup_ key $ Config.mkShapeScriptPath key))
8484 pure $ fromMaybe'
8585 (\_ -> { index: insert key Nothing index, results: [] })
8686 do
@@ -94,7 +94,7 @@ lookup key index@(TypeIndex map) =
9494 -> Maybe (Array SearchResult)
9595 -> TypeIndex
9696 -> TypeIndex
97− insert k v = unwrap >>> Map.insert k v >>> wrap
97+ insert k v = over TypeIndex (Map.insert k v)
9898
9999
100100query
src/Docs/Search/TypeQuery.purs+9−9
@@ -10,7 +10,7 @@ module Docs.Search.TypeQuery
1010 )
1111where
1212
13−import Docs.Search.Config (config)
13+import Docs.Search.Config as Config
1414import Docs.Search.Extra (foldl1, foldr1)
1515import Docs.Search.TypeDecoder (QualifiedName(..), Type(..), joinConstraints, joinRows)
1616import Docs.Search.Types (Identifier(..))
@@ -302,7 +302,7 @@ unify query type_ = go Nil (List.singleton { q: query, t: type_ })
302302penalty :: TypeQuery -> Type -> Int
303303penalty typeQuery ty =
304304 let substs = unify typeQuery ty in
305− typeVarPenalty substs * config.penalties.typeVars +
305+ typeVarPenalty substs * Config.penalties.typeVars +
306306 namesPenalty substs +
307307 mismatchPenalty substs
308308
@@ -341,12 +341,12 @@ namesPenalty = go 0
341341 go p Nil = p
342342 go p (Match a b : rest)
343343 | a == b = go p rest
344− | otherwise = go (p + config.penalties.match) rest
344+ | otherwise = go (p + Config.penalties.match) rest
345345 go p (MatchConstraints qcs tcs : rest)
346346 = let p' = Set.size (Set.union qcs tcs) -
347347 Set.size (Set.intersection qcs tcs) in
348− go (p + config.penalties.matchConstraint * p') rest
349− go p (RowsMismatch n m : rest) = go (config.penalties.rowsMismatch * abs (n - m)) rest
348+ go (p + Config.penalties.matchConstraint * p') rest
349+ go p (RowsMismatch n m : rest) = go (Config.penalties.rowsMismatch * abs (n - m)) rest
350350 go p (_ : rest) = go p rest
351351
352352
@@ -356,11 +356,11 @@ mismatchPenalty = go 0
356356 where
357357 go n Nil = n
358358 go n (Instantiate q t : rest) = go (n + typeSize t *
359− config.penalties.instantiate) rest
359+ Config.penalties.instantiate) rest
360360 go n (Generalize q t : rest) = go (n + typeQuerySize q *
361− config.penalties.generalize) rest
362− go n (ExcessiveConstraint : rest) = go (n + config.penalties.excessiveConstraint) rest
363− go n (MissingConstraint : rest) = go (n + config.penalties.missingConstraint) rest
361+ Config.penalties.generalize) rest
362+ go n (ExcessiveConstraint : rest) = go (n + Config.penalties.excessiveConstraint) rest
363+ go n (MissingConstraint : rest) = go (n + Config.penalties.missingConstraint) rest
364364 go n (Mismatch q t : rest) = go (n + typeQuerySize q + typeSize t) rest
365365 go n (TypeMismatch t : rest) = go (n + typeSize t) rest
366366 go n (QueryMismatch q : rest) = go (n + typeQuerySize q) rest
src/Docs/Search/Types.purs+6−1
@@ -63,6 +63,7 @@ instance decodeJsonPackageInfo :: DecodeJson PackageInfo where
6363instance encodeJsonPackageInfo :: EncodeJson PackageInfo where
6464 encodeJson = genericEncodeJson
6565
66+
6667newtype PackageScore = PackageScore Int
6768
6869derive instance newtypePackageScore :: Newtype PackageScore _
@@ -75,7 +76,11 @@ derive newtype instance showPackageScore :: Show PackageScore
7576derive newtype instance decodeJsonPackageScore :: DecodeJson PackageScore
7677derive newtype instance encodeJsonPackageScore :: EncodeJson PackageScore
7778
78−type URL = String
79+
80+newtype URL = URL String
81+
82+derive instance newtypeURL :: Newtype URL _
83+
7984
8085newtype GlobalIdentifier = GlobalIdentifier String
8186