diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,13 @@
+Copyright 2020 Taylor Fausak
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.markdown b/README.markdown
new file mode 100644
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,3 @@
+# Witch
+
+:mage_woman: Convert values from one type into another.
diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Witch.hs
@@ -0,0 +1,298 @@
+{-# language AllowAmbiguousTypes #-}
+{-# language DefaultSignatures #-}
+{-# language FlexibleInstances #-}
+{-# language MultiParamTypeClasses #-}
+{-# language ScopedTypeVariables #-}
+
+-- | This module provides the 'From' type class for converting values between
+-- various types. This aims to be a common interface for the various @xToY@ or
+-- @yFromX@ functions you might write instead. It is inspired by the
+-- @std::convert::From@ trait that the Rust programming language provides.
+--
+-- Many Haskell libraries already provide similar functionality. Here's how
+-- this module compares to them:
+--
+-- - <https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Coerce.html>:
+--   This type class is convenient because it's automatically inferred by the
+--   compiler, but it only works for types that have the same runtime
+--   representation.
+--
+-- - <https://hackage.haskell.org/package/convertible-1.1.1.0/docs/Data-Convertible-Base.html>:
+--   This type class allows for conversions to fail.
+--
+-- - <https://hackage.haskell.org/package/basement-0.0.11/docs/Basement-From.html>:
+--   This type class is essentially the same, but the @basement@ package is an
+--   alternative standard library that some people may not want to depend on.
+--
+-- - <https://hackage.haskell.org/package/inj-base-0.2.0.0/docs/Inj-Base.html>:
+--   This type class requires conversions to be injective, as opposed to merely
+--   suggesting it. Also some conversions fail at runtime.
+--
+-- - <https://github.com/mbj/conversions/blob/6ac6c52/src/Data/Conversions/FromType.hs>:
+--   This type class comes with many convenient helper functions, but some of
+--   the provided instances fail at runtime.
+--
+-- - <https://github.com/kframework/kore/blob/626f230/kore/src/From.hs>:
+--   This package is not available on Hackage, but otherwise is very similar to
+--   this one.
+module Witch (From(from), into, via) where
+
+import qualified Data.ByteString as ByteString
+import qualified Data.ByteString.Lazy as LazyByteString
+import qualified Data.Coerce as Coerce
+import qualified Data.Foldable as Foldable
+import qualified Data.Int as Int
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Map as Map
+import qualified Data.Sequence as Seq
+import qualified Data.Set as Set
+import qualified Data.Text as Text
+import qualified Data.Text.Lazy as LazyText
+import qualified Data.Tuple as Tuple
+import qualified Data.Void as Void
+import qualified Data.Word as Word
+import qualified Numeric.Natural as Natural
+
+-- | This type class represents a way to convert values from some type into
+-- another type. The constraint @From a b@ means that you can convert from a
+-- value of type @a@ into a value of type @b@.
+--
+-- This is primarily intended for "zero cost" conversions like @newtype@s. For
+-- example if you wanted to have a type to represent someone's name, you could
+-- say:
+--
+-- > newtype Name = Name String
+-- > instance From String Name
+-- > instance From Name String
+--
+-- And then you could convert back and forth between @Name@s and @String@s:
+--
+-- > let someString = "Taylor"
+-- > let someName = Name someString
+-- > into @Name someString -- convert from string to name
+-- > into @String someName -- convert from name to string
+--
+-- This type class does not have any laws, but it does have some expectations:
+--
+-- - Conversions should be total. A conversion should never fail or crash.
+--   Avoid writing instances like @From Int (Maybe Char)@. (It might be
+--   worthwhile to have a separate @TryFrom@ type class for this.)
+--
+-- - Conversions should be unambiguous. For example there are many ways to
+--   decode a @ByteString@ into @Text@, so you shouldn't provide an instance
+--   for that.
+--
+-- - Conversions should be cheap, ideally free. For example converting from
+--   @String@ to @Text@ is probably fine, but converting from a UTF-8 encoded
+--   @ByteString@ to @Text@ is problematic.
+--
+-- - Conversions should be lossless. In other words if you have @From a b@ then
+--   no two @a@ values should be converted to the same @b@ value. For example
+--   @From Int Integer@ is fine because every @Int@ can be mapped to a
+--   corresponding @Integer@, but @From Integer Int@ is not good because some
+--   @Integer@s are out of bounds and would need to be clamped.
+--
+-- - If you have both @From a b@ and @From b a@, then @from . from@ should be
+--   the same as 'id'. In other words @a@ and @b@ are isomorphic.
+--
+-- - If you have both @From a b@ and @From b c@, then it's up to you if you
+--   want to provide @From a c@. Sometimes using 'via' is ergonomic enough,
+--   other times you want the extra instance. (It would be nice if we could
+--   provide @instance (From a b, From b c) => From a c where from = via \@b@.)
+class From a b where
+  -- | This method converts a value from one type into another. This is
+  -- intended to be used with the @TypeApplications@ language extension. For
+  -- example, here are a few ways to convert from an 'Int' into an 'Integer':
+  --
+  -- > from @Int @Integer 123
+  -- > from @_ @Integer (123 :: Int)
+  -- > from @Int @_ 123 :: Integer
+  -- > from @Int 123 :: Integer
+  -- > from (123 :: Int) :: Integer
+  --
+  -- Often the context around an expression will make the explicit type
+  -- signatures unnecessary. If you find yourself using a partial type
+  -- signature, consider using 'into' instead. For example:
+  --
+  -- > let someInt = 123 :: Int
+  -- > from @_ @Integer someInt -- avoid this
+  -- > into @Integer someInt -- prefer this
+  --
+  -- The default implementation of 'from' simply calls 'Coerce.coerce', which
+  -- works for types that have the same runtime representation.
+  from :: a -> b
+  default from :: Coerce.Coercible a b => a -> b
+  from = Coerce.coerce
+
+-- | This function converts a value from one type into another. This is the
+-- same as 'from' except that the type variables are in the opposite order.
+into :: forall b a . From a b => a -> b
+into = from
+
+-- | This function converts a value from one type into another by going through
+-- some third type. This is the same as calling 'from' (or 'into') twice, but
+-- can sometimes be more convenient.
+--
+-- Note that the type in the middle of the conversion is the first type
+-- variable of this function. In other words, @via \@b \@a \@c@ first converts
+-- from @a@ to @b@, and then from @b@ to @c@. Often both @a@ and @c@ will be
+-- inferred from context, which means you can just write @via \@b@.
+via :: forall b a c . (From a b, From b c) => a -> c
+via = from . (\ x -> x :: b) . from
+
+-- | 'id'
+instance From a a where
+  from = id
+
+-- | 'const'
+instance From a (x -> a) where
+  from = const
+
+-- | 'pure'
+instance From a [a] where
+  from = pure
+
+-- | 'Just'
+instance From a (Maybe a) where
+  from = Just
+
+-- | 'Left'
+instance From a (Either a x) where
+  from = Left
+
+-- | 'Right'
+instance From a (Either x a) where
+  from = Right
+
+-- | 'Void.absurd'
+instance From Void.Void x where
+  from = Void.absurd
+
+-- | 'fst'
+instance From (a, x) a where
+  from = fst
+
+-- | 'snd'
+instance From (x, a) a where
+  from = snd
+
+-- | 'Tuple.swap'
+instance From (a, b) (b, a) where
+  from = Tuple.swap
+
+-- | 'NonEmpty.toList'
+instance From (NonEmpty.NonEmpty a) [a] where
+  from = NonEmpty.toList
+
+-- | 'fromIntegral'
+instance From Word.Word8 Word.Word16 where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Word.Word16 Word.Word32 where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Word.Word32 Word.Word64 where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Word Natural.Natural where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Natural.Natural Integer where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Int.Int8 Int.Int16 where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Int.Int16 Int.Int32 where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Int.Int32 Int.Int64 where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Int Integer where
+  from = fromIntegral
+
+-- | 'fromIntegral'
+instance From Integer Rational where
+  from = fromIntegral
+
+-- | 'realToFrac'
+instance From Float Double where
+  from = realToFrac
+
+-- | 'fromEnum'
+instance From Bool Int where
+  from = fromEnum
+
+-- | 'fromEnum'
+instance From Char Int where
+  from = fromEnum
+
+-- | 'ByteString.pack'
+instance From [Word.Word8] ByteString.ByteString where
+  from = ByteString.pack
+
+-- | 'ByteString.unpack'
+instance From ByteString.ByteString [Word.Word8] where
+  from = ByteString.unpack
+
+-- | 'LazyByteString.fromStrict'
+instance From ByteString.ByteString LazyByteString.ByteString where
+  from = LazyByteString.fromStrict
+
+-- | 'LazyByteString.toStrict'
+instance From LazyByteString.ByteString ByteString.ByteString where
+  from = LazyByteString.toStrict
+
+-- | 'Text.pack'
+instance From String Text.Text where
+  from = Text.pack
+
+-- | 'Text.unpack'
+instance From Text.Text String where
+  from = Text.unpack
+
+-- | 'LazyText.fromStrict'
+instance From Text.Text LazyText.Text where
+  from = LazyText.fromStrict
+
+-- | 'LazyText.toStrict'
+instance From LazyText.Text Text.Text where
+  from = LazyText.toStrict
+
+-- | 'Seq.fromList'
+instance From [a] (Seq.Seq a) where
+  from = Seq.fromList
+
+-- | 'Foldable.toList'
+instance From (Seq.Seq a) [a] where
+  from = Foldable.toList
+
+-- | 'Set.fromList'
+--
+-- Note that this will remove duplicate elements from the list.
+instance Ord a => From [a] (Set.Set a) where
+  from = Set.fromList
+
+-- | 'Set.toAscList'
+instance From (Set.Set a) [a] where
+  from = Set.toAscList
+
+-- | 'Map.fromList'
+--
+-- Note that if there are duplicate keys in the list, the one closest to the
+-- end will win.
+instance Ord k => From [(k, v)] (Map.Map k v) where
+  from = Map.fromList
+
+-- | 'Map.toAscList'
+instance From (Map.Map k v) [(k, v)] where
+  from = Map.toAscList
diff --git a/src/test/Main.hs b/src/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/test/Main.hs
@@ -0,0 +1,161 @@
+{-# language TypeApplications #-}
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as LB
+import qualified Data.Foldable as Foldable
+import qualified Data.Int as Int
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Map as Map
+import qualified Data.Sequence as Seq
+import qualified Data.Set as Set
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as LT
+import qualified Data.Tuple as Tuple
+import qualified Data.Word as Word
+import qualified Numeric.Natural as Natural
+import qualified Test.Hspec as Hspec
+import qualified Test.Hspec.QuickCheck as Hspec
+import qualified Test.QuickCheck as QuickCheck
+import qualified Witch
+
+main :: IO ()
+main = Hspec.hspec . Hspec.parallel $ do
+
+  Hspec.describe "From a a" $ do
+    Hspec.prop "from" $ \ x -> Witch.from @Int @Int x == x
+    Hspec.prop "into" $ \ x -> Witch.into @Int @Int x == x
+    Hspec.prop "via" $ \ x -> Witch.via @Int @Int @Int x == x
+
+  Hspec.prop "From a (x -> a)" $ \ x ->
+    Witch.from @Int @(() -> Int) x () == x
+
+  Hspec.prop "From a [a]" $ \ x ->
+    Witch.from @Int @[Int] x == [x]
+
+  Hspec.prop "From a (Maybe a)" $ \ x ->
+    Witch.from @Int @(Maybe Int) x == Just x
+
+  Hspec.prop "From a (Either a x)" $ \ x ->
+    Witch.from @Int @(Either Int ()) x == Left x
+
+  Hspec.prop "From a (Either x a)" $ \ x ->
+    Witch.from @Int @(Either () Int) x == Right x
+
+  Hspec.it "From Void x" $ Hspec.pending
+
+  Hspec.prop "From (a, x) a" $ \ x ->
+    Witch.from @(Int, ()) @Int (x, ()) == x
+
+  Hspec.prop "From (x, a) a" $ \ x ->
+    Witch.from @((), Int) @Int ((), x) == x
+
+  Hspec.prop "From (a, b) (b, a)" $ \ x ->
+    Witch.from @(Char, Int) @(Int, Char) x == Tuple.swap x
+
+  Hspec.prop "From (NonEmpty a) [a]" $ \ x ->
+    Witch.from @(NonEmpty.NonEmpty Int) @[Int] x == NonEmpty.toList x
+
+  Hspec.prop "From Word8 Word16" $ \ x ->
+    Witch.from @Word.Word8 @Word.Word16 x == fromIntegral x
+
+  Hspec.prop "From Word16 Word32" $ \ x ->
+    Witch.from @Word.Word16 @Word.Word32 x == fromIntegral x
+
+  Hspec.prop "From Word32 Word64" $ \ x ->
+    Witch.from @Word.Word32 @Word.Word64 x == fromIntegral x
+
+  Hspec.prop "From Word Natural" $ \ x ->
+    Witch.from @Word @Natural.Natural x == fromIntegral x
+
+  Hspec.prop "From Natural Integer" $ \ x ->
+    Witch.from @Natural.Natural @Integer x == fromIntegral x
+
+  Hspec.prop "From Int8 Int16" $ \ x ->
+    Witch.from @Int.Int8 @Int.Int16 x == fromIntegral x
+
+  Hspec.prop "From Int16 Int32" $ \ x ->
+    Witch.from @Int.Int16 @Int.Int32 x == fromIntegral x
+
+  Hspec.prop "From Int32 Int64" $ \ x ->
+    Witch.from @Int.Int32 @Int.Int64 x == fromIntegral x
+
+  Hspec.prop "From Int Integer" $ \ x ->
+    Witch.from @Int @Integer x == fromIntegral x
+
+  Hspec.prop "From Integer Rational" $ \ x ->
+    Witch.from @Integer @Rational x == fromIntegral x
+
+  Hspec.prop "From Float Double" $ \ x ->
+    Witch.from @Float @Double x == realToFrac x
+
+  Hspec.prop "From Bool Int" $ \ x ->
+    Witch.from @Bool @Int x == fromEnum x
+
+  Hspec.prop "From Char Int" $ \ x ->
+    Witch.from @Char @Int x == fromEnum x
+
+  Hspec.prop "From [Word8] ByteString" $ \ x ->
+    Witch.from @[Word.Word8] @B.ByteString x == B.pack x
+
+  Hspec.prop "From ByteString [Word8]" $ \ x ->
+    Witch.from @B.ByteString @[Word.Word8] x == B.unpack x
+
+  Hspec.prop "From ByteString LazyByteString" $ \ x ->
+    Witch.from @B.ByteString @LB.ByteString x == LB.fromStrict x
+
+  Hspec.prop "From LazyByteString ByteString" $ \ x ->
+    Witch.from @LB.ByteString @B.ByteString x == LB.toStrict x
+
+  Hspec.prop "From String Text" $ \ x ->
+    Witch.from @String @T.Text x == T.pack x
+
+  Hspec.prop "From Text String" $ \ x ->
+    Witch.from @T.Text @String x == T.unpack x
+
+  Hspec.prop "From Text LazyText" $ \ x ->
+    Witch.from @T.Text @LT.Text x == LT.fromStrict x
+
+  Hspec.prop "From LazyText Text" $ \ x ->
+    Witch.from @LT.Text @T.Text x == LT.toStrict x
+
+  Hspec.prop "From [a] (Seq a)" $ \ x ->
+    Witch.from @[Int] @(Seq.Seq Int) x == Seq.fromList x
+
+  Hspec.prop "From (Seq a) [a]" $ \ x ->
+    Witch.from @(Seq.Seq Int) @[Int] x == Foldable.toList x
+
+  Hspec.prop "From [a] (Set a)" $ \ x ->
+    Witch.from @[Int] @(Set.Set Int) x == Set.fromList x
+
+  Hspec.prop "From (Set a) [a]" $ \ x ->
+    Witch.from @(Set.Set Int) @[Int] x == Set.toAscList x
+
+  Hspec.prop "From [(k, v)] (Map k v)" $ \ x ->
+    Witch.from @[(Char, Int)] @(Map.Map Char Int) x == Map.fromList x
+
+  Hspec.prop "From (Map k v) [(k, v)]" $ \ x ->
+    Witch.from @(Map.Map Char Int) @[(Char, Int)] x == Map.toAscList x
+
+instance QuickCheck.Arbitrary Natural.Natural where
+  arbitrary = QuickCheck.arbitrarySizedNatural
+  shrink = QuickCheck.shrinkIntegral
+
+instance QuickCheck.Arbitrary a => QuickCheck.Arbitrary (NonEmpty.NonEmpty a) where
+  arbitrary = QuickCheck.applyArbitrary2 (NonEmpty.:|)
+  shrink = QuickCheck.genericShrink
+
+instance QuickCheck.Arbitrary LB.ByteString where
+  arbitrary = fmap LB.pack QuickCheck.arbitrary
+  shrink = QuickCheck.shrinkMap LB.pack LB.unpack
+
+instance QuickCheck.Arbitrary B.ByteString where
+  arbitrary = fmap B.pack QuickCheck.arbitrary
+  shrink = QuickCheck.shrinkMap B.pack B.unpack
+
+instance QuickCheck.Arbitrary T.Text where
+  arbitrary = fmap T.pack QuickCheck.arbitrary
+  shrink = QuickCheck.shrinkMap T.pack T.unpack
+
+instance QuickCheck.Arbitrary LT.Text where
+  arbitrary = fmap LT.pack QuickCheck.arbitrary
+  shrink = QuickCheck.shrinkMap LT.pack LT.unpack
diff --git a/witch.cabal b/witch.cabal
new file mode 100644
--- /dev/null
+++ b/witch.cabal
@@ -0,0 +1,51 @@
+cabal-version: >= 1.10
+
+build-type: Simple
+category: Data
+description: Witch converts values from one type into another.
+extra-source-files: README.markdown
+license-file: LICENSE.txt
+license: ISC
+maintainer: Taylor Fausak
+name: witch
+synopsis: Convert values from one type into another.
+version: 0.0.0.0
+
+source-repository head
+  location: https://github.com/tfausak/witch
+  type: git
+
+library
+  build-depends:
+    base >= 4.12.0 && < 4.15
+    , bytestring >= 0.10.8 && < 0.11
+    , containers >= 0.6.0 && < 0.7
+    , text >= 1.2.3 && < 1.3
+  default-language: Haskell2010
+  exposed-modules: Witch
+  ghc-options:
+    -Weverything
+    -Wno-implicit-prelude
+    -Wno-safe
+    -Wno-unsafe
+  hs-source-dirs: src/lib
+
+  if impl(ghc >= 8.10)
+    ghc-options:
+      -Wno-missing-safe-haskell-mode
+      -Wno-prepositive-qualified-module
+
+test-suite test
+  build-depends:
+    base
+    , bytestring
+    , containers
+    , hspec >= 2.7.1 && < 2.8
+    , QuickCheck >= 2.13.2 && < 2.14
+    , text
+    , witch
+  default-language: Haskell2010
+  ghc-options: -rtsopts -threaded
+  hs-source-dirs: src/test
+  main-is: Main.hs
+  type: exitcode-stdio-1.0
