diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+Version 0.1.0.0
+---------------
+
+*August 20, 2018*
+
+*   Initial release
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Justin Le (c) 2018
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Justin Le nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+# [bins][hackage]
+
+Bin continuous values into discrete containers in an interval, useful for
+histograms.
+
+[hackage]: https://hackage.haskell.org/package/bins
+
+## Usage
+
+```haskell
+-- divide into 10 bins between 5 and 50, logarithmically
+ghci> withBinner (logBS @10 5 50) $ \toBin -> do
+        print (toBin 1)
+        print (toBin 30)
+        print (binIx (toBin 30))
+        print (toBin 100)
+```
+
+```
+Bin (-inf .. 5.0)       -- 1 is outside of range
+Bin [25.06 .. 31.55)    -- 30 is inside bin enclosed by 25.06 and 31.55
+PElem (Finite 7)        -- 30 is in Bin #7 (indexed from 0)
+Bin [50 .. -inf)        -- 100 is outside of range
+```
+
+```haskell
+-- Generate a histogram based on the bins from valules in a list
+ghci> xs = [1..100] :: [Double]
+ghci> withBinner (logBS @10 5 50) $ \toBin -> do
+         mapM_ (\(b, n) -> putStrLn (displayBinDouble 4 b ++ "\t" ++ show n))
+       . M.toList
+       $ binFreq toBin xs
+```
+
+```
+(-inf .. 5.0000)        4
+[5.0000 .. 6.2946)      2
+[6.2946 .. 7.9245)      1
+[7.9245 .. 9.9763)      2
+[9.9763 .. 12.5594)     3
+[12.5594 .. 15.8114)    3
+[15.8114 .. 19.9054)    4
+[19.9054 .. 25.0594)    6
+[25.0594 .. 31.5479)    6
+[31.5479 .. 39.7164)    8
+[39.7164 .. 50.0000)    10
+[50.0000 .. +inf)       51
+```
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/bins.cabal b/bins.cabal
new file mode 100644
--- /dev/null
+++ b/bins.cabal
@@ -0,0 +1,48 @@
+-- This file has been generated from package.yaml by hpack version 0.28.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: af7b58caad9e45647be51713c90572885560f4a41301b7384272bc6827c3264a
+
+name:           bins
+version:        0.1.0.0
+synopsis:       Aggregate continuous values into discrete bins
+description:    Please see the README on GitHub at <https://github.com/mstksg/bins#readme>
+category:       Math
+homepage:       https://github.com/mstksg/bins#readme
+bug-reports:    https://github.com/mstksg/bins/issues
+author:         Justin Le
+maintainer:     justin@jle.im
+copyright:      (c) Justin Le 2018
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+extra-source-files:
+    CHANGELOG.md
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/mstksg/bins
+
+library
+  exposed-modules:
+      Data.Bin
+  other-modules:
+      Paths_bins
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wredundant-constraints
+  build-depends:
+      base >=4.7 && <5
+    , containers
+    , finite-typelits
+    , ghc-typelits-knownnat
+    , ghc-typelits-natnormalise
+    , math-functions
+    , profunctors
+    , reflection
+    , tagged
+    , vector-sized >=1.0
+  default-language: Haskell2010
diff --git a/src/Data/Bin.hs b/src/Data/Bin.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bin.hs
@@ -0,0 +1,435 @@
+{-# LANGUAGE ApplicativeDo                            #-}
+{-# LANGUAGE DataKinds                                #-}
+{-# LANGUAGE DeriveFunctor                            #-}
+{-# LANGUAGE ExistentialQuantification                #-}
+{-# LANGUAGE FlexibleContexts                         #-}
+{-# LANGUAGE KindSignatures                           #-}
+{-# LANGUAGE LambdaCase                               #-}
+{-# LANGUAGE RankNTypes                               #-}
+{-# LANGUAGE RecordWildCards                          #-}
+{-# LANGUAGE ScopedTypeVariables                      #-}
+{-# LANGUAGE StandaloneDeriving                       #-}
+{-# LANGUAGE TypeApplications                         #-}
+{-# LANGUAGE TypeOperators                            #-}
+{-# LANGUAGE UndecidableInstances                     #-}
+{-# LANGUAGE ViewPatterns                             #-}
+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
+{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise       #-}
+
+-- |
+-- Module      : Data.Bin
+-- Copyright   : (c) Justin Le 2018
+-- License     : BSD3
+--
+-- Maintainer  : justin@jle.im
+-- Stability   : experimental
+-- Portability : non-portable
+--
+-- Tools for aggregating numeric values into a set of discrete bins
+-- according to some binning specification.
+--
+-- See 'withBinner' for main usage information, and 'Bin' for the main
+-- binned data type, and 'binFreq' for a common usage example.
+--
+
+module Data.Bin (
+  -- * Specifying the binning
+    BinView, linView, logView, gaussView
+  , BinSpec(..), linBS, logBS, gaussBS
+  , binSpecIntervals
+  -- * Creating and manipulating bins
+  , Bin, Binner, withBinner, fromFin
+  -- ** Inspecting bins
+  , binFin, binRange, binMin, binMax
+  -- ** Showing bins
+  , displayBin, displayBinDouble
+  -- *** In-depth inspection
+  , Pointed(..), pElem, binIx, fromIx
+  -- * Untyped
+  , SomeBin(..), sameBinSpec
+  -- * Handy use patterns
+  , binFreq
+  ) where
+
+import           Control.Monad
+import           Data.Finite
+import           Data.Foldable
+import           Data.Profunctor
+import           Data.Proxy
+import           Data.Reflection
+import           Data.Tagged
+import           Data.Type.Equality
+import           GHC.TypeNats
+import           Numeric.SpecFunctions
+import           Text.Printf
+import           Unsafe.Coerce
+import qualified Data.Map              as M
+import qualified Data.Vector.Sized     as SV
+
+-- | A bidirectional "view" to transform the data type before binning.
+--
+-- See 'linView' for a linear binning, and 'logView' for a logarithmic
+-- binning.
+--
+-- This type is essentially 'Iso' from the /lens/ library, and any 'Iso''
+-- from lens can be used here.  However, it is important that all of these
+-- represent /monotonic/ isomorphisms.
+type BinView a b = forall p. Profunctor p => p b b -> p a a
+
+-- | Construct a 'BinView' based on "to" and "from" functions
+--
+-- It is important that the "to" and "from" functions be /inverses/ of each
+-- other.  Furthermore, both "to" and "from" should be __monotonic__.
+binView
+    :: (a -> b)       -- ^ "to"
+    -> (b -> a)       -- ^ "from"
+    -> BinView a b
+binView = dimap
+
+-- | Linear binning
+linView :: BinView a a
+linView = binView id id
+
+-- | Logarithmic binning (smaller bins at lower levels, larger bins at
+-- higher levels).
+logView :: Floating a => BinView a a
+logView = binView log exp
+
+-- | Binning based on a Gaussian Distribution.  Bins "by standard
+-- deviation"; there are more bins the closer to the mean you get, and less
+-- bins the farther away.
+gaussView
+    :: RealFrac a
+    => a           -- ^ center / mean
+    -> a           -- ^ standard deviation
+    -> BinView a Double
+gaussView μ σ = binView to from
+  where
+    to   = erf . realToFrac . (/ σ) . subtract μ
+    from = (+ μ) . (* σ) . realToFrac . invErf
+
+view :: BinView a b -> a -> b
+view v = runForget (v (Forget id))
+
+review :: BinView a b -> b -> a
+review v = unTagged . v . Tagged
+
+-- | Specification of binning.
+--
+-- A @'BinSpec' n a b@ will bin values of type @a@ into @n@ bins, according
+-- to a scaling in type @b@.
+--
+-- Constructor is meant to be used with type application syntax to indicate
+-- @n@, like @'BinSpec' @5 0 10 'linView'@
+data BinSpec (n :: Nat) a b =
+        BS { bsMin  :: a             -- ^ lower bound of values
+           , bsMax  :: a             -- ^ upper bound of values
+           , bsView :: BinView a b   -- ^ binning view
+           }
+
+-- | Convenient constructor for a 'BinSpec' for a linear scaling.
+--
+-- Meant to be used with type application syntax:
+--
+-- @
+-- 'linBS' @5 0 10
+-- @
+linBS :: forall n a. a -> a -> BinSpec n a a
+linBS mn mx = BS mn mx linView
+
+-- | Convenient constructor for a 'BinSpec' for a logarithmic scaling.
+--
+-- Meant to be used with type application syntax:
+--
+-- @
+-- 'logBS' @5 0 10
+-- @
+logBS :: forall n a. Floating a => a -> a -> BinSpec n a a
+logBS mn mx = BS mn mx logView
+
+-- | Convenient constructor for a 'BinSpec' for a gaussian scaling.  Uses
+-- the midpoint as the inferred mean.
+--
+-- Meant to be used with type application syntax:
+--
+-- @
+-- 'gaussBS' @5 0 10
+-- @
+gaussBS
+    :: forall n a. RealFrac a
+    => a
+    -> a
+    -> a
+    -> BinSpec n a Double
+gaussBS σ mn mx = BS mn mx (gaussView ((mn + mx)/2) σ)
+
+-- | Data type extending a value with an extra "minimum" and "maximum"
+-- value.
+data Pointed a = Bot
+               | PElem !a
+               | Top
+  deriving (Show, Eq, Ord, Functor)
+
+-- | Extract the item from a 'Pointed' if it is neither the extra minimum
+-- or maximum.
+pElem :: Pointed a -> Maybe a
+pElem = \case
+    Bot     -> Nothing
+    PElem x -> Just x
+    Top     -> Nothing
+
+-- | A @'Bin' s n@ is a single bin index out of @n@ partitions of the
+-- original data set, according to a 'BinSpec' represented by @s@.
+--
+-- All 'Bin's with the same @s@ follow the same 'BinSpec', so you can
+-- safely use 'binRange' 'withBinner'.
+--
+-- It has useful 'Eq' and 'Ord' instances.
+--
+-- Actually has @n + 2@ partitions, since it also distinguishes values
+-- that are outside the 'BinSpec' range.
+newtype Bin s n = Bin { _binIx :: Pointed (Finite n) }
+  deriving (Eq, Ord)
+
+-- | A more specific version of 'binFin' that indicates whether or not the
+-- value was too high or too low for the 'BinSpec' range.
+binIx :: Bin s n -> Pointed (Finite n)
+binIx = _binIx
+
+-- | Extract, potentially, the 'Bin' index.  Will return 'Nothing' if the
+-- original value was outside the 'BinSpec' range.
+--
+-- See 'binIx' for a more specific version, which indicates if the original
+-- value was too high or too low.
+binFin :: Bin s n -> Maybe (Finite n)
+binFin = pElem . binIx
+
+tick
+    :: forall n a b. (KnownNat n, Fractional b)
+    => BinSpec n a b
+    -> b
+tick BS{..} = totRange / fromIntegral (natVal (Proxy @n))
+  where
+    totRange = view bsView bsMax - view bsView bsMin
+
+packExtFinite
+    :: KnownNat n
+    => Integer
+    -> Pointed (Finite n)
+packExtFinite n
+    | n < 0     = Bot
+    | otherwise = maybe Top PElem . packFinite $ n
+
+mkBin_
+    :: forall n a b. (KnownNat n, RealFrac b)
+    => BinSpec n a b
+    -> a
+    -> Pointed (Finite n)
+mkBin_ bs = packExtFinite
+          . floor
+          . (/ tick bs)
+          . subtract (scaleIn (bsMin bs))
+          . scaleIn
+  where
+    scaleIn = view (bsView bs)
+
+mkBin
+    :: forall n a b s. (KnownNat n, RealFrac b, Reifies s (BinSpec n a b))
+    => a
+    -> Bin s n
+mkBin = Bin . mkBin_ (reflect (Proxy @s))
+
+-- | The type of a "binning function", given by 'withBinner'.  See
+-- 'withBinner' for information on how to use.
+type Binner s n a = a -> Bin s n
+
+-- | With a 'BinSpec', give a "binning function" that you can use to create
+-- bins within a continuation.  The binning function is meant to be used
+-- with TypeApplications to specify how many bins to use:
+--
+-- @
+-- 'withBinner' myBinSpec $ \toBin ->
+--     show (toBin 2.8523)
+-- @
+withBinner
+    :: (KnownNat n, RealFrac b)
+    => BinSpec n a b
+    -> (forall s. Reifies s (BinSpec n a b) => Binner s n a -> r)
+    -> r
+withBinner bs f = reify bs $ \(_ :: Proxy s) -> f @s mkBin
+
+-- | Generate a vector of the boundaries deriving the bins from
+-- a 'BinSpec'.  Can be useful for debugging.
+binSpecIntervals
+    :: forall n a b. (KnownNat n, Fractional b)
+    => BinSpec n a b
+    -> SV.Vector (n + 1) a
+binSpecIntervals bs = SV.generate $ \i ->
+    case strengthen i of
+      Just (fromIntegral->i') -> scaleOut $ i' * t + scaleIn (bsMin bs)
+      Nothing                 -> bsMax bs
+  where
+    t        = tick bs
+    scaleIn  = view (bsView bs)
+    scaleOut = review (bsView bs)
+
+binRange_
+    :: forall n a b. (KnownNat n, Fractional b)
+    => BinSpec n a b
+    -> Pointed (Finite n)
+    -> (Maybe a, Maybe a)
+binRange_ bs = \case
+    Bot     -> ( Nothing         , Just (SV.head v))
+    PElem i -> ( Just (v `SV.index` weaken i)
+               , Just (v `SV.index` shift i )
+               )
+    Top     -> ( Just (SV.last v), Nothing         )
+  where
+    v        = binSpecIntervals @n bs
+
+-- | Extract the minimum and maximum of the range indicabed by a given
+-- 'Bin'.
+--
+-- A 'Nothing' value indicates that we are outside of the normal range of
+-- the 'BinSpec', so is "unbounded" in that direction.
+binRange
+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))
+    => Bin s n
+    -> (Maybe a, Maybe a)
+binRange = binRange_ (reflect (Proxy @s)) . binIx
+
+-- | Extract the minimum of the range indicabed by a given 'Bin'.
+--
+-- A 'Nothing' value means that the original value was below the minimum
+-- limit of the 'BinSpec', so is "unbounded" in the lower direction.
+binMin
+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))
+    => Bin s n
+    -> Maybe a
+binMin = fst . binRange
+
+-- | Extract the maximum of the range indicabed by a given 'Bin'.
+--
+-- A 'Nothing' value means that the original value was above the maximum
+-- limit of the 'BinSpec', so is "unbounded" in the upper direction.
+binMax
+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))
+    => Bin s n
+    -> Maybe a
+binMax = snd . binRange
+
+-- | Display the interval maintained by a 'Bin'.
+displayBin
+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))
+    => (a -> String)        -- ^ how to display a value
+    -> Bin s n
+    -> String
+displayBin f b = printf "%s .. %s" mn' mx'
+  where
+    (mn, mx) = binRange b
+    mn' = case mn of
+            Nothing -> "(-inf"
+            Just m  -> "[" ++ f m
+    mx' = case mx of
+            Nothing -> "+inf)"
+            Just m  -> f m ++ ")"
+
+-- | Display the interval maintained by a 'Bin', if the 'Bin' contains
+-- a 'Double'.
+displayBinDouble
+    :: forall n b s. (KnownNat n, Fractional b, Reifies s (BinSpec n Double b))
+    => Int                      -- ^ number of decimal places to round
+    -> Bin s n
+    -> String
+displayBinDouble d = displayBin (printf ("%." ++ show d ++ "f"))
+
+instance (KnownNat n, Show a, Fractional b, Reifies s (BinSpec n a b)) => Show (Bin s n) where
+    showsPrec d b = showParen (d > 10) $
+      showString "Bin " . showString (displayBin @n show b)
+
+-- | Generate a histogram: given a container of @a@s, generate a frequency
+-- map of how often values in a given discrete bin occurred.
+--
+-- @
+-- xs :: [Double]
+-- xs = [1..100]
+--
+-- main :: IO ()
+-- main = withBinner (logBS @10 5 50) $ \toBin ->
+--     mapM_ (\(b, n) -> putStrLn (displayBinDouble 4 b ++ "\t" ++ show n))
+--   . M.toList
+--   $ binFreq toBin xs
+-- @
+--
+-- @
+-- (-inf .. 5.0000)        4
+-- [5.0000 .. 6.2946)      2
+-- [6.2946 .. 7.9245)      1
+-- [7.9245 .. 9.9763)      2
+-- [9.9763 .. 12.5594)     3
+-- [12.5594 .. 15.8114)    3
+-- [15.8114 .. 19.9054)    4
+-- [19.9054 .. 25.0594)    6
+-- [25.0594 .. 31.5479)    6
+-- [31.5479 .. 39.7164)    8
+-- [39.7164 .. 50.0000)    10
+-- [50.0000 .. +inf)       51
+-- @
+binFreq
+    :: forall n t a s. Foldable t
+    => Binner s n a
+    -> t a
+    -> M.Map (Bin s n) Int
+binFreq toBin = M.unionsWith (+) . map go . toList
+  where
+    go :: a -> M.Map (Bin s n) Int
+    go x = M.singleton (toBin x) 1
+
+-- | Construct a 'Bin' if you know the bin number you want to specify, or
+-- if the bin is over or under the maximum.
+fromIx :: Pointed (Finite n) -> Bin s n
+fromIx = Bin
+
+-- | Construct a 'Bin' if you know the bin number you want to specify.  See
+-- 'fromIx' if you want to specify bins that are over or under the maximum,
+-- as well.
+fromFin :: Finite n -> Bin s n
+fromFin = Bin . PElem
+
+-- | A @'SomeBin' a n@ is @'Bin' s n@, except with the 'BinSpec' s hidden.
+-- It's useful for returning out of 'withBinner'.
+--
+-- It has useful 'Eq' and 'Ord' instances.
+--
+-- To be able to "unify" two 'Bin's inside a 'SomeBin', use 'sameBinSpec'
+-- to verify that the two 'SomeBin's were created with the same 'BinSpec'.
+data SomeBin a n = forall s b. (Fractional b, Reifies s (BinSpec n a b)) 
+    => SomeBin { getSomeBin :: Bin s n }
+
+deriving instance (KnownNat n, Show a) => Show (SomeBin a n)
+
+-- | Compares if the ranges match.  Note that this is less performant than
+-- comparing the original 'Bin's, or extracting and using 'sameBinSpec'.
+instance (KnownNat n, Eq a) => Eq (SomeBin a n) where
+    SomeBin x == SomeBin y = binRange x == binRange y
+
+-- | Lexicographical ordering -- compares the lower bound, then the upper
+-- bounds.  Note that this is less performant than comparing the original
+-- 'Bin's, or extracting and using 'sameBinSpec'
+instance (KnownNat n, Ord a) => Ord (SomeBin a n) where
+    compare (SomeBin x) (SomeBin y) = compare (binRange x) (binRange y)
+
+-- | Verify that the two reified 'BinSpec' types refer to the same one,
+-- allowing you to use functions like '==' and 'compare' on 'Bin's that you
+-- get out of a 'SomeBin'.
+sameBinSpec
+    :: forall s t n a b p. (Reifies s (BinSpec n a b), Reifies t (BinSpec n a b), KnownNat n, Eq a, Fractional b)
+    => p s
+    -> p t
+    -> Maybe (s :~: t)
+sameBinSpec _ _ = do
+    guard $ binSpecIntervals bs1 == binSpecIntervals bs2
+    pure (unsafeCoerce Refl)
+  where
+    bs1 = reflect (Proxy @s)
+    bs2 = reflect (Proxy @t)
