-- This file is part of hs-tax
-- Copyright (C) 2018 Fraser Tweedale
--
-- hs-tax is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see .
{-|
This library provides combinators for constructing taxes. It is
based on the
library.
The most basic tax is a flat rate tax:
@
businessTax = 'flat' 0.3
@
To compute the tax, use 'getTax':
@
λ> 'getTax' businessTax (review money 1000000)
$300000.0
@
Taxes form a semigroup (sum of tax outputs) and monoid:
@
λ> getTax (flat 0.1 <> flat 0.2) (review money 10)
$3.0
λ> getTax mempty (review money 10)
$0
@
Marginal tax rates can be constructed using the 'above' combinator,
which taxes the amount above a given threshold at a flat rate.
@
individualIncomeTax =
'above' (review money 18200) 0.19
<> 'above' (review money 37000) (0.325 - 0.19)
<> 'above' (review money 87000) (0.37 - 0.325)
<> 'above' (review money 180000) (0.45 - 0.37)
@
Taxes can be negative. For exmaple, the 'lump', 'above' and 'limit'
combinators can be used to construct a low-income tax offset that
starts at $445 and reduces at a rate of 1.5c per dollar earned over
$37000:
@
lowIncomeTaxOffset =
'limit' mempty
('lump' (review money (-445)) <> 'above' (review money 37000) 0.015)
@
The 'threshold' combinator applies a tax to the full input amount,
if it exceeds the threshold. Some taxes have "shade-in" where the
amount above the threshold is taxed at a higher rate to "catch up"
to some lower flat rate. The 'threshold'' and 'lesserOf'
combinators can be used to construct this tax:
@
medicareLevy =
'threshold'' l ('lesserOf' ('above' l 0.1) ('flat' 0.02))
where l = review money 21656
@
Although some of the combinators deal directory with 'Money', a
'Tax' can be defined for other types. For example, you can tax a
person a certain number of days labour, based on their age.
@
data Sex = M | F
newtype Years = Years Int
newtype Days = Days Int
data Person = Person Years Sex
corvée :: Tax Person Days
corvée = Tax f
where
f (Person (Years age) sex) = Days $ if age >= 18 && age <= maxAge sex then 10 else 0
maxAge sex = case sex of M -> 45 ; F -> 35
@
-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Tax
(
-- * Constructing taxes
Tax(..)
, MoneyTax
, lump
, flat
, threshold
, threshold'
, thresholds
, above
, above'
, marginal
, lesserOf
, greaterOf
, limit
, effective
-- * Miscellanea
, Semigroup(..)
, Monoid(..)
, Profunctor(..)
, module Data.Money
) where
import Data.Monoid (Monoid(..))
import Data.Profunctor (Profunctor(..))
import Data.Semigroup (Semigroup(..))
import Data.Money
-- | A function from gross income to tax payable.
--
-- Taxes form a semigroup where the tax payable is the
-- sum of tax payable of consituent taxes.
--
-- Taxes form a monoid where the identity is a tax of 0%
--
-- Taxes are a profunctor, making it trivial to perform simple
-- transformations of the input and/or output (e.g. rounding
-- down to whole dollars).
--
newtype Tax a b = Tax { getTax :: a -> b }
deriving (Semigroup, Monoid, Functor, Profunctor)
-- | Convenience synonym for working with 'Money'
type MoneyTax a = Tax (Money a) (Money a)
-- | Tax the amount exceeding the threshold at a flat rate.
--
above :: (Num a, Ord a) => Money a -> a -> Tax (Money a) (Money a)
above l = above' l . flat
-- | Tax the amount exceeding the threshold
above' :: (Num b, Ord b) => Money b -> Tax (Money b) a -> Tax (Money b) a
above' l = lmap (\x -> max (x $-$ l) mempty)
-- | Convert a @[(threshold, rate)]@ into a marginal tax.
-- The rates are /cumulative/, i.e. the top marginal rate is the
-- sum of the rates that apply for a given input.
--
marginal :: (Fractional a, Ord a) => [(Money a, a)] -> Tax (Money a) (Money a)
marginal = foldMap (uncurry above)
-- | A lump-sum tax; a fixed value, not affected by the size of the input
--
lump :: a -> Tax b a
lump = Tax . const
-- | Construct a flat rate tax with no threshold
flat :: (Num a) => a -> Tax (Money a) (Money a)
flat = Tax . (*$)
-- | Tax full amount at flat rate if input >= threshold
threshold :: (Num a, Ord a) => Money a -> a -> Tax (Money a) (Money a)
threshold l = threshold' l . flat
-- | Levy the tax if input >= threshold, otherwise don't
threshold' :: (Ord b, Monoid a) => b -> Tax b a -> Tax b a
threshold' l tax = Tax (\x -> if x >= l then getTax tax x else mempty)
-- | Convert a @[(threshold, rate)]@ into a flat tax whose rate is
-- the sum of the rates that apply for a given input. The rates
-- are /cumulative/. For example, if you want to tax people earning
-- >$30,000 20%, and people earning >$50,000 30%, you only tax an
-- extra 10% at 50000:
--
-- @
-- tax = thresholds [(30000, .2), (50000, .1)]
-- @
--
thresholds :: (Fractional a, Ord a) => [(Money a, a)] -> Tax (Money a) (Money a)
thresholds = foldMap (uncurry threshold)
-- | Levy the lesser of two taxes
lesserOf :: (Ord a) => Tax b a -> Tax b a -> Tax b a
lesserOf t1 t2 = Tax (\x -> min (getTax t1 x) (getTax t2 x))
-- | Levy the greater of two taxes
greaterOf :: (Ord a) => Tax b a -> Tax b a -> Tax b a
greaterOf t1 t2 = Tax (\x -> max (getTax t1 x) (getTax t2 x))
-- | Limit the tax payable to the given amount
--
-- This could be used e.g. for limiting a compulsory loan
-- repayment to the balance of the loan, or ensuring a
-- (negative) tax offset does not become a (positive) tax.
--
limit :: (Ord a) => a -> Tax b a -> Tax b a
limit = lesserOf . lump
-- | Given a tax and an amount construct the effective flat tax rate
--
effective
:: (Fractional a)
=> Money a -> Tax (Money a) (Money a) -> Tax (Money a) (Money a)
effective x tax = flat (getTax tax x $/$ x)