diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for aws-arn
 
+## 0.3.3.0 -- 2025-01-16
+
+* Remove direct dependency on `profunctors`.
+* Add `Network.AWS.ARN.States` with support for State Machine ARNs.
+
 ## 0.3.2.0 -- 2024-08-22
 
 * Use prisms and isos from `microlens-pro`.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
 
    **Remark:** If you need to write tests for these functions, the
    corresponding module should live at
-   `test/Network/AWS/ARN/SomeAWSService/Test.hs`
+   `test/Network/AWS/ARN/SomeAWSServiceTest.hs`
 
 4. Define a `_Foo` `Prism'` that combines the parsing/unparsing
    functions above:
diff --git a/aws-arn.cabal b/aws-arn.cabal
--- a/aws-arn.cabal
+++ b/aws-arn.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.2
 name:            aws-arn
-version:         0.3.2.0
+version:         0.3.3.0
 synopsis:
   Types and optics for manipulating Amazon Resource Names (ARNs)
 
@@ -38,7 +38,13 @@
   README.md
 
 tested-with:
-  GHC ==8.10.7 || ==9.0.2 || ==9.2.5 || ==9.4.5 || ==9.6.6 || ==9.8.2
+  GHC ==8.10.7
+   || ==9.0.2
+   || ==9.2.5
+   || ==9.4.5
+   || ==9.6.6
+   || ==9.8.2
+   || ==9.10.1
 
 common opts
   default-language: Haskell2010
@@ -51,10 +57,9 @@
 
 common deps
   build-depends:
-    , base             >=4.12   && <4.20
+    , base             >=4.12   && <4.21
     , deriving-compat  >=0.5.10 && <0.7
     , microlens-pro    ^>=0.2
-    , profunctors      >=5.0    && <5.7
     , tagged           ^>=0.8
     , text             ^>=1.2.3 || ^>=2.0 || ^>=2.1
 
@@ -64,6 +69,7 @@
     Network.AWS.ARN
     Network.AWS.ARN.Lambda
     Network.AWS.ARN.S3
+    Network.AWS.ARN.States
 
   build-depends:   hashable >=1.3.0.0 && <1.6
   hs-source-dirs:  src
@@ -73,9 +79,10 @@
   type:               exitcode-stdio-1.0
   main-is:            Main.hs
   other-modules:
-    Network.AWS.ARN.Lambda.Test
-    Network.AWS.ARN.S3.Test
-    Network.AWS.ARN.Test
+    Network.AWS.ARN.LambdaTest
+    Network.AWS.ARN.S3Test
+    Network.AWS.ARN.StatesTest
+    Network.AWS.ARNTest
 
   hs-source-dirs:     test
   ghc-options:        -threaded
diff --git a/src/Network/AWS/ARN/States.hs b/src/Network/AWS/ARN/States.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/ARN/States.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.AWS.ARN.States
+  ( StateMachine (..),
+    parseStateMachine,
+    renderStateMachine,
+    _StateMachine,
+  )
+where
+
+import Data.Hashable (Hashable)
+import Data.Maybe (maybeToList)
+import Data.Text (Text)
+import qualified Data.Text as T
+import GHC.Generics (Generic)
+import Lens.Micro.Pro (Prism', prism')
+
+-- $setup
+-- >>> :set -XOverloadedStrings
+-- >>> import Lens.Micro.Pro ((^?))
+
+-- | An AWS State Machine, made of its name and optional qualifier.
+--
+-- >>> "stateMachine:orderProcessor" ^? _StateMachine
+-- Just (StateMachine {name = "orderProcessor", qualifier = Nothing})
+--
+-- >>> "stateMachine:orderProcessor:v2" ^? _StateMachine
+-- Just (StateMachine {name = "orderProcessor", qualifier = Just "v2"})
+--
+-- @since 0.3.3.0
+data StateMachine = StateMachine
+  { name :: Text
+  , qualifier :: Maybe Text
+  }
+  deriving (Eq, Ord, Hashable, Show, Generic)
+
+-- | @since 0.3.3.0
+parseStateMachine :: Text -> Maybe StateMachine
+parseStateMachine t = case T.splitOn ":" t of
+  ("stateMachine" : nam : qual) ->
+    Just (StateMachine nam) <*> case qual of
+      [q] -> Just $ Just q
+      [] -> Just Nothing
+      _ -> Nothing
+  _ -> Nothing
+
+-- | @since 0.3.3.0
+renderStateMachine :: StateMachine -> Text
+renderStateMachine s =
+  T.intercalate ":" $
+    ["stateMachine", name s] ++ maybeToList (qualifier s)
+
+-- | @since 0.3.3.0
+_StateMachine :: Prism' Text StateMachine
+_StateMachine = prism' renderStateMachine parseStateMachine
diff --git a/test/Network/AWS/ARN/Lambda/Test.hs b/test/Network/AWS/ARN/Lambda/Test.hs
deleted file mode 100644
--- a/test/Network/AWS/ARN/Lambda/Test.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-module Network.AWS.ARN.Lambda.Test where
-
-import Data.Text (Text)
-import Lens.Micro.Pro (Lens', set, (^?))
-import Network.AWS.ARN.Lambda
-import Test.Tasty
-import Test.Tasty.HUnit
-
-test_all :: TestTree
-test_all =
-  testGroup
-    "Network.AWS.ARN.Lambda"
-    [ testGroup
-        "Function"
-        [ testGroup
-            "parsing"
-            [ testCase "unqualified function name" $
-                "function:foo" ^? _Function @?= Just (Function "foo" Nothing),
-              testCase "qualified function name" $
-                "function:bar:3" ^? _Function @?= Just (Function "bar" $ Just "3")
-            ],
-          testGroup
-            "updating"
-            [ testCase "setting qualifier" $
-                set (_Function . fQualifier) (Just "baz") "function:foo"
-                  @?= "function:foo:baz",
-              testCase "unsetting qualifier" $
-                set (_Function . fQualifier) Nothing "function:quux:42"
-                  @?= "function:quux"
-            ]
-        ]
-    ]
-
-fQualifier :: Lens' Function (Maybe Text)
-fQualifier l f@Function {qualifier} = (\q -> f {qualifier = q}) <$> l qualifier
diff --git a/test/Network/AWS/ARN/LambdaTest.hs b/test/Network/AWS/ARN/LambdaTest.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/AWS/ARN/LambdaTest.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.AWS.ARN.LambdaTest where
+
+import Data.Text (Text)
+import Lens.Micro.Pro (Lens', set, (^?))
+import Network.AWS.ARN.Lambda
+import Test.Tasty
+import Test.Tasty.HUnit
+
+test_all :: TestTree
+test_all =
+  testGroup
+    "Network.AWS.ARN.Lambda"
+    [ testGroup
+        "Function"
+        [ testGroup
+            "parsing"
+            [ testCase "unqualified function name" $
+                "function:foo" ^? _Function @?= Just (Function "foo" Nothing),
+              testCase "qualified function name" $
+                "function:bar:3" ^? _Function @?= Just (Function "bar" $ Just "3")
+            ],
+          testGroup
+            "updating"
+            [ testCase "setting qualifier" $
+                set (_Function . fQualifier) (Just "baz") "function:foo"
+                  @?= "function:foo:baz",
+              testCase "unsetting qualifier" $
+                set (_Function . fQualifier) Nothing "function:quux:42"
+                  @?= "function:quux"
+            ]
+        ]
+    ]
+
+fQualifier :: Lens' Function (Maybe Text)
+fQualifier l f@Function {qualifier} = (\q -> f {qualifier = q}) <$> l qualifier
diff --git a/test/Network/AWS/ARN/S3/Test.hs b/test/Network/AWS/ARN/S3/Test.hs
deleted file mode 100644
--- a/test/Network/AWS/ARN/S3/Test.hs
+++ /dev/null
@@ -1,45 +0,0 @@
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-module Network.AWS.ARN.S3.Test where
-
-import Data.Text (Text)
-import Lens.Micro.Pro (Lens', set, (^?))
-import Network.AWS.ARN.S3
-import Test.Tasty
-import Test.Tasty.HUnit
-
-test_all :: TestTree
-test_all =
-  testGroup
-    "Network.AWS.ARN.S3"
-    [ testGroup
-        "Object"
-        [ testGroup
-            "parsing"
-            [ testCase "s3 bucket" $
-                "bucket-name" ^? _Object @?= Nothing,
-              testCase "s3 bucket and object" $
-                "bucket-name/my/object" ^? _Object @?= Just (Object (Bucket "bucket-name") "my/object")
-            ],
-          testGroup
-            "updating"
-            [ testCase "setting object key" $
-                set (_Object . rObjectKey) "my/other/object" "bucket-name/my/object"
-                  @?= "bucket-name/my/other/object"
-            ]
-        ],
-      testGroup
-        "S3 bucket"
-        [ testGroup
-            "parsing"
-            [ testCase "s3 bucket" $
-                "bucket-name" ^? _Bucket @?= Just (Bucket "bucket-name"),
-              testCase "s3 bucket and object" $
-                "bucket-name/my/object" ^? _Bucket @?= Nothing
-            ]
-        ]
-    ]
-
-rObjectKey :: Lens' Object Text
-rObjectKey l r@Object {objectKey} = (\o -> r {objectKey = o}) <$> l objectKey
diff --git a/test/Network/AWS/ARN/S3Test.hs b/test/Network/AWS/ARN/S3Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/AWS/ARN/S3Test.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.AWS.ARN.S3Test where
+
+import Data.Text (Text)
+import Lens.Micro.Pro (Lens', set, (^?))
+import Network.AWS.ARN.S3
+import Test.Tasty
+import Test.Tasty.HUnit
+
+test_all :: TestTree
+test_all =
+  testGroup
+    "Network.AWS.ARN.S3"
+    [ testGroup
+        "Object"
+        [ testGroup
+            "parsing"
+            [ testCase "s3 bucket" $
+                "bucket-name" ^? _Object @?= Nothing,
+              testCase "s3 bucket and object" $
+                "bucket-name/my/object" ^? _Object @?= Just (Object (Bucket "bucket-name") "my/object")
+            ],
+          testGroup
+            "updating"
+            [ testCase "setting object key" $
+                set (_Object . rObjectKey) "my/other/object" "bucket-name/my/object"
+                  @?= "bucket-name/my/other/object"
+            ]
+        ],
+      testGroup
+        "S3 bucket"
+        [ testGroup
+            "parsing"
+            [ testCase "s3 bucket" $
+                "bucket-name" ^? _Bucket @?= Just (Bucket "bucket-name"),
+              testCase "s3 bucket and object" $
+                "bucket-name/my/object" ^? _Bucket @?= Nothing
+            ]
+        ]
+    ]
+
+rObjectKey :: Lens' Object Text
+rObjectKey l r@Object {objectKey} = (\o -> r {objectKey = o}) <$> l objectKey
diff --git a/test/Network/AWS/ARN/StatesTest.hs b/test/Network/AWS/ARN/StatesTest.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/AWS/ARN/StatesTest.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.AWS.ARN.StatesTest where
+
+import Data.Text (Text)
+import Lens.Micro.Pro (Lens', set, (^?))
+import Network.AWS.ARN.States
+import Test.Tasty
+import Test.Tasty.HUnit
+
+test_all :: TestTree
+test_all =
+  testGroup
+    "Network.AWS.ARN.StateMachine"
+    [ testGroup
+        "StateMachine"
+        [ testGroup
+            "parsing"
+            [ testCase "unqualified state machine name" $
+                "stateMachine:workflow" ^? _StateMachine @?= Just (StateMachine "workflow" Nothing),
+              testCase "qualified state machine name" $
+                "stateMachine:workflow:42" ^? _StateMachine @?= Just (StateMachine "workflow" $ Just "42")
+            ],
+          testGroup
+            "updating"
+            [ testCase "setting name" $
+                set (_StateMachine . smName) "newWorkflow" "stateMachine:workflow"
+                  @?= "stateMachine:newWorkflow",
+              testCase "setting qualifier" $
+                set (_StateMachine . smQualifier) (Just "v1") "stateMachine:workflow"
+                  @?= "stateMachine:workflow:v1",
+              testCase "unsetting qualifier" $
+                set (_StateMachine . smQualifier) Nothing "stateMachine:workflow:v1"
+                  @?= "stateMachine:workflow"
+            ]
+        ]
+    ]
+
+smName :: Lens' StateMachine Text
+smName l sm@StateMachine {name} = (\n -> sm {name = n}) <$> l name
+
+smQualifier :: Lens' StateMachine (Maybe Text)
+smQualifier l sm@StateMachine {qualifier} = (\q -> sm {qualifier = q}) <$> l qualifier
diff --git a/test/Network/AWS/ARN/Test.hs b/test/Network/AWS/ARN/Test.hs
deleted file mode 100644
--- a/test/Network/AWS/ARN/Test.hs
+++ /dev/null
@@ -1,60 +0,0 @@
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-module Network.AWS.ARN.Test where
-
-import Data.List.NonEmpty (NonEmpty (..))
-import Data.Text (Text)
-import Lens.Micro.Pro (Lens', over, preview, review)
-import Network.AWS.ARN
-import Test.Tasty
-import Test.Tasty.HUnit
-
-test_all :: TestTree
-test_all =
-  testGroup
-    "Network.AWS.ARN"
-    [ testGroup
-        "basic tests"
-        [ testCase "inspect function name and alias of Lambda ARN" $
-            (resource <$> parseARN aliasedLambdaSampleARN)
-              @?= Just "function:the-coolest-function-ever:Alias",
-          testCase "parses empty region OK" $
-            (region <$> parseARN s3FileSampleARN) @?= Just "",
-          testCase "parses empty account OK" $
-            (account <$> parseARN s3FileSampleARN) @?= Just "",
-          testCase "rejects non-ARNs" $
-            parseARN sampleNotAnARN @?= Nothing
-        ],
-      testGroup
-        "optic tests"
-        [ testCase "prism roundtrip" $
-            (review _ARN <$> preview _ARN authorizerSampleARN)
-              @?= Just authorizerSampleARN,
-          testCase "edit path of Lambda Authorizer ARN" $
-            over (_ARN . arnResource . slashes) (\parts -> take 2 parts ++ ["*"]) authorizerSampleARN
-              @?= "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api/stage/*"
-        ]
-    ]
-
-authorizerSampleARN :: Text
-authorizerSampleARN = "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api/stage/GET/some/deep/path"
-
-aliasedLambdaSampleARN :: Text
-aliasedLambdaSampleARN = "arn:aws:lambda:us-east-1:123456789012:function:the-coolest-function-ever:Alias"
-
-s3FileSampleARN :: Text
-s3FileSampleARN = "arn:aws:s3:::an-okay-bucket/sample.txt"
-
-sampleNotAnARN :: Text
-sampleNotAnARN = "//library.googleapis.com/shelves/shelf1/books/book2"
-
-arnResource :: Lens' (ARN a) a
-arnResource l a@(ARN {resource}) = (\r -> a {resource = r}) <$> l resource
-
--- In Data.List.NonEmpty as of base >=4.16, but not worth breaking
--- compatibility just for this. Remove once the three latest GHC major
--- releases are all base >=4.16.
-prependList :: [a] -> NonEmpty a -> NonEmpty a
-prependList [] ys = ys
-prependList (x : xs) (y :| ys) = x :| xs ++ y : ys
diff --git a/test/Network/AWS/ARNTest.hs b/test/Network/AWS/ARNTest.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/AWS/ARNTest.hs
@@ -0,0 +1,60 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.AWS.ARNTest where
+
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.Text (Text)
+import Lens.Micro.Pro (Lens', over, preview, review)
+import Network.AWS.ARN
+import Test.Tasty
+import Test.Tasty.HUnit
+
+test_all :: TestTree
+test_all =
+  testGroup
+    "Network.AWS.ARN"
+    [ testGroup
+        "basic tests"
+        [ testCase "inspect function name and alias of Lambda ARN" $
+            (resource <$> parseARN aliasedLambdaSampleARN)
+              @?= Just "function:the-coolest-function-ever:Alias",
+          testCase "parses empty region OK" $
+            (region <$> parseARN s3FileSampleARN) @?= Just "",
+          testCase "parses empty account OK" $
+            (account <$> parseARN s3FileSampleARN) @?= Just "",
+          testCase "rejects non-ARNs" $
+            parseARN sampleNotAnARN @?= Nothing
+        ],
+      testGroup
+        "optic tests"
+        [ testCase "prism roundtrip" $
+            (review _ARN <$> preview _ARN authorizerSampleARN)
+              @?= Just authorizerSampleARN,
+          testCase "edit path of Lambda Authorizer ARN" $
+            over (_ARN . arnResource . slashes) (\parts -> take 2 parts ++ ["*"]) authorizerSampleARN
+              @?= "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api/stage/*"
+        ]
+    ]
+
+authorizerSampleARN :: Text
+authorizerSampleARN = "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api/stage/GET/some/deep/path"
+
+aliasedLambdaSampleARN :: Text
+aliasedLambdaSampleARN = "arn:aws:lambda:us-east-1:123456789012:function:the-coolest-function-ever:Alias"
+
+s3FileSampleARN :: Text
+s3FileSampleARN = "arn:aws:s3:::an-okay-bucket/sample.txt"
+
+sampleNotAnARN :: Text
+sampleNotAnARN = "//library.googleapis.com/shelves/shelf1/books/book2"
+
+arnResource :: Lens' (ARN a) a
+arnResource l a@(ARN {resource}) = (\r -> a {resource = r}) <$> l resource
+
+-- In Data.List.NonEmpty as of base >=4.16, but not worth breaking
+-- compatibility just for this. Remove once the three latest GHC major
+-- releases are all base >=4.16.
+prependList :: [a] -> NonEmpty a -> NonEmpty a
+prependList [] ys = ys
+prependList (x : xs) (y :| ys) = x :| xs ++ y : ys
