diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Change Log
 
+## 0.35.0
+
+* Use a GADT for `Val a` so it is properly typed. This shouldn't break any
+  programs that produced valid CloudFormation templates, but it should increase
+  safety for templates that use intrinsic functions. This also allows us to
+  properly implement some more intrinsic functions, like `Not`. See
+  https://github.com/freckle/stratosphere/pull/120
+* Added the `Fn::Not` intrinsic function. Fixes
+  https://github.com/freckle/stratosphere/issues/80
+
 ## 0.34.0
 
 * Don't encode `Bool`, `Int`, and `Double` values as strings in JSON. See:
diff --git a/library/Stratosphere/Values.hs b/library/Stratosphere/Values.hs
--- a/library/Stratosphere/Values.hs
+++ b/library/Stratosphere/Values.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE DeriveFunctor #-}
 {-# LANGUAGE GADTs #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE StandaloneDeriving #-}
@@ -15,43 +13,54 @@
 
 import Data.Aeson
 import Data.HashMap.Strict (HashMap)
+import Data.Maybe (fromMaybe)
 import Data.String (IsString (..))
 import Data.Text (Text)
+import Data.Typeable
 import GHC.Exts (IsList(..))
 
--- GADTs are cool, but I couldn't get this to work with FromJSON. Now that we
--- don't have FromJSON though, we can revisit this.
-
--- data Val a where
---   Literal :: a -> Val a
---   Ref :: Text -> Val a
---   If :: Text -> Val a -> Val a -> Val a
---   And :: Val Bool -> Val Bool -> Val Bool
---   Equals :: (Show a, ToJSON a) => Val a -> Val a -> Val Bool
---   Or :: Val Bool -> Val Bool -> Val Bool
-
 -- | This type is a wrapper around any values in a template. A value can be a
 -- 'Literal', a 'Ref', or an intrinsic function. See:
 -- http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
-data Val a
- = Literal a
- | Ref Text
- | If Text (Val a) (Val a)
- | And (Val Bool) (Val Bool)
- | Equals (Val a) (Val a)
- | Or (Val Bool) (Val Bool)
- | GetAtt Text Text
- | 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
+data Val a where
+  Literal :: a -> Val a
+  Ref :: Text -> Val a
+  If :: Text -> Val a -> Val a -> Val a
+  And :: Val Bool -> Val Bool -> Val Bool
+  Equals :: (Show a, ToJSON a, Eq a, Typeable a) => Val a -> Val a -> Val Bool
+  Or :: Val Bool -> Val Bool -> Val Bool
+  Not :: Val Bool -> Val Bool
+  GetAtt :: Text -> Text -> Val a
+  Base64 :: Val Text -> Val Text
+  Join :: Text -> ValList Text -> Val Text
+  Select :: Integer -> ValList a -> Val a
+  FindInMap :: Val Text -> Val Text -> Val Text -> Val a -- ^ Map name, top level key, and second level key
+  ImportValue :: Text -> Val a -- ^ The account-and-region-unique exported name of the value to import
+  Sub :: Text -> Maybe (HashMap Text (Val Text)) -> Val Text -- ^ Substitution string and optional map of values
 
 deriving instance (Show a) => Show (Val a)
-deriving instance (Eq a) => Eq (Val a)
-deriving instance Functor Val
 
+instance Eq a => Eq (Val a) where
+  Literal a == Literal a' = a == a'
+  Ref a == Ref a' = a == a'
+  If a b c == If a' b' c' = a == a' && b == b' && c == c'
+  And a b == And a' b' = a == a' && b == b'
+  Equals a b == Equals a' b' = eqEquals a b a' b'
+  Or a b == Or a' b' = a == a' && b == b'
+  Not a == Not a' = a == a'
+  GetAtt a b == GetAtt a' b' = a == a' && b == b'
+  Base64 a == Base64 a' = a == a'
+  FindInMap a b c == FindInMap a' b' c' = a == a' && b == b' && c == c'
+  ImportValue a == ImportValue a' = a == a'
+  Sub a b == Sub a' b' = a == a' && b == b'
+  _ == _ = False
+
+eqEquals :: (Typeable a, Typeable b, Eq a, Eq b) => a -> a -> b -> b -> Bool
+eqEquals a b a' b' = fromMaybe False $ do
+  a'' <- cast a'
+  b'' <- cast b'
+  pure $ a == a'' && b == b''
+
 instance (IsString a) => IsString (Val a) where
   fromString s = Literal (fromString s)
 
@@ -62,6 +71,7 @@
   toJSON (And x y) = mkFunc "Fn::And" [toJSON x, toJSON y]
   toJSON (Equals x y) = mkFunc "Fn::Equals" [toJSON x, toJSON y]
   toJSON (Or x y) = mkFunc "Fn::Or" [toJSON x, toJSON y]
+  toJSON (Not x) = mkFunc "Fn::Not" [toJSON x]
   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]
@@ -96,8 +106,6 @@
   | Split Text (Val a)
   | GetAZs (Val Text)
   deriving (Show, Eq)
-
-deriving instance Functor ValList
 
 instance IsList (ValList a) where
   type Item (ValList a) = Val a
diff --git a/stratosphere.cabal b/stratosphere.cabal
--- a/stratosphere.cabal
+++ b/stratosphere.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 10ff95d8a3bd9b30a8bd0d461d1de0b4309dd157ef361b2855e113cbbf8995dd
+-- hash: ea7cd0bc0e3e727dd5d25bdf08ba888d69317a1d5ace706408c2e87601a6583e
 
 name:           stratosphere
-version:        0.34.0
+version:        0.35.0
 synopsis:       EDSL for AWS CloudFormation
 description:    EDSL for AWS CloudFormation
 category:       AWS, Cloud
diff --git a/tests/Stratosphere/ValuesSpec.hs b/tests/Stratosphere/ValuesSpec.hs
--- a/tests/Stratosphere/ValuesSpec.hs
+++ b/tests/Stratosphere/ValuesSpec.hs
@@ -20,22 +20,3 @@
 
     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
