diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Allen Nelson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Text/Str.hs b/src/Text/Str.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Str.hs
@@ -0,0 +1,224 @@
+{-# LANGUAGE OverloadedStrings, 
+             TypeSynonymInstances, 
+             FlexibleInstances,
+             LambdaCase #-}
+module Text.Str (
+    Str(..)
+  , IsString(..)
+  , show
+  , error
+  , joinLines
+  , joinCommas
+  , joinSemis
+  , joinSlashes
+  , asString
+  , asByteString
+  , asText
+  , asString2
+  , asByteString2
+  , wrapText
+  , wrapString
+  , wrapByteString
+  , wrapByteString2
+  , unlines
+  , putStrLn
+  ) where
+
+import qualified Prelude as P
+import Prelude (IO, Show, String, Char, Bool, Int, Ord(..), (.), id, ($), flip)
+import qualified Codec.Binary.UTF8.String as US
+import Data.Char (isSpace, toLower, toUpper)
+import Data.Hashable
+import Data.Monoid
+import Data.String (IsString(..))
+import qualified Data.List as L
+import qualified Data.List.Utils as L
+import qualified Data.Text as T
+import Data.Text (Text)
+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Base16 as B16
+import Data.ByteString (ByteString)
+import Codec.Utils (Octet)
+
+-- | @Str@ types are any type which can be thought as abstract strings; that 
+-- is, ordered lists of Char. There are at least 3 commonly-used string types
+-- in Haskell (String, ByteString and Text), as well as @newtype@d strings.
+-- The interop with these types can be tedious or even bug-prone. Using
+-- @Str@ allows functions to be written agnostically towards any particular 
+-- type. It provides a set of commonly-needed string manipulation functions,
+-- and the ability to convert to and from a variety of string types, which
+-- lets us "borrow" existing functions which only operate on one of the types
+-- (see the various @as-@ functions). @Str@ extends several useful classes, 
+-- perhaps most importantly @IsString@, which lets us use string literals to 
+-- represent @Str@s.
+class (IsString s, Show s, Ord s, Hashable s, Monoid s) => Str s where 
+  toString :: s -> String
+  toByteString :: s -> ByteString
+  toText :: s -> Text
+  toOctets :: s -> [Octet]
+  toHex :: s -> s
+  toHex = asByteString B16.encode
+  fromText :: Text -> s
+  fromByteString :: ByteString -> s
+  fromOctets :: [Octet] -> s
+  joinBy :: s -> [s] -> s
+  splitOn :: s -> s -> [s]
+  smap :: (Char -> Char) -> s -> s
+  singleton :: Char -> s
+  cons :: Char -> s -> s
+  snoc :: s -> Char -> s
+  lower :: s -> s
+  lower = smap toLower
+  upper :: s -> s
+  upper = smap toUpper
+  capitalize :: s -> s
+  capitalize = asString $ \case
+    "" -> ""
+    (c:cs) -> toUpper c : cs
+  reverse :: s -> s
+  length :: s -> Int
+  dropWhile :: (Char -> Bool) -> s -> s
+  isPrefixOf :: s -> s -> Bool
+  isSuffixOf :: s -> s -> Bool
+  trim :: s -> s
+  trim = let f = reverse . dropWhile isSpace in f . f
+
+
+instance Str String where 
+  toString = id
+  toByteString = BC.pack
+  toText = T.pack
+  toOctets = US.encode
+  fromByteString = BC.unpack
+  fromText = T.unpack
+  fromOctets = US.decode
+  joinBy = L.intercalate
+  smap = P.map
+  splitOn = L.split
+  singleton c = [c]
+  cons = (:)
+  snoc s c = s <> [c]
+  reverse = P.reverse
+  dropWhile = P.dropWhile
+  isPrefixOf = L.isPrefixOf
+  isSuffixOf = L.isSuffixOf
+  length = L.length
+
+instance Str ByteString where 
+  toString = BC.unpack
+  toByteString = id
+  toText = decodeUtf8
+  toOctets = B.unpack
+  fromByteString = id
+  fromText = encodeUtf8
+  fromOctets = B.pack
+  smap = BC.map
+  joinBy = BC.intercalate
+  singleton = BC.singleton
+  splitOn = P.undefined
+  cons = BC.cons
+  snoc = BC.snoc
+  reverse = BC.reverse
+  dropWhile = BC.dropWhile
+  isPrefixOf = B.isPrefixOf
+  isSuffixOf = B.isSuffixOf
+  length = B.length
+
+instance Str Text where 
+  toString = T.unpack
+  toByteString = encodeUtf8
+  toText = id
+  toOctets = US.encode . toString
+  fromByteString = decodeUtf8
+  fromText = id
+  fromOctets = fromString . US.decode
+  smap = T.map
+  joinBy = T.intercalate
+  singleton = T.singleton
+  cons = T.cons
+  snoc = T.snoc
+  reverse = T.reverse
+  dropWhile = T.dropWhile
+  splitOn = T.splitOn
+  isPrefixOf = T.isPrefixOf
+  isSuffixOf = T.isSuffixOf
+  length = T.length
+
+-- | Generalizes @show@ to return any string type.
+show :: (Show a, Str s) => a -> s
+show = fromString . P.show
+
+-- | Generalizes @error@ to accept any string type.
+error :: Str s => s -> a
+error = wrapString P.error
+
+-- | Generalizes @putStrLn@.
+putStrLn :: Str s => s -> IO ()
+putStrLn = wrapString P.putStrLn
+
+-- | Generalizes functions that take a @String@.
+wrapString :: Str s => (String -> a) -> s -> a
+wrapString f = f . toString
+
+-- | Generalizes functions that take a @ByteString@.
+wrapByteString :: Str s => (ByteString -> a) -> s -> a
+wrapByteString f = f . toByteString
+
+-- | Generalizes functions that take two @ByteStrings@.
+wrapByteString2 :: Str s => (ByteString -> ByteString -> a) -> s -> s -> a
+wrapByteString2 f s1 s2 = f (toByteString s1) (toByteString s2)
+
+-- | Converts a function that takes a @Text@ into one that takes any @Str@.
+wrapText :: Str s => (Text -> a) -> s -> a
+wrapText f = f . toText
+
+-- | Converts a function that operates on @String@s to one that operates
+-- on any @Str@.
+asString :: Str s => (String -> String) -> s -> s
+asString func = fromString . func . toString
+
+-- | Converts a function that operates on @ByteString@s to one that operates
+-- on any @Str@.
+asByteString :: Str s => (ByteString -> ByteString) -> s -> s
+asByteString func = fromByteString . func . toByteString
+
+-- | Converts a function that operates on @Text@ to one that operates on any
+-- @Str@.
+asText :: Str s => (Text -> Text) -> s -> s
+asText func = fromText . func . toText
+
+asOctets :: Str s => ([Octet] -> [Octet]) -> s -> s
+asOctets f = fromOctets . f . toOctets
+
+-- | Same as @asString@ but for functions with arity 2.
+asString2 :: Str s => (String -> String -> String) -> s -> s -> s
+asString2 f s1 s2 = fromString $ f (toString s1) (toString s2)
+
+-- | Same as @asByteString@ but for functions with arity 2.
+asByteString2 :: Str s => (ByteString -> ByteString -> ByteString) -> s -> s -> s
+asByteString2 f s1 s2 = fromByteString $ f (toByteString s1) (toByteString s2)
+
+asOctets2 :: Str s => ([Octet] -> [Octet] -> [Octet]) -> s -> s -> s
+asOctets2 f s1 s2 = fromOctets $ f (toOctets s1) (toOctets s2)
+
+-- | Joins strings with newline separation, and adds a trailing newline.
+unlines :: Str s => [s] -> s
+unlines s = joinLines s `snoc` '\n'
+
+-- | Joins strings with newlines.
+joinLines :: Str s => [s] -> s
+joinLines = joinBy "\n"
+
+-- | Joins strings with commas.
+joinCommas :: Str s => [s] -> s
+joinCommas = joinBy ","
+
+-- | Joins strings with semicolons.
+joinSemis :: Str s => [s] -> s
+joinSemis = joinBy ";"
+
+-- | Joins strings with forward slashes.
+joinSlashes :: Str s => [s] -> s
+joinSlashes = joinBy "/"
diff --git a/str.cabal b/str.cabal
new file mode 100644
--- /dev/null
+++ b/str.cabal
@@ -0,0 +1,25 @@
+-- Initial str.cabal generated by cabal init.  For further documentation,
+-- see http://haskell.org/cabal/users-guide/
+
+name:                str
+version:             0.1.0.0
+synopsis:            A type class to abstract between many different string types.
+-- description:
+homepage:            http://www.github.com/thinkpad20/str
+license:             MIT
+license-file:        LICENSE
+author:              Allen Nelson
+maintainer:          ithinkican@gmail.com
+-- copyright:
+category:            Text
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Text.Str
+  -- other-modules:
+  other-extensions:    OverloadedStrings, TypeSynonymInstances, FlexibleInstances, LambdaCase
+  build-depends:       base >=4.7 && <4.8, hashable >=1.2 && <1.3, MissingH >=1.2 && <1.3, text >=1.1 && <1.2, bytestring >=0.10 && <0.11, Crypto, base16-bytestring, utf8-string
+  hs-source-dirs:      src
+  default-language:    Haskell2010
