Skip to content

Commit d78e1a7

Browse files
mpscholtenclaude
andauthored
Use Text instead of String in pathTo for better performance (#2226)
* Use Text instead of String in pathTo for better performance Add actionPrefixText and stripActionSuffixText functions that use efficient Text operations (Text.isPrefixOf, Text.stripSuffix, Text.breakOn) instead of recursive char-by-char String traversal. Update pathTo to use these Text functions, reducing Text.pack calls in the hot path. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove actionPrefix and stripActionSuffixString Replace usages of actionPrefix with Text.encodeUtf8 (actionPrefixText ...) and delete the old String-based functions that are no longer needed. Also removes unused Data.Char import. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 05bab5d commit d78e1a7

File tree

1 file changed

+37
-37
lines changed

1 file changed

+37
-37
lines changed

ihp/IHP/RouterSupport.hs

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ import Unsafe.Coerce
5959
import IHP.HaskellSupport hiding (get)
6060
import qualified Data.Typeable as Typeable
6161
import qualified Data.ByteString.Char8 as ByteString
62-
import qualified Data.Char as Char
6362
import Control.Monad.Fail
6463
import Data.String.Conversions (ConvertibleStrings (convertString), cs)
6564
import qualified Text.Blaze.Html5 as Html5
@@ -345,7 +344,7 @@ class Data controller => AutoRoute controller where
345344
parseAction :: Constr -> Parser controller
346345
parseAction constr = let
347346
prefix :: ByteString
348-
prefix = ByteString.pack (actionPrefix @controller)
347+
prefix = Text.encodeUtf8 (actionPrefixText @controller)
349348

350349
actionName = ByteString.pack (showConstr constr)
351350

@@ -412,49 +411,50 @@ class Data controller => AutoRoute controller where
412411
-- All controllers defined in the `Web/` directory don't have a prefix at all.
413412
--
414413
-- E.g. controllers in the `Admin/` directory are prefixed with @/admin/@.
415-
actionPrefix :: forall (controller :: Type). Typeable controller => String
416-
actionPrefix =
417-
case moduleName of
418-
('W':'e':'b':'.':_) -> "/"
419-
('I':'H':'P':'.':_) -> "/"
420-
("") -> "/"
421-
moduleName -> "/" <> let prefix = getPrefix "" moduleName in map Char.toLower prefix <> "/"
414+
actionPrefixText :: forall (controller :: Type). Typeable controller => Text
415+
actionPrefixText
416+
| "Web." `Text.isPrefixOf` moduleName = "/"
417+
| "IHP." `Text.isPrefixOf` moduleName = "/"
418+
| Text.null moduleName = "/"
419+
| otherwise = "/" <> Text.toLower (getPrefix moduleName) <> "/"
422420
where
423-
moduleName :: String
424-
moduleName = Typeable.typeOf (error "unreachable" :: controller)
421+
moduleName :: Text
422+
moduleName = Text.pack $ Typeable.typeOf (error "unreachable" :: controller)
425423
|> Typeable.typeRepTyCon
426424
|> Typeable.tyConModule
427425

428-
-- E.g. getPrefix "" "Admin.User" == "Admin"
429-
getPrefix prefix ('.':_) = prefix
430-
getPrefix prefix (x:xs) = getPrefix (prefix <> [x]) xs
431-
getPrefix prefix [] = prefix
426+
getPrefix :: Text -> Text
427+
getPrefix t = fst (Text.breakOn "." t)
428+
{-# INLINE actionPrefixText #-}
432429

433-
{-# INLINE actionPrefix #-}
434-
435-
-- | Strips the "Action" at the end of action names
430+
-- | Strips the "Action" suffix from action names
436431
--
437-
-- >>> stripActionSuffixString "ShowUserAction"
432+
-- >>> stripActionSuffixByteString "ShowUserAction"
438433
-- "ShowUser"
439434
--
440-
-- >>> stripActionSuffixString "UsersAction"
435+
-- >>> stripActionSuffixByteString "UsersAction"
441436
-- "UsersAction"
442437
--
443-
-- >>> stripActionSuffixString "User"
438+
-- >>> stripActionSuffixByteString "User"
444439
-- "User"
445-
stripActionSuffixString :: String -> String
446-
stripActionSuffixString string =
447-
case string of
448-
"Action" -> ""
449-
(x:xs) -> x : stripActionSuffixString xs
450-
"" -> ""
451-
{-# INLINE stripActionSuffixString #-}
452-
453-
-- | Like 'stripActionSuffixString' but for ByteStrings
454440
stripActionSuffixByteString :: ByteString -> ByteString
455441
stripActionSuffixByteString actionName = fromMaybe actionName (ByteString.stripSuffix "Action" actionName)
456442
{-# INLINE stripActionSuffixByteString #-}
457443

444+
-- | Strips the "Action" suffix from action names
445+
--
446+
-- >>> stripActionSuffixText "ShowUserAction"
447+
-- "ShowUser"
448+
--
449+
-- >>> stripActionSuffixText "UsersAction"
450+
-- "UsersAction"
451+
--
452+
-- >>> stripActionSuffixText "User"
453+
-- "User"
454+
stripActionSuffixText :: Text -> Text
455+
stripActionSuffixText actionName = fromMaybe actionName (Text.stripSuffix "Action" actionName)
456+
{-# INLINE stripActionSuffixText #-}
457+
458458

459459
-- | Returns the create action for a given controller.
460460
-- Example: `createAction @UsersController == Just CreateUserAction`
@@ -529,13 +529,13 @@ instance QueryParam a => QueryParam [a] where
529529

530530
instance {-# OVERLAPPABLE #-} (Show controller, AutoRoute controller) => HasPath controller where
531531
{-# INLINABLE pathTo #-}
532-
pathTo !action = Text.pack (appPrefix <> actionName <> arguments)
532+
pathTo !action = appPrefix <> actionName <> Text.pack arguments
533533
where
534-
appPrefix :: String
535-
!appPrefix = actionPrefix @controller
534+
appPrefix :: Text
535+
!appPrefix = actionPrefixText @controller
536536

537-
actionName :: String
538-
!actionName = stripActionSuffixString $! showConstr constructor
537+
actionName :: Text
538+
!actionName = stripActionSuffixText $! Text.pack (showConstr constructor)
539539

540540
constructor = toConstr action
541541

@@ -804,7 +804,7 @@ webSocketAppWithCustomPathAndHTTPFallback path = do
804804

805805
-- | Defines the start page for a router (when @\/@ is requested).
806806
startPage :: forall action application. (Controller action, InitControllerContext application, ?application::application, ?request :: Request, ?respond :: Respond, Typeable application, Typeable action) => action -> Parser Application
807-
startPage action = get (ByteString.pack (actionPrefix @action)) action
807+
startPage action = get (Text.encodeUtf8 (actionPrefixText @action)) action
808808
{-# INLINABLE startPage #-}
809809

810810
withPrefix prefix routes = string prefix >> choice (map (\r -> r <* endOfInput) routes)
@@ -876,7 +876,7 @@ parseRouteWithId = do
876876

877877
catchAll :: forall action application. (?request :: Request, ?respond :: Respond, Controller action, InitControllerContext application, Typeable action, ?application :: application, Typeable application, Data action) => action -> Parser Application
878878
catchAll action = do
879-
string (ByteString.pack (actionPrefix @action))
879+
string (Text.encodeUtf8 (actionPrefixText @action))
880880
_ <- takeByteString
881881
pure (runAction' @application action)
882882
{-# INLINABLE catchAll #-}

0 commit comments

Comments
 (0)