diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Change Log
 
+## 0.7.0
+
+* Made `Val` and `ValList` more type-safe by moving some constructors to
+  `ValList` and being more specific with types in functions that only accept
+  `Text` parameters. Specific examples include:
+  - `Join` and `Select` now require a `ValList` argument
+  - `Base64` and `Join` now work only on `Val Text`, not `Val a`
+  - `GetAZs` and `Split` are now in `ValList`, not `Val`
+* Created `ImportValueList` as a `ValList` alternative to `ImportValue`.
+* Added support for `Fn::Sub` intrinsic function.
+
 ## 0.6.0
 
 * **BREAKING CHANGE**: Added `ValList` type. This new type allows you to
diff --git a/library/Stratosphere/ResourceAttributes/AutoScalingRollingUpdatePolicy.hs b/library/Stratosphere/ResourceAttributes/AutoScalingRollingUpdatePolicy.hs
--- a/library/Stratosphere/ResourceAttributes/AutoScalingRollingUpdatePolicy.hs
+++ b/library/Stratosphere/ResourceAttributes/AutoScalingRollingUpdatePolicy.hs
@@ -23,7 +23,7 @@
   , _autoScalingRollingUpdatePolicyMinInstancesInService :: Maybe (Val Integer)
   , _autoScalingRollingUpdatePolicyMinSuccessfulInstancesPercent :: Maybe (Val Integer)
   , _autoScalingRollingUpdatePolicyPauseTime :: Maybe (Val Text)
-  , _autoScalingRollingUpdatePolicySuspendProcess :: Maybe [Val Text]
+  , _autoScalingRollingUpdatePolicySuspendProcess :: Maybe (ValList Text)
   , _autoScalingRollingUpdatePolicyWaitOnResourceSignals :: Maybe (Val Bool)
   } deriving (Show, Eq)
 
@@ -110,7 +110,7 @@
 -- execute scaling policies associated with an alarm. For valid values, see
 -- the ScalingProcesses.member.N parameter for the SuspendProcesses action in
 -- the Auto Scaling API Reference.
-asrupSuspendProcess :: Lens' AutoScalingRollingUpdatePolicy (Maybe [Val Text])
+asrupSuspendProcess :: Lens' AutoScalingRollingUpdatePolicy (Maybe (ValList Text))
 asrupSuspendProcess = lens _autoScalingRollingUpdatePolicySuspendProcess (\s a -> s { _autoScalingRollingUpdatePolicySuspendProcess = a })
 
 -- | Specifies whether the Auto Scaling group waits on signals from new
diff --git a/library/Stratosphere/Values.hs b/library/Stratosphere/Values.hs
--- a/library/Stratosphere/Values.hs
+++ b/library/Stratosphere/Values.hs
@@ -8,6 +8,7 @@
 
 module Stratosphere.Values
   ( Val (..)
+  , sub
   , ValList (..)
   , Integer' (..)
   , Bool' (..)
@@ -16,6 +17,7 @@
   ) where
 
 import Data.Aeson
+import Data.HashMap.Strict (HashMap)
 import qualified Data.HashMap.Strict as HM
 import Data.String (IsString (..))
 import Data.Text (Text)
@@ -42,13 +44,12 @@
  | Equals (Val Bool) (Val Bool)
  | Or (Val Bool) (Val Bool)
  | GetAtt Text Text
- | Base64 (Val a)
- | Join Text [Val a]
- | Split Text Text
- | Select Integer (Val a)
- | GetAZs (Val a)
+ | Base64 (Val Text)
+ | Join Text (ValList Text)
+ | Select Integer (ValList a)
  | FindInMap (Val a) (Val a) (Val a) -- ^ Map name, top level key, and second level key
  | ImportValue Text -- ^ The account-and-region-unique exported name of the value to import
+ | Sub Text (Maybe (HashMap Text (Val Text))) -- ^ Substitution string and optional map of values
 
 deriving instance (Show a) => Show (Val a)
 deriving instance (Eq a) => Eq (Val a)
@@ -67,16 +68,23 @@
   toJSON (GetAtt x y) = mkFunc "Fn::GetAtt" [toJSON x, toJSON y]
   toJSON (Base64 v) = object [("Fn::Base64", toJSON v)]
   toJSON (Join d vs) = mkFunc "Fn::Join" [toJSON d, toJSON vs]
-  toJSON (Split d s) = mkFunc "Fn::Split" [toJSON d, toJSON s]
   toJSON (Select i vs) = mkFunc "Fn::Select" [toJSON (Integer' i), toJSON vs]
-  toJSON (GetAZs r) = object [("Fn::GetAZs", toJSON r)]
   toJSON (FindInMap mapName topKey secondKey) =
     object [("Fn::FindInMap", toJSON [toJSON mapName, toJSON topKey, toJSON secondKey])]
-  toJSON (ImportValue t) = object [("Fn::ImportValue", toJSON t)]
+  toJSON (ImportValue t) = importValueToJSON t
+  toJSON (Sub s Nothing) = object [("Fn::Sub", toJSON s)]
+  toJSON (Sub s (Just vals)) = mkFunc "Fn::Sub" [toJSON s, Object (toJSON <$> vals)]
 
+-- | Simple version of 'Sub' without a map of values.
+sub :: Text -> Val Text
+sub s = Sub s Nothing
+
 refToJSON :: Text -> Value
 refToJSON ref = object [("Ref", toJSON ref)]
 
+importValueToJSON :: Text -> Value
+importValueToJSON ref = object [("Fn::ImportValue", toJSON ref)]
+
 mkFunc :: Text -> [Value] -> Value
 mkFunc name args = object [(name, Array $ fromList args)]
 
@@ -92,27 +100,33 @@
       [("Fn::GetAtt", o')] -> uncurry GetAtt <$> parseJSON o'
       [("Fn::Base64", o')] -> Base64 <$> parseJSON o'
       [("Fn::Join", o')] -> uncurry Join <$> parseJSON o'
-      [("Fn::Split", o')] -> uncurry Split <$> parseJSON o'
       [("Fn::Select", o')] -> (\(x, y) -> Select (unInteger' x) y) <$> parseJSON o'
-      [("Fn::GetAZs", o')] -> GetAZs <$> parseJSON o'
       [("Fn::FindInMap", o')] -> do
         (mapName, topKey, secondKey) <- parseJSON o'
         return (FindInMap mapName topKey secondKey)
       [("Fn::ImportValue", o')] -> ImportValue <$> parseJSON o'
+      [("Fn::Sub", vals)] ->
+        case vals of
+          (String s) -> return $ Sub s Nothing
+          otherwise' -> uncurry Sub <$> parseJSON otherwise'
       [(n, o')] -> Literal <$> parseJSON (object [(n, o')])
       os -> Literal <$> parseJSON (object os)
   parseJSON v = Literal <$> parseJSON v
 
--- | There are two ways to construct a list of 'Val's. One is to use a literal
--- 'ValList' to construct the list. The other is to reference something that is
--- already a list. For example, if you have a parameter called @SubnetIds@ of
--- type @List<AWS::EC2::Subnet::Id>@ then, you can use @RefList "SubnetIds"@ to
+-- | 'ValList' is like 'Val', except it is used in place of lists of Vals in
+-- templates. For example, if you have a parameter called @SubnetIds@ of type
+-- @List<AWS::EC2::Subnet::Id>@ then, you can use @RefList "SubnetIds"@ to
 -- reference it.
 data ValList a
   = ValList [Val a]
   | RefList Text
+  | ImportValueList Text
+  | Split Text Text
+  | GetAZs (Val Text)
   deriving (Show, Eq)
 
+deriving instance Functor ValList
+
 instance IsList (ValList a) where
   type Item (ValList a) = Val a
   fromList = ValList
@@ -121,16 +135,25 @@
   -- This is obviously not meaningful, but the IsList instance is so useful
   -- that I decided to allow it.
   toList (RefList _) = []
+  toList (ImportValueList _) = []
+  toList (Split _ _) = []
+  toList (GetAZs _) = []
 
 instance (ToJSON a) => ToJSON (ValList a) where
   toJSON (ValList vals) = toJSON vals
   toJSON (RefList ref) = refToJSON ref
+  toJSON (ImportValueList ref) = importValueToJSON ref
+  toJSON (Split d s) = mkFunc "Fn::Split" [toJSON d, toJSON s]
+  toJSON (GetAZs r) = object [("Fn::GetAZs", toJSON r)]
 
 instance (FromJSON a) => FromJSON (ValList a) where
   parseJSON a@(Array _) = ValList <$> parseJSON a
   parseJSON (Object o) =
     case HM.toList o of
       [("Ref", o')] -> RefList <$> parseJSON o'
+      [("Fn::ImportValue", o')] -> ImportValueList <$> parseJSON o'
+      [("Fn::Split", o')] -> uncurry Split <$> parseJSON o'
+      [("Fn::GetAZs", o')] -> GetAZs <$> parseJSON o'
       _ -> fail "Could not parse object into RefList"
   parseJSON _ = fail "Expected Array or Object for ValList in parseJSON"
 
diff --git a/stratosphere.cabal b/stratosphere.cabal
--- a/stratosphere.cabal
+++ b/stratosphere.cabal
@@ -3,7 +3,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           stratosphere
-version:        0.6.0
+version:        0.7.0
 synopsis:       EDSL for AWS CloudFormation
 description:    EDSL for AWS CloudFormation
 category:       AWS, Cloud
@@ -733,9 +733,9 @@
     buildable: False
   default-language: Haskell2010
 
-test-suite style
+test-suite spec
   type: exitcode-stdio-1.0
-  main-is: HLint.hs
+  main-is: Spec.hs
   hs-source-dirs:
       tests
   build-depends:
@@ -749,15 +749,17 @@
     , text >= 1.1
     , unordered-containers >= 0.2
     , base
-    , directory
-    , hlint
+    , stratosphere
+    , hspec
+    , hspec-discover
   other-modules:
-      Main
+      HLint
+      Stratosphere.ValuesSpec
   default-language: Haskell2010
 
-test-suite tasty
+test-suite style
   type: exitcode-stdio-1.0
-  main-is: Main.hs
+  main-is: HLint.hs
   hs-source-dirs:
       tests
   build-depends:
@@ -771,9 +773,9 @@
     , text >= 1.1
     , unordered-containers >= 0.2
     , base
-    , stratosphere
-    , tasty
-    , tasty-hspec
+    , directory
+    , hlint
   other-modules:
-      HLint
+      Spec
+      Stratosphere.ValuesSpec
   default-language: Haskell2010
diff --git a/tests/HLint.hs b/tests/HLint.hs
--- a/tests/HLint.hs
+++ b/tests/HLint.hs
@@ -10,6 +10,7 @@
   , "tests"
   , "-i", "Use newtype instead of data"
   , "-i", "Unused LANGUAGE pragma"
+  , "-i", "Redundant do"
   ]
 
 main :: IO ()
diff --git a/tests/Main.hs b/tests/Main.hs
deleted file mode 100644
--- a/tests/Main.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-import qualified Test.Tasty
-import Test.Tasty.Hspec
-
-main :: IO ()
-main = do
-    test <- testSpec "stratosphere" spec
-    Test.Tasty.defaultMain test
-
-spec :: Spec
-spec = parallel $
-    it "is trivially true" $
-        True `shouldBe` True
diff --git a/tests/Spec.hs b/tests/Spec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/tests/Stratosphere/ValuesSpec.hs b/tests/Stratosphere/ValuesSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Stratosphere/ValuesSpec.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE OverloadedLists #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Stratosphere.ValuesSpec
+  ( spec
+  ) where
+
+import Data.Aeson
+import Data.Text (Text)
+import Test.Hspec
+
+import Stratosphere.Values
+
+spec :: Spec
+spec = do
+  describe "Val/ValList JSON instances" $ do
+    it "is idempotent for Sub" $ do
+      idempotentJSON (sub "MyString")
+      idempotentJSON (Sub "${Greeting} ${Name}" (Just []) :: Val Text)
+      let
+        values =
+          [ ("Greeting", "Hello")
+          , ("Name", "Bob")
+          ]
+      idempotentJSON (Sub "${Greeting} ${Name}" (Just values) :: Val Text)
+
+    it "Ref and RefList produce the same JSON" $ do
+      toJSON (Ref "MyVal" :: Val Text) `shouldBe` toJSON (RefList "MyVal" :: ValList Text)
+
+    it "ImportValue and ImportValueList produce the same JSON" $ do
+      toJSON (ImportValue "MyVal" :: Val Text) `shouldBe` toJSON (ImportValueList "MyVal" :: ValList Text)
+
+    it "Functor conversions in nested Vals work" $ do
+      let
+        input :: Val Integer
+        input =
+          Select 1 $
+            ValList
+            [ Literal 1
+            , Ref "Hello"
+            , Literal 2
+            ]
+        expected =
+          Select 1 $
+            ValList
+            [ Literal 2
+            , Ref "Hello"
+            , Literal 3
+            ]
+      fmap (+1) input `shouldBe` expected
+
+idempotentJSON :: (ToJSON a, FromJSON a, Show a, Eq a) => a -> Expectation
+idempotentJSON x = decode (encode x) `shouldBe` Just x
