ordinal 0.5.0.0 → 0.6.0.0
raw patch · 14 files changed
+1205/−820 lines, 14 filesdep +unordered-containersnew-component:exe:ordinalPVP ok
version bump matches the API change (PVP)
Dependencies added: unordered-containers
API changes (from Hackage documentation)
- Text.Numerals.Class: type ClockText = ClockSegment " The 'ClockSegment' that describes the state of minutes within an hour." -> DaySegment " The 'DaySegment' that describes the state of hours within a day." -> Int " The number of hours." -> Int " The number of minutes." -> Text " A 'Text' object that describes the given time."
+ Text.Numerals.Class: type ClockText = -- | The 'ClockSegment' that describes the state of minutes within an hour. ClockSegment -> -- | The 'DaySegment' that describes the state of hours within a day. DaySegment -> -- | The number of hours. Int -> -- | The number of minutes. Int -> -- | A 'Text' object that describes the given time. Text
Files
- CHANGELOG.md +4/−0
- app/Main.hs +86/−0
- ordinal.cabal +13/−1
- src/Text/Numerals.hs +15/−16
- src/Text/Numerals/Algorithm.hs +190/−127
- src/Text/Numerals/Algorithm/Template.hs +40/−35
- src/Text/Numerals/Class.hs +239/−159
- src/Text/Numerals/Internal.hs +69/−45
- src/Text/Numerals/Languages.hs +19/−17
- src/Text/Numerals/Languages/Dutch.hs +123/−97
- src/Text/Numerals/Languages/English.hs +117/−92
- src/Text/Numerals/Languages/French.hs +104/−78
- src/Text/Numerals/Languages/German.hs +115/−90
- src/Text/Numerals/Prefix.hs +71/−63
CHANGELOG.md view
@@ -2,6 +2,10 @@ For a full list of changes, see the history on [GitHub](https://github.com/hapytex/ordinal). +## Version 0.6.0.0++Added an executable to make conversions more convenient.+ ## Version 0.5.0.0 Use `data-default-class` instead of `data-default` to reduce the number of dependencies.
+ app/Main.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import Data.Char (digitToInt, isDigit)+import Data.Default.Class (Default (def))+import Data.HashMap.Strict (HashMap, fromList, lookup)+import Data.Maybe (fromMaybe)+import Data.Text (Text, pack, toCaseFold)+import qualified Data.Text.IO as TI+import System.Console.GetOpt (ArgDescr (NoArg, ReqArg), ArgOrder (RequireOrder), OptDescr (Option), getOpt, usageInfo)+import System.Environment (getArgs)+import Text.Numerals.Algorithm (NumeralsAlgorithm)+import Text.Numerals.Class (toCardinal, toOrdinal, toShortOrdinal, toTimeText')+import Text.Numerals.Languages (dutch, english, french, german)+import Prelude hiding (lookup)++languages_ :: [([Text], NumeralsAlgorithm)]+languages_ =+ [ (["nl", "nld", "dut", "dutch"], dutch),+ (["en", "eng", "english"], english),+ (["fr", "fra", "fre", "french"], french),+ (["de", "deu", "ger", "german"], german)+ ]++languages :: HashMap Text NumeralsAlgorithm+languages = fromList [(toCaseFold k, v) | (ks, v) <- languages_, k <- ks]++data OrdinalMode = Cardinal | Ordinal | ShortOrdinal | Time deriving (Eq, Ord, Read, Show)++data OrdinalConfig = OrdinalConfig {lang :: NumeralsAlgorithm, mode :: OrdinalMode, help :: Bool}++instance Default OrdinalMode where+ def = Cardinal++instance Default OrdinalConfig where+ def = OrdinalConfig english def False++determine :: OrdinalMode -> NumeralsAlgorithm -> Integer -> Text+determine Cardinal = toCardinal+determine Ordinal = toOrdinal+determine ShortOrdinal = toShortOrdinal+determine Time = go+ where+ go lng x = toTimeText' lng (fromIntegral (x `div` 60)) (fromIntegral (x `mod` 60))++findLanguage :: String -> IO NumeralsAlgorithm+findLanguage k =+ case lookup (toCaseFold (pack k)) languages of+ Just x -> pure x+ _ -> fail ("Can not find language \"" ++ k ++ "\".")++options :: [OptDescr (OrdinalConfig -> IO OrdinalConfig)]+options =+ [ Option "l" ["lang", "language"] (ReqArg (\l' v -> (\l -> v {lang = l}) <$> findLanguage l') "lang") "specify the language, English by default",+ Option "c" ["cardinal"] (NoArg (\v -> pure (v {mode = Cardinal}))) "set the number mode to cardinal numbers, which is the deault",+ Option "o" ["ordinal"] (NoArg (\v -> pure (v {mode = Ordinal}))) "set the number mode to ordinal numbers",+ Option "s" ["short-ordinal"] (NoArg (\v -> pure (v {mode = ShortOrdinal}))) "set the number mode to short ordinal numbers",+ Option "t" ["time"] (NoArg (\v -> pure (v {mode = Time}))) "set the number mode to time",+ Option "?h" ["help"] (NoArg (\v -> pure (v {help = True}))) "show this help page"+ ]++header :: String+header = "Usage: ordinal [OPTION...] numbers..."++compilerOpts :: [String] -> IO ([OrdinalConfig -> IO OrdinalConfig], [String])+compilerOpts argv =+ case getOpt RequireOrder options argv of+ (o, n, []) -> return (o, n)+ (_, _, errs) -> ioError (userError (concat errs ++ usageInfo header options))++readInt :: String -> Integer+readInt v = fromMaybe (read v) (go 0 v)+ where+ go h (':' : xs) = Just (60 * h + read xs)+ go h (x : xs) | isDigit x = go (h * 10 + fromIntegral (digitToInt x)) xs+ go _ _ = Nothing++main :: IO ()+main = do+ args <- getArgs+ ~(opts, xs) <- compilerOpts args+ opt <- foldl (>>=) def opts+ if help opt+ then putStrLn (usageInfo header options)+ else mapM_ (TI.putStrLn . determine (mode opt) (lang opt) . readInt) xs
ordinal.cabal view
@@ -1,5 +1,5 @@ name: ordinal-version: 0.5.0.0+version: 0.6.0.0 synopsis: Convert numbers to words in different languages. description: A package based on Python's num2words package that converts numbers@@ -48,6 +48,18 @@ , template-haskell >=2.2.0.0 , vector >= 0.7 default-language: Haskell2010++executable ordinal+ hs-source-dirs: app+ main-is: Main.hs+ build-depends:+ base >=4.7 && <5+ , ordinal+ , data-default-class >=0.0.1+ , text >= 0.1+ , unordered-containers >=0.1.0+ default-language: Haskell2010+ test-suite cardinal type: exitcode-stdio-1.0
src/Text/Numerals.hs view
@@ -1,19 +1,18 @@-{-|-Module : Text.Numerals-Description : The main module that converts numbers to words. This module re-exports a set of modules.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX--The main module of the @ordinal@ package. This module re-exports the most important modules of the package to convert numbers to words in the supported languages and functions to construct algorithmic transformations.--}--module Text.Numerals (- module Text.Numerals.Algorithm- , module Text.Numerals.Class- , module Text.Numerals.Languages- , module Text.Numerals.Prefix- ) where+-- |+-- Module : Text.Numerals+-- Description : The main module that converts numbers to words. This module re-exports a set of modules.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- The main module of the @ordinal@ package. This module re-exports the most important modules of the package to convert numbers to words in the supported languages and functions to construct algorithmic transformations.+module Text.Numerals+ ( module Text.Numerals.Algorithm,+ module Text.Numerals.Class,+ module Text.Numerals.Languages,+ module Text.Numerals.Prefix,+ )+where import Text.Numerals.Algorithm import Text.Numerals.Class
src/Text/Numerals/Algorithm.hs view
@@ -1,92 +1,116 @@-{-# LANGUAGE CPP, DeriveDataTypeable, DeriveGeneric, OverloadedStrings, RankNTypes, TupleSections #-}--{-|-Module : Text.Numerals.Algorithm-Description : A module that contains functions to construct algorithmic conversions from numbers to words.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TupleSections #-} -A module that contains data types and functions to automatically convert a number to words. It has tooling for a 'NumeralsAlgorithm'-as well as a 'HighNumberAlgorithm' that is used to generate a 'ShortScale' or 'LongScale'.--}+-- |+-- Module : Text.Numerals.Algorithm+-- Description : A module that contains functions to construct algorithmic conversions from numbers to words.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- A module that contains data types and functions to automatically convert a number to words. It has tooling for a 'NumeralsAlgorithm'+-- as well as a 'HighNumberAlgorithm' that is used to generate a 'ShortScale' or 'LongScale'.+module Text.Numerals.Algorithm+ ( -- * Data types for number algorithms+ NumeralsAlgorithm,+ numeralsAlgorithm, -module Text.Numerals.Algorithm (- -- * Data types for number algorithms- NumeralsAlgorithm- , numeralsAlgorithm -- * Large number algorithms- , HighNumberAlgorithm(ShortScale, LongScale)- , shortScale, longScale- , shortScaleTitle, longScaleTitle- , valueSplit'+ HighNumberAlgorithm (ShortScale, LongScale),+ shortScale,+ longScale,+ shortScaleTitle,+ longScaleTitle,+ valueSplit',+ -- * Conversion to a 'NumberSegment'- , toSegments- , toSegmentLow, toSegmentMid, toSegmentHigh- -- * Segment compression- , compressSegments- ) where+ toSegments,+ toSegmentLow,+ toSegmentMid,+ toSegmentHigh, -import Control.DeepSeq(NFData)+ -- * Segment compression+ compressSegments,+ )+where -import Data.Data(Data)-import Data.Default.Class(Default(def))-import Data.Foldable(toList)-import Data.List(sortOn)+import Control.DeepSeq (NFData)+import Data.Data (Data)+import Data.Default.Class (Default (def))+import Data.Foldable (toList)+import Data.List (sortOn) #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text, cons, toTitle)-import Data.Vector(Vector, (!), (!?), fromList)+import Data.Text (Text, cons, toTitle)+import Data.Vector (Vector, fromList, (!), (!?)) import qualified Data.Vector as V--import GHC.Generics(Generic)--import Test.QuickCheck(oneof)-import Test.QuickCheck.Arbitrary(Arbitrary(arbitrary, shrink))--import Text.Numerals.Class(- NumToWord(toCardinal, toOrdinal, toShortOrdinal, toTimeText')- , FreeMergerFunction, FreeNumberToWords, FreeValueSplitter- , MergerFunction, MNumberSegment- , NumberSegment(NumberSegment), NumberSegmenting- , ValueSplit(valueSplit), ValueSplitter- , ClockText- , toClockSegment, toDaySegment+import GHC.Generics (Generic)+import Test.QuickCheck (oneof)+import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary, shrink))+import Text.Numerals.Class+ ( ClockText,+ FreeMergerFunction,+ FreeNumberToWords,+ FreeValueSplitter,+ MNumberSegment,+ MergerFunction,+ NumToWord (toCardinal, toOrdinal, toShortOrdinal, toTimeText'),+ NumberSegment (NumberSegment),+ NumberSegmenting,+ ValueSplit (valueSplit),+ ValueSplitter,+ toClockSegment,+ toDaySegment, )-import Text.Numerals.Internal(_genText, _shrinkText, _thousand, _iLogFloor)-import Text.Numerals.Prefix(latinPrefixes)+import Text.Numerals.Internal (_genText, _iLogFloor, _shrinkText, _thousand)+import Text.Numerals.Prefix (latinPrefixes) -- | A data type for algorithmic number to word conversions. Most western -- languages /likely/ can work with this data type.-data NumeralsAlgorithm = NumeralsAlgorithm {- minusWord :: Text -- ^ The word used as prefix to denote negative numbers.- , oneWord :: Text -- ^ The word used to denote /one/ in the language.- , lowWords :: Vector Text -- ^ A 'Vector' of small numbers, the first item is the word for /two/ and each successor is the word for the next number.- , midWords :: [(Integer, Text)] -- ^ A list of 2-tuples where the first item contains the value, and the second the corresponding word, the values are ordered in descending value order.- , highWords :: FreeValueSplitter -- ^ A function that is used to generate words for large values (greater than or equal to one /million/), often constructed with the /short scale/ or /long scale/.- , mergeFunction :: FreeMergerFunction -- ^ A function that specifies how to merge words based on the grammar of that specific language.- , ordinize :: Text -> Text -- ^ A function to conver the /cardinal/ form of a number in an /ordinal/ one.- , shortOrdinal :: FreeNumberToWords -- ^ A function that converts a number to its /short ordinal/ form.- , clockText :: ClockText -- ^ A function that converts the clock segment and day segment to a /Text/ that describes the time of the day in words.+data NumeralsAlgorithm = NumeralsAlgorithm+ { -- | The word used as prefix to denote negative numbers.+ minusWord :: Text,+ -- | The word used to denote /one/ in the language.+ oneWord :: Text,+ -- | A 'Vector' of small numbers, the first item is the word for /two/ and each successor is the word for the next number.+ lowWords :: Vector Text,+ -- | A list of 2-tuples where the first item contains the value, and the second the corresponding word, the values are ordered in descending value order.+ midWords :: [(Integer, Text)],+ -- | A function that is used to generate words for large values (greater than or equal to one /million/), often constructed with the /short scale/ or /long scale/.+ highWords :: FreeValueSplitter,+ -- | A function that specifies how to merge words based on the grammar of that specific language.+ mergeFunction :: FreeMergerFunction,+ -- | A function to conver the /cardinal/ form of a number in an /ordinal/ one.+ ordinize :: Text -> Text,+ -- | A function that converts a number to its /short ordinal/ form.+ shortOrdinal :: FreeNumberToWords,+ -- | A function that converts the clock segment and day segment to a /Text/ that describes the time of the day in words.+ clockText :: ClockText } instance NumToWord NumeralsAlgorithm where- toCardinal NumeralsAlgorithm { minusWord=_minusWord, oneWord=_oneWord, lowWords=_lowWords, midWords=_midWords, highWords=_highWords, mergeFunction=_mergeFunction } = cardinal- where cardinal i- | i < 0 = _minusWord <> cons ' ' (go (-j))- | otherwise = go j- where go = compressSegments _oneWord _mergeFunction . toSegments _lowWords _midWords _highWords- j = fromIntegral i :: Integer-- toOrdinal na@NumeralsAlgorithm { ordinize=_ordinize } = _ordinize . toCardinal na- toShortOrdinal = shortOrdinal- toTimeText' alg h m = clockText alg (toClockSegment m) (toDaySegment h) h m+ toCardinal NumeralsAlgorithm {minusWord = _minusWord, oneWord = _oneWord, lowWords = _lowWords, midWords = _midWords, highWords = _highWords, mergeFunction = _mergeFunction} = cardinal+ where+ cardinal i+ | i < 0 = _minusWord <> cons ' ' (go (-j))+ | otherwise = go j+ where+ go = compressSegments _oneWord _mergeFunction . toSegments _lowWords _midWords _highWords+ j = fromIntegral i :: Integer + toOrdinal na@NumeralsAlgorithm {ordinize = _ordinize} = _ordinize . toCardinal na+ toShortOrdinal = shortOrdinal+ toTimeText' alg h m = clockText alg (toClockSegment m) (toDaySegment h) h m _toNumberScale :: (Integral i, Integral j) => i -> (j, i) _toNumberScale i = (l, k)- where ~(_, l, k) = _iLogFloor _thousand i+ where+ ~(_, l, k) = _iLogFloor _thousand i -- | A data type used for to map larger numbers to words. This data type -- supports the /short scale/ and /long scale/ with /Latin/ prefixes, and@@ -105,7 +129,7 @@ shrink (LongScale ta tb) = ((`LongScale` tb) <$> _shrinkText ta) <> (LongScale ta <$> _shrinkText tb) instance Default HighNumberAlgorithm where- def = ShortScale "illion"+ def = ShortScale "illion" -- | Construct a 'FreeValueSplitter' function for the given suffix for a /short scale/. shortScale :: Text -> FreeValueSplitter@@ -123,28 +147,32 @@ longScaleTitle :: Text -> Text -> FreeValueSplitter longScaleTitle suf1 = valueSplit' toTitle . LongScale suf1 - _highWithSuffix :: Text -> Int -> Maybe Text _highWithSuffix suf = fmap (<> suf) . (latinPrefixes !?) _highToText :: HighNumberAlgorithm -> Int -> Maybe Text _highToText (ShortScale suf) j = _highWithSuffix suf j _highToText (LongScale suf1 suf2) j- | even j = _highWithSuffix suf1 k- | otherwise = _highWithSuffix suf2 k- where k = div j 2+ | even j = _highWithSuffix suf1 k+ | otherwise = _highWithSuffix suf2 k+ where+ k = div j 2 -- | Generate a /value splitter/ for a 'HighNumberAlgorithm' but where the result -- is post-processed by a function.-valueSplit'- :: (Text -> Text) -- ^ The post-processing function.- -> HighNumberAlgorithm -- ^ The 'HighNumberAlgorithm' that is used.- -> FreeValueSplitter -- ^ The 'FreeValueSplitter' result.-valueSplit' f vs i = (m,) . f <$> _highToText vs (j-2)- where ~(j, m) = _toNumberScale i+valueSplit' ::+ -- | The post-processing function.+ (Text -> Text) ->+ -- | The 'HighNumberAlgorithm' that is used.+ HighNumberAlgorithm ->+ -- | The 'FreeValueSplitter' result.+ FreeValueSplitter+valueSplit' f vs i = (m,) . f <$> _highToText vs (j - 2)+ where+ ~(j, m) = _toNumberScale i instance ValueSplit HighNumberAlgorithm where- valueSplit = valueSplit' id+ valueSplit = valueSplit' id -- | A /smart constructor/ for the 'NumeralsAlgorithm' type. This constructor -- allows one to use an arbitrary 'Foldable' type for the low words and mid@@ -154,77 +182,112 @@ _maybeSegment :: Integral i => (i -> NumberSegment i) -> i -> MNumberSegment i _maybeSegment f = go- where go 0 = Nothing- go i = Just (f i)+ where+ go 0 = Nothing+ go i = Just (f i) -- | Convert the given number to a 'NumberSegment' with the given 'Vector' of -- low numbers. Mid words and large numbers are not taken into account. This -- is often the next step after the 'toSegmentMid'.-toSegmentLow :: Integral i- => Vector Text -- ^ A 'Vector' of low words.- -> NumberSegmenting i -- ^ The function that maps the number to the 'NumberSegment'.+toSegmentLow ::+ Integral i =>+ -- | A 'Vector' of low words.+ Vector Text ->+ -- | The function that maps the number to the 'NumberSegment'.+ NumberSegmenting i toSegmentLow vs = go- where go i | i >= nvs = NumberSegment (Just (go dv)) nvs lv (tl md)- | otherwise = NumberSegment Nothing i (vs ! fromIntegral i) Nothing- where (dv, md) = divMod i nvs- lv = V.last vs- nvs = fromIntegral (V.length vs) - 1- tl = _maybeSegment go+ where+ go i+ | i >= nvs = NumberSegment (Just (go dv)) nvs lv (tl md)+ | otherwise = NumberSegment Nothing i (vs ! fromIntegral i) Nothing+ where+ (dv, md) = divMod i nvs+ lv = V.last vs+ nvs = fromIntegral (V.length vs) - 1+ tl = _maybeSegment go _splitRecurse :: Integral i => (i -> NumberSegment i) -> (i -> NumberSegment i) -> i -> Text -> i -> NumberSegment i _splitRecurse f g im v j = NumberSegment hd im v (_maybeSegment g md)- where hd | dv == 1 = Nothing- | otherwise = Just (f dv)- ~(dv, md) = divMod j im+ where+ hd+ | dv == 1 = Nothing+ | otherwise = Just (f dv)+ ~(dv, md) = divMod j im -- | Convert the given number to a 'NumberSegment' with the given 'Vector' of -- low numbers, and the /sorted/ list of mid numbers. Large numbers are not -- taken into account. This is often the next step after the 'toSegmentHigh'.-toSegmentMid :: Integral i- => Vector Text -- ^ A 'Vector' of low words.- -> [(Integer, Text)] -- ^ The list of name and the names of these numbers in /descending/ order for the mid words.- -> NumberSegmenting i -- ^ The function that maps the number to the 'NumberSegment'.+toSegmentMid ::+ Integral i =>+ -- | A 'Vector' of low words.+ Vector Text ->+ -- | The list of name and the names of these numbers in /descending/ order for the mid words.+ [(Integer, Text)] ->+ -- | The function that maps the number to the 'NumberSegment'.+ NumberSegmenting i toSegmentMid lows = go- where go [] n = toSegmentLow lows n- go ma@((m, v) : ms) n- | im > n = goms n- | otherwise = _splitRecurse (go ma) goms im v n- where im = fromIntegral m- goms = go ms+ where+ go [] n = toSegmentLow lows n+ go ma@((m, v) : ms) n+ | im > n = goms n+ | otherwise = _splitRecurse (go ma) goms im v n+ where+ im = fromIntegral m+ goms = go ms -- | Convert the given number to a 'NumberSegment' with the given 'Vector' of -- low numbers, the /sorted/ list of mid numbers, and a 'FreeValueSplitter' for -- large numbers.-toSegmentHigh :: Integral i- => Vector Text -- ^ A 'Vector' of low words.- -> [(Integer, Text)] -- ^ The list of name and the names of these numbers in /descending/ order for the mid words.- -> ValueSplitter i -- ^ The 'ValueSplitter' used for large numbers, likely a splitter from a /short scale/ or /long scale/.- -> NumberSegmenting i -- ^ The function that maps the number to the 'NumberSegment'.+toSegmentHigh ::+ Integral i =>+ -- | A 'Vector' of low words.+ Vector Text ->+ -- | The list of name and the names of these numbers in /descending/ order for the mid words.+ [(Integer, Text)] ->+ -- | The 'ValueSplitter' used for large numbers, likely a splitter from a /short scale/ or /long scale/.+ ValueSplitter i ->+ -- | The function that maps the number to the 'NumberSegment'.+ NumberSegmenting i toSegmentHigh lows mids highs = go- where go v | Just (i, t) <- highs v = _splitRecurse go go i t v- | otherwise = toSegmentMid lows mids v+ where+ go v+ | Just (i, t) <- highs v = _splitRecurse go go i t v+ | otherwise = toSegmentMid lows mids v -- | Convert the given number to a 'NumberSegment' with the given 'Vector' of -- low numbers, the /sorted/ list of mid numbers, and a 'FreeValueSplitter' for -- large numbers.-toSegments :: Integral i- => Vector Text -- ^ A 'Vector' of low words.- -> [(Integer, Text)] -- ^ The list of name and the names of these numbers in /descending/ order for the mid words.- -> ValueSplitter i -- ^ The 'ValueSplitter' used for large numbers, likely a splitter from a /short scale/ or /long scale/.- -> NumberSegmenting i -- ^ The function that maps the number to the 'NumberSegment'.+toSegments ::+ Integral i =>+ -- | A 'Vector' of low words.+ Vector Text ->+ -- | The list of name and the names of these numbers in /descending/ order for the mid words.+ [(Integer, Text)] ->+ -- | The 'ValueSplitter' used for large numbers, likely a splitter from a /short scale/ or /long scale/.+ ValueSplitter i ->+ -- | The function that maps the number to the 'NumberSegment'.+ NumberSegmenting i toSegments = toSegmentHigh -- | Use the given 'MergerFunction' to compress the 'NumberSegment' to a single -- 'Text' object that represents the given number.-compressSegments :: Integral i- => Text -- ^ The value used for /one/ in the specific language.- -> MergerFunction i -- ^ The 'MergerFunction' for the specific language that implements the grammar rules how to merge values.- -> NumberSegment i -- ^ The given 'NumberSegment' value to turn into a 'Text' object.- -> Text -- ^ The 'Text' object that contains the name of the number stored in the 'NumberSegment'.+compressSegments ::+ Integral i =>+ -- | The value used for /one/ in the specific language.+ Text ->+ -- | The 'MergerFunction' for the specific language that implements the grammar rules how to merge values.+ MergerFunction i ->+ -- | The given 'NumberSegment' value to turn into a 'Text' object.+ NumberSegment i ->+ -- | The 'Text' object that contains the name of the number stored in the 'NumberSegment'.+ Text compressSegments one' merger = snd . go- where go (NumberSegment dv' i t md') = _mergeTail md' (dvi * i, merger dvi i dv t)- where (dvi, dv) = _unwrap dv'- _unwrap = maybe (1, one') go- _mergeTail Nothing r = r- _mergeTail (Just md') (vi, v) = (vi + mdi, merger vi mdi v md)- where (mdi, md) = go md'+ where+ go (NumberSegment dv' i t md') = _mergeTail md' (dvi * i, merger dvi i dv t)+ where+ (dvi, dv) = _unwrap dv'+ _unwrap = maybe (1, one') go+ _mergeTail Nothing r = r+ _mergeTail (Just md') (vi, v) = (vi + mdi, merger vi mdi v md)+ where+ (mdi, md) = go md'
src/Text/Numerals/Algorithm/Template.hs view
@@ -1,36 +1,34 @@-{-# LANGUAGE CPP, TemplateHaskellQuotes #-}--{-|-Module : Text.Numerals.Algorithm.Template-Description : A module that constructs template Haskell to make defining an ordinize function more convenient.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX--The module is designed to construct an 'Exp' based on the mapping data provided. It will check if the text object-ends with the given suffix, and replace the suffix with another suffix. It aims to compile this into an efficient function.--}+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskellQuotes #-} -module Text.Numerals.Algorithm.Template (- ordinizeFromDict- ) where+-- |+-- Module : Text.Numerals.Algorithm.Template+-- Description : A module that constructs template Haskell to make defining an ordinize function more convenient.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- The module is designed to construct an 'Exp' based on the mapping data provided. It will check if the text object+-- ends with the given suffix, and replace the suffix with another suffix. It aims to compile this into an efficient function.+module Text.Numerals.Algorithm.Template+ ( ordinizeFromDict,+ )+where -import Data.Map.Strict(Map, elems, fromListWith)+import Data.Map.Strict (Map, elems, fromListWith) #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text, isSuffixOf, pack, snoc)--import Text.Numerals.Internal(_replaceSuffix)--import Language.Haskell.TH(Body(GuardedB), Clause(Clause), Dec(FunD, SigD), Exp(AppE, ConE, LitE, VarE), Guard(NormalG), Lit(CharL, IntegerL, StringL), Name, Pat(VarP), Type(ConT, AppT, ArrowT), mkName)+import Data.Text (Text, isSuffixOf, pack, snoc)+import Language.Haskell.TH (Body (GuardedB), Clause (Clause), Dec (FunD, SigD), Exp (AppE, ConE, LitE, VarE), Guard (NormalG), Lit (CharL, IntegerL, StringL), Name, Pat (VarP), Type (AppT, ArrowT, ConT), mkName)+import Text.Numerals.Internal (_replaceSuffix) _getPrefix :: [Char] -> [Char] -> (Int, [Char]) _getPrefix [] bs = (0, bs) _getPrefix as [] = (length as, [])-_getPrefix aa@(a:as) ba@(b:bs)- | a == b = _getPrefix as bs- | otherwise = (length aa, ba)+_getPrefix aa@(a : as) ba@(b : bs)+ | a == b = _getPrefix as bs+ | otherwise = (length aa, ba) _orCondition :: [Exp] -> Guard _orCondition [] = NormalG (ConE 'False)@@ -47,11 +45,13 @@ _ordinizeSingle :: Exp -> String -> String -> ((Int, String), ([Exp], Exp)) _ordinizeSingle nm sa sb = (p, ([AppE (AppE (VarE 'isSuffixOf) (_packText sa)) nm], _packExp l sc nm))- where p@(l, sc) = _getPrefix sa sb+ where+ p@(l, sc) = _getPrefix sa sb _ordinizeMap :: Exp -> [(String, String)] -> Map (Int, String) ([Exp], Exp) _ordinizeMap n = fromListWith f . map (uncurry (_ordinizeSingle n))- where f (as, a) (bs, _) = (bs ++ as, a)+ where+ f (as, a) (bs, _) = (bs ++ as, a) _toGuard :: ([Exp], Exp) -> (Guard, Exp) _toGuard (gs, es) = (_orCondition gs, es)@@ -59,13 +59,18 @@ -- | Construct a function with the given name that maps suffixes in the first -- item of the 2-tuples to the second item of the 2-tuples. It turns this into a -- declaration.-ordinizeFromDict- :: String -- ^ The name of the function, often this is just @ordinize'@- -> [(String, String)] -- ^ The list of suffixes and their corresponding mapping, the suffixes should be non-overlapping.- -> Name -- ^ The name of the post-processing function in case there was no match, one can for example use 'id'.- -> [Dec] -- ^ The corresponding declaration.+ordinizeFromDict ::+ -- | The name of the function, often this is just @ordinize'@+ String ->+ -- | The list of suffixes and their corresponding mapping, the suffixes should be non-overlapping.+ [(String, String)] ->+ -- | The name of the post-processing function in case there was no match, one can for example use 'id'.+ Name ->+ -- | The corresponding declaration.+ [Dec] ordinizeFromDict nm ts pp = [SigD nnm (tText (tText ArrowT)), FunD nnm [Clause [VarP t] (GuardedB (map _toGuard (elems (_ordinizeMap t' ts)) ++ [(NormalG (ConE 'True), AppE (VarE pp) t')])) []]]- where t = mkName "t"- t' = VarE t- nnm = mkName nm- tText = (`AppT` ConT ''Text)+ where+ t = mkName "t"+ t' = VarE t+ nnm = mkName nm+ tText = (`AppT` ConT ''Text)
src/Text/Numerals/Class.hs view
@@ -1,70 +1,84 @@-{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor, DeriveFoldable, DeriveGeneric, RankNTypes, Safe #-}--{-|-Module : Text.Numerals.Class-Description : A module that contains the typeclasses on which the rest of the module works.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE Safe #-} -A module that defines the typeclasses that are used in the rest of the module. The 'NumToWord' class-is the typeclass that is used by all algorithmic conversion tools.--}+-- |+-- Module : Text.Numerals.Class+-- Description : A module that contains the typeclasses on which the rest of the module works.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- A module that defines the typeclasses that are used in the rest of the module. The 'NumToWord' class+-- is the typeclass that is used by all algorithmic conversion tools.+module Text.Numerals.Class+ ( -- * Typeclasses+ NumToWord (toCardinal, toOrdinal, toShortOrdinal, toWords, toTimeText, toTimeText'),+ ValueSplit (valueSplit), -module Text.Numerals.Class (- -- * Typeclasses- NumToWord(toCardinal, toOrdinal, toShortOrdinal, toWords, toTimeText, toTimeText')- , ValueSplit(valueSplit) -- * Types of numbers- , NumberType(Cardinal, Ordinal, ShortOrdinal)+ NumberType (Cardinal, Ordinal, ShortOrdinal),+ -- * Segmenting a number- , NumberSegment(NumberSegment, segmentDivision, segmentValue, segmentText, segmentRemainder)- , MNumberSegment+ NumberSegment (NumberSegment, segmentDivision, segmentValue, segmentText, segmentRemainder),+ MNumberSegment,+ -- * Segments of time- , ClockSegment(OClock, Past, QuarterPast, ToHalf, Half, PastHalf, QuarterTo, To)- , DayPart(Night, Morning, Afternoon, Evening)- , DaySegment(DaySegment, dayPart, dayHour)- , toDayPart, toDaySegment, toClockSegment- , hourCorrection+ ClockSegment (OClock, Past, QuarterPast, ToHalf, Half, PastHalf, QuarterTo, To),+ DayPart (Night, Morning, Afternoon, Evening),+ DaySegment (DaySegment, dayPart, dayHour),+ toDayPart,+ toDaySegment,+ toClockSegment,+ hourCorrection,+ -- * Convert the current time to words- , currentTimeText, currentTimeText'- -- * Utility type synonyms- , NumberToWords, FreeNumberToWords- , MergerFunction, FreeMergerFunction, ValueSplitter, FreeValueSplitter, NumberSegmenting- , ClockText- ) where+ currentTimeText,+ currentTimeText', -import Control.DeepSeq(NFData, NFData1)+ -- * Utility type synonyms+ NumberToWords,+ FreeNumberToWords,+ MergerFunction,+ FreeMergerFunction,+ ValueSplitter,+ FreeValueSplitter,+ NumberSegmenting,+ ClockText,+ )+where -import Data.Data(Data)-import Data.Default.Class(Default(def))+import Control.DeepSeq (NFData, NFData1)+import Data.Data (Data)+import Data.Default.Class (Default (def)) #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text)-import Data.Time.Clock(getCurrentTime, utctDayTime)-import Data.Time.LocalTime(TimeOfDay(TimeOfDay), TimeZone, timeToTimeOfDay, utcToLocalTimeOfDay)--import GHC.Generics(Generic, Generic1)--import Test.QuickCheck(choose)-import Test.QuickCheck.Arbitrary(Arbitrary(arbitrary, shrink), Arbitrary1(liftArbitrary), arbitrary1, arbitraryBoundedEnum)--import Text.Numerals.Internal(_genText, _shrinkText)+import Data.Text (Text)+import Data.Time.Clock (getCurrentTime, utctDayTime)+import Data.Time.LocalTime (TimeOfDay (TimeOfDay), TimeZone, timeToTimeOfDay, utcToLocalTimeOfDay)+import GHC.Generics (Generic, Generic1)+import Test.QuickCheck (choose)+import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary, shrink), Arbitrary1 (liftArbitrary), arbitrary1, arbitraryBoundedEnum)+import Text.Numerals.Internal (_genText, _shrinkText) -- | A type alias for a function that maps a number to a 'Text' object. type NumberToWords i = i -> Text -- | A type alias for a 'NumberToWords' function, with a free 'Integral' -- variable.-type FreeNumberToWords = forall i . Integral i => NumberToWords i+type FreeNumberToWords = forall i. Integral i => NumberToWords i -- | A type alias of a function that is used to merge the names of two numbers according -- to gramatical rules. The type parameter is the type of the numbers to merge. type MergerFunction i = i -> i -> Text -> Text -> Text -- | A type alias of a 'MergerFunction' function with a free 'Integral' variable.-type FreeMergerFunction = forall i . Integral i => MergerFunction i+type FreeMergerFunction = forall i. Integral i => MergerFunction i -- | A type alias of a function that maps a number to a 2-tuple that contains a -- number and the word for that number. This number is normally the largest@@ -74,7 +88,7 @@ -- | A type alias of a 'ValueSplitter' function, with a free 'Integral' -- variable.-type FreeValueSplitter = forall i . Integral i => ValueSplitter i+type FreeValueSplitter = forall i. Integral i => ValueSplitter i -- | A type alias of a function that converts a number to a 'NumberSegment' for that number. type NumberSegmenting i = i -> NumberSegment i@@ -82,12 +96,17 @@ -- | A data type used to convert a number into segments. Each segment has an -- optional division and remainder part, together with a value and the name of -- that value in a language.-data NumberSegment i = NumberSegment {- segmentDivision :: MNumberSegment i -- ^ The optional division part. 'Nothing' if the division is equal to one.- , segmentValue :: i -- ^ The value of the given segment.- , segmentText :: Text -- ^ The name of the value of the given segment, in a specific language.- , segmentRemainder :: MNumberSegment i -- ^ The optional remainder part. 'Nothing' if the remainder is equal to zero.- } deriving (Data, Eq, Foldable, Functor, Generic, Generic1, Ord, Read, Show)+data NumberSegment i = NumberSegment+ { -- | The optional division part. 'Nothing' if the division is equal to one.+ segmentDivision :: MNumberSegment i,+ -- | The value of the given segment.+ segmentValue :: i,+ -- | The name of the value of the given segment, in a specific language.+ segmentText :: Text,+ -- | The optional remainder part. 'Nothing' if the remainder is equal to zero.+ segmentRemainder :: MNumberSegment i+ }+ deriving (Data, Eq, Foldable, Functor, Generic, Generic1, Ord, Read, Show) instance NFData a => NFData (NumberSegment a) @@ -95,16 +114,16 @@ instance Arbitrary1 NumberSegment where liftArbitrary gen = go- where go = NumberSegment <$> liftArbitrary go <*> gen <*> _genText <*> liftArbitrary go+ where+ go = NumberSegment <$> liftArbitrary go <*> gen <*> _genText <*> liftArbitrary go instance Arbitrary i => Arbitrary (NumberSegment i) where arbitrary = arbitrary1 shrink (NumberSegment dv val txt rm) =- ((\x -> NumberSegment x val txt rm) <$> shrink dv) <>- ((\x -> NumberSegment dv x txt rm) <$> shrink val) <>- ((\x -> NumberSegment dv val x rm) <$> _shrinkText txt) <>- (NumberSegment dv val txt <$> shrink rm)-+ ((\x -> NumberSegment x val txt rm) <$> shrink dv)+ <> ((\x -> NumberSegment dv x txt rm) <$> shrink val)+ <> ((\x -> NumberSegment dv val x rm) <$> _shrinkText txt)+ <> (NumberSegment dv val txt <$> shrink rm) -- | A 'Maybe' variant of the 'NumberSegment' data type. This is used since the -- division part can be one, or the remainder part can be zero.@@ -113,9 +132,12 @@ -- | A data type that specifies the different types of numbers. These can be -- used to specify the "target format". The 'Default' number type is 'Cardinal'. data NumberType- = Cardinal -- ^ /Cardinal/ numbers like one, two, three, etc.- | Ordinal -- ^ /Ordinal/ numbers like first, second, third, etc.- | ShortOrdinal -- ^ /Short ordinal/ numbers like 1st, 2nd, 3rd, etc.+ = -- | /Cardinal/ numbers like one, two, three, etc.+ Cardinal+ | -- | /Ordinal/ numbers like first, second, third, etc.+ Ordinal+ | -- | /Short ordinal/ numbers like 1st, 2nd, 3rd, etc.+ ShortOrdinal deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show) instance Arbitrary NumberType where@@ -125,23 +147,36 @@ -- | The type of a function that converts time to its description. The first -- two parameters are used to make conversion more convenient.-type ClockText- = ClockSegment -- ^ The 'ClockSegment' that describes the state of minutes within an hour.- -> DaySegment -- ^ The 'DaySegment' that describes the state of hours within a day.- -> Int -- ^ The number of hours.- -> Int -- ^ The number of minutes.- -> Text -- ^ A 'Text' object that describes the given time.+type ClockText =+ -- | The 'ClockSegment' that describes the state of minutes within an hour.+ ClockSegment ->+ -- | The 'DaySegment' that describes the state of hours within a day.+ DaySegment ->+ -- | The number of hours.+ Int ->+ -- | The number of minutes.+ Int ->+ -- | A 'Text' object that describes the given time.+ Text -- | A data type that describes the state of the minutes within an hour. data ClockSegment- = OClock -- ^ The number of minutes is zero.- | Past Int -- ^ The parameter is the number of minutes past the hour, this is between @1@ and @14@.- | QuarterPast -- ^ It is a quarter past the hour.- | ToHalf Int -- ^ The parameter is the number of minutes until half, this is between @1@ and @14@.- | Half -- ^ It is half past an hour.- | PastHalf Int -- ^ The parameter is the number of minutes past half, this is between @1@ and @14@.- | QuarterTo -- ^ It is a quarter to an hour.- | To Int -- ^ The parameter is the number of minutes to the next hour, this is between @1@ and @14@.+ = -- | The number of minutes is zero.+ OClock+ | -- | The parameter is the number of minutes past the hour, this is between @1@ and @14@.+ Past Int+ | -- | It is a quarter past the hour.+ QuarterPast+ | -- | The parameter is the number of minutes until half, this is between @1@ and @14@.+ ToHalf Int+ | -- | It is half past an hour.+ Half+ | -- | The parameter is the number of minutes past half, this is between @1@ and @14@.+ PastHalf Int+ | -- | It is a quarter to an hour.+ QuarterTo+ | -- | The parameter is the number of minutes to the next hour, this is between @1@ and @14@.+ To Int deriving (Data, Eq, Generic, Ord, Read, Show) instance NFData ClockSegment@@ -151,10 +186,14 @@ -- | A data type that describes the state of the hours within a day. data DayPart- = Night -- ^ It is night, this means that it is between @0:00@ and @5:59@.- | Morning -- ^ It is morning, this means that it is between @6:00@ and @11:59@.- | Afternoon -- ^ It is afternoon, this means it is between @12:00@ and @17:59@.- | Evening -- ^ It is evening, this means it is between @18:00@ and @23:59@.+ = -- | It is night, this means that it is between @0:00@ and @5:59@.+ Night+ | -- | It is morning, this means that it is between @6:00@ and @11:59@.+ Morning+ | -- | It is afternoon, this means it is between @12:00@ and @17:59@.+ Afternoon+ | -- | It is evening, this means it is between @18:00@ and @23:59@.+ Evening deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show) instance Arbitrary DayPart where@@ -164,11 +203,12 @@ -- | A data type that describes the part of the day, and the number of hours on -- a 12-hour clock.-data DaySegment- = DaySegment {- dayPart :: DayPart -- ^ The part of the day.- , dayHour :: Int -- ^ The number of hours, between @1@ and @12@ (both inclusive).- }+data DaySegment = DaySegment+ { -- | The part of the day.+ dayPart :: DayPart,+ -- | The number of hours, between @1@ and @12@ (both inclusive).+ dayHour :: Int+ } deriving (Data, Eq, Generic, Ord, Read, Show) instance Arbitrary DaySegment where@@ -177,119 +217,159 @@ instance NFData DaySegment -- | Convert the given number of minutes to the corresponding 'ClockSegment'.-toClockSegment- :: Int -- ^ The number of minutes.- -> ClockSegment -- ^ The corresponding 'ClockSegment'.+toClockSegment ::+ -- | The number of minutes.+ Int ->+ -- | The corresponding 'ClockSegment'.+ ClockSegment toClockSegment 0 = OClock toClockSegment 15 = QuarterPast toClockSegment 30 = Half toClockSegment 45 = QuarterTo toClockSegment n- | n <= 15 = Past n- | n <= 30 = ToHalf (30-n)- | n <= 45 = PastHalf (n-30)- | otherwise = To (60-n)+ | n <= 15 = Past n+ | n <= 30 = ToHalf (30 - n)+ | n <= 45 = PastHalf (n - 30)+ | otherwise = To (60 - n) -- | Convert the given number of hours to the corresponding 'DayPart'.-toDayPart- :: Int -- ^ The given number of hours.- -> DayPart -- ^ The corresponding 'DayPart'.+toDayPart ::+ -- | The given number of hours.+ Int ->+ -- | The corresponding 'DayPart'.+ DayPart toDayPart n- | n <= 5 = Night- | n <= 11 = Morning- | n <= 17 = Afternoon- | otherwise = Evening+ | n <= 5 = Night+ | n <= 11 = Morning+ | n <= 17 = Afternoon+ | otherwise = Evening -- | Convert the given number of hours to the corresponding 'DaySegment'.-toDaySegment- :: Int -- ^ The given number of hours.- -> DaySegment -- ^ The corresponding 'DaySegment'.+toDaySegment ::+ -- | The given number of hours.+ Int ->+ -- | The corresponding 'DaySegment'.+ DaySegment toDaySegment n = DaySegment (toDayPart n) (hourCorrection n) -- | Correct the hour to a 12 number segment. -- The input can be any Int number, whereas the -- result will be in the @1 .. 12@ range.-hourCorrection- :: Int -- ^ The value for the number of hours.- -> Int -- ^ The hours in the @1 .. 12@ range.+hourCorrection ::+ -- | The value for the number of hours.+ Int ->+ -- | The hours in the @1 .. 12@ range.+ Int hourCorrection h = ((h - 1) `mod` 12) + 1 instance Default NumberType where- def = Cardinal+ def = Cardinal -- | A type class used for num to word algorithms. It maps an 'Integral' type -- @i@ to 'Text'. class NumToWord a where- -- | Convert the given number to a 'Text' object that is the given number in- -- words in /cardinal/ form.- toCardinal :: Integral i- => a -- ^ The conversion algorithm that transforms the number into words.- -> i -- ^ The number to transform into a /cardinal/ form.- -> Text -- ^ The number in words in a /cardinal/ form.- toCardinal = toWords Cardinal+ -- | Convert the given number to a 'Text' object that is the given number in+ -- words in /cardinal/ form.+ toCardinal ::+ Integral i =>+ -- | The conversion algorithm that transforms the number into words.+ a ->+ -- | The number to transform into a /cardinal/ form.+ i ->+ -- | The number in words in a /cardinal/ form.+ Text+ toCardinal = toWords Cardinal - -- | Convert the given number to a 'Text' object that is the given number in- -- words in /cardinal/ form.- toOrdinal :: Integral i- => a -- ^ The conversion algorithm that transforms the number into words.- -> i -- ^ The number to transform into a /ordinal/ form.- -> Text -- ^ The number in words in a /ordinal/ form.- toOrdinal = toWords Ordinal+ -- | Convert the given number to a 'Text' object that is the given number in+ -- words in /cardinal/ form.+ toOrdinal ::+ Integral i =>+ -- | The conversion algorithm that transforms the number into words.+ a ->+ -- | The number to transform into a /ordinal/ form.+ i ->+ -- | The number in words in a /ordinal/ form.+ Text+ toOrdinal = toWords Ordinal - -- | Convert the given number to a 'Text' object that is the given number- -- in words in /short cardinal/ form.- toShortOrdinal :: Integral i- => a -- ^ The conversion algorithm that transforms the number into words.- -> i -- ^ The number to transform into a /ordinal/ form.- -> Text -- ^ The number in words in a /ordinal/ form.- toShortOrdinal = toWords Ordinal+ -- | Convert the given number to a 'Text' object that is the given number+ -- in words in /short cardinal/ form.+ toShortOrdinal ::+ Integral i =>+ -- | The conversion algorithm that transforms the number into words.+ a ->+ -- | The number to transform into a /ordinal/ form.+ i ->+ -- | The number in words in a /ordinal/ form.+ Text+ toShortOrdinal = toWords Ordinal - -- | Convert the given number to a 'Text' object that is the given number in- -- words in the given 'NumberType'.- toWords :: Integral i- => NumberType -- ^ The given format to convert the number to.- -> a -- ^ The conversion algorithm that transforms the number into words.- -> i -- ^ The number to transform into the given form.- -> Text -- ^ The number in words in the given form.- toWords Cardinal = toCardinal- toWords Ordinal = toOrdinal- toWords ShortOrdinal = toShortOrdinal+ -- | Convert the given number to a 'Text' object that is the given number in+ -- words in the given 'NumberType'.+ toWords ::+ Integral i =>+ -- | The given format to convert the number to.+ NumberType ->+ -- | The conversion algorithm that transforms the number into words.+ a ->+ -- | The number to transform into the given form.+ i ->+ -- | The number in words in the given form.+ Text+ toWords Cardinal = toCardinal+ toWords Ordinal = toOrdinal+ toWords ShortOrdinal = toShortOrdinal - -- | Convert the given time of the day to text describing that time.- toTimeText- :: a -- ^ The conversion algorithm to transform numbers into words.- -> TimeOfDay -- ^ The time of the day to convert to words.- -> Text -- ^ The time as /text/.- toTimeText gen (TimeOfDay h m _) = toTimeText' gen h m+ -- | Convert the given time of the day to text describing that time.+ toTimeText ::+ -- | The conversion algorithm to transform numbers into words.+ a ->+ -- | The time of the day to convert to words.+ TimeOfDay ->+ -- | The time as /text/.+ Text+ toTimeText gen (TimeOfDay h m _) = toTimeText' gen h m - -- | Convert the given hours and minutes to text that describes the time.- toTimeText'- :: a -- ^ The conversion algorithm to transform numbers into words.- -> Int -- ^ The number of hours, between 0 and 23 (both inclusive)- -> Int -- ^ The number of minutes, beween 0 and 59 (both inclusive)- -> Text -- ^ The time as /text/.- toTimeText' gen h m = toTimeText gen (TimeOfDay h m 0)- {-# MINIMAL ((toCardinal, toOrdinal, toShortOrdinal) | toWords), (toTimeText | toTimeText') #-}+ -- | Convert the given hours and minutes to text that describes the time.+ toTimeText' ::+ -- | The conversion algorithm to transform numbers into words.+ a ->+ -- | The number of hours, between 0 and 23 (both inclusive)+ Int ->+ -- | The number of minutes, beween 0 and 59 (both inclusive)+ Int ->+ -- | The time as /text/.+ Text+ toTimeText' gen h m = toTimeText gen (TimeOfDay h m 0) + {-# MINIMAL ((toCardinal, toOrdinal, toShortOrdinal) | toWords), (toTimeText | toTimeText') #-}+ -- | Convert the current time in the given 'TimeZone' to the time in words with the given 'NumToWord' -- algorithm.-currentTimeText :: NumToWord a- => TimeZone -- ^ The given 'TimeZone'.- -> a -- ^ The 'NumToWord' algorithm that converts time to words.- -> IO Text -- ^ An 'IO' that will generate a 'Text' object that describes the current time in words.+currentTimeText ::+ NumToWord a =>+ -- | The given 'TimeZone'.+ TimeZone ->+ -- | The 'NumToWord' algorithm that converts time to words.+ a ->+ -- | An 'IO' that will generate a 'Text' object that describes the current time in words.+ IO Text currentTimeText tz alg = toTimeText alg . snd . utcToLocalTimeOfDay tz . timeToTimeOfDay . utctDayTime <$> getCurrentTime -- | Convert the current time to the time in words with the given 'NumToWord' -- algorithm as UTC time.-currentTimeText' :: NumToWord a- => a -- ^ The 'NumToWord' algorithm that converts time to words.- -> IO Text -- ^ An 'IO' that will generate a 'Text' object that describes the current time in words.+currentTimeText' ::+ NumToWord a =>+ -- | The 'NumToWord' algorithm that converts time to words.+ a ->+ -- | An 'IO' that will generate a 'Text' object that describes the current time in words.+ IO Text currentTimeText' alg = toTimeText alg . timeToTimeOfDay . utctDayTime <$> getCurrentTime -- | A type class used to split a value, based on the name of a number in a -- specific language. The value that is used to split, is often, depending on -- the language, the largest value smaller than the given number. class ValueSplit a where- -- | A function that takes an 'Integral' value, and based on the object- -- splits it with a value and the name of the number in a specific language.- valueSplit :: a -> FreeValueSplitter+ -- | A function that takes an 'Integral' value, and based on the object+ -- splits it with a value and the name of the number in a specific language.+ valueSplit :: a -> FreeValueSplitter
src/Text/Numerals/Internal.hs view
@@ -1,46 +1,62 @@-{-# LANGUAGE CPP, Safe #-}--module Text.Numerals.Internal (- _div10, _rem10, _divisableBy, _divisable100- , _pluralize, _pluralize'- , _showText- , _mergeWith, _mergeWithSpace, _mergeWithHyphen, _mergeWith', _replaceSuffix- , _hundred, _thousand, _million, _billion, _trillion- , _iLog, _iLogFloor- , _stripLastIf- , _showIntegral- , _showPositive- , _genText, _shrinkText- ) where+{-# LANGUAGE CPP #-}+{-# LANGUAGE Safe #-} -import Control.Applicative(liftA2)+module Text.Numerals.Internal+ ( _div10,+ _rem10,+ _divisableBy,+ _divisable100,+ _pluralize,+ _pluralize',+ _showText,+ _mergeWith,+ _mergeWithSpace,+ _mergeWithHyphen,+ _mergeWith',+ _replaceSuffix,+ _hundred,+ _thousand,+ _million,+ _billion,+ _trillion,+ _iLog,+ _iLogFloor,+ _stripLastIf,+ _showIntegral,+ _showPositive,+ _genText,+ _shrinkText,+ )+where -import Data.Char(intToDigit)+import Control.Applicative (liftA2)+import Data.Char (intToDigit) #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text, cons, dropEnd, inits, isSuffixOf, singleton, tails, pack)+import Data.Text (Text, cons, dropEnd, inits, isSuffixOf, pack, singleton, tails) import qualified Data.Text as T--import Test.QuickCheck(listOf)-import Test.QuickCheck.Arbitrary(Arbitrary(arbitrary))-import Test.QuickCheck.Gen(Gen)+import Test.QuickCheck (listOf)+import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary))+import Test.QuickCheck.Gen (Gen) _pluralize :: a -> a -> Int -> a _pluralize sing plur = go- where go 1 = sing- go (-1) = sing- go _ = plur+ where+ go 1 = sing+ go (-1) = sing+ go _ = plur _pluralize' :: a -> a -> Int -> a _pluralize' sing plur = go- where go 1 = sing- go _ = plur+ where+ go 1 = sing+ go _ = plur _stripLastIf :: Char -> Text -> Text _stripLastIf c t- | singleton c `isSuffixOf` t = T.init t- | otherwise = t+ | singleton c `isSuffixOf` t = T.init t+ | otherwise = t _mergeWith' :: Char -> Text -> Text -> Text _mergeWith' m = (. cons m) . (<>)@@ -89,35 +105,43 @@ _iLogFloor :: (Integral i, Integral j) => i -> i -> (i, j, i) _iLogFloor b m = go b- where go i | m < i = (m, 0, 1)- | q < i = (q, 2 * e, j)- | otherwise = (div q i, 2 * e + 1, j * i)- where ~(q, e, j) = go (i*i)+ where+ go i+ | m < i = (m, 0, 1)+ | q < i = (q, 2 * e, j)+ | otherwise = (div q i, 2 * e + 1, j * i)+ where+ ~(q, e, j) = go (i * i) _iLog :: (Integral i, Integral j) => i -> i -> Maybe j _iLog b m = snd <$> go b- where go i | m < i = Just (m, 0)- | Just (q, e) <- go (i*i) = go' i q e- | otherwise = Nothing- go' i q e | q < i = Just (q, 2 * e)- | md == 0 = Just (d, 2 * e + 1)- | otherwise = Nothing- where (d, md) = divMod q i+ where+ go i+ | m < i = Just (m, 0)+ | Just (q, e) <- go (i * i) = go' i q e+ | otherwise = Nothing+ go' i q e+ | q < i = Just (q, 2 * e)+ | md == 0 = Just (d, 2 * e + 1)+ | otherwise = Nothing+ where+ (d, md) = divMod q i _replaceSuffix :: Int -> Text -> Text -> Text _replaceSuffix n s = (<> s) . dropEnd n _showIntegral :: Integral i => i -> String -> String _showIntegral n s- | n < 0 = '-' : _showPositive (-(fromIntegral n :: Integer)) s- | otherwise = _showPositive n s+ | n < 0 = '-' : _showPositive (-(fromIntegral n :: Integer)) s+ | otherwise = _showPositive n s _showPositive :: Integral i => i -> String -> String _showPositive n s- | q == 0 = tl- | otherwise = _showPositive q tl- where (q, r) = quotRem n 10- tl = intToDigit (fromIntegral r) : s+ | q == 0 = tl+ | otherwise = _showPositive q tl+ where+ (q, r) = quotRem n 10+ tl = intToDigit (fromIntegral r) : s _genText :: Gen Text _genText = pack <$> listOf arbitrary
src/Text/Numerals/Languages.hs view
@@ -1,18 +1,20 @@-{-|-Module : Text.Numerals.Languages-Description : A module that re-exports the algorithms to convert numbers to words in the supported languages.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX--This module imports the /num to word/ algorithms and re-exports these algorithms. The module thus can be used to conveniently import algorithms for all supported languages.--}--module Text.Numerals.Languages (- dutch, english, french, german- ) where+-- |+-- Module : Text.Numerals.Languages+-- Description : A module that re-exports the algorithms to convert numbers to words in the supported languages.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- This module imports the /num to word/ algorithms and re-exports these algorithms. The module thus can be used to conveniently import algorithms for all supported languages.+module Text.Numerals.Languages+ ( dutch,+ english,+ french,+ german,+ )+where -import Text.Numerals.Languages.Dutch(dutch)-import Text.Numerals.Languages.English(english)-import Text.Numerals.Languages.French(french)-import Text.Numerals.Languages.German(german)+import Text.Numerals.Languages.Dutch (dutch)+import Text.Numerals.Languages.English (english)+import Text.Numerals.Languages.French (french)+import Text.Numerals.Languages.German (german)
src/Text/Numerals/Languages/Dutch.hs view
@@ -1,61 +1,76 @@-{-# LANGUAGE CPP, OverloadedLists, OverloadedStrings, TemplateHaskell #-}--{-|-Module : Text.Numerals.Languages.Dutch-Description : A module to convert numbers to words in the /Dutch/ language.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} -This module contains logic to convert numbers to words in the /Dutch/ language.--}+-- |+-- Module : Text.Numerals.Languages.Dutch+-- Description : A module to convert numbers to words in the /Dutch/ language.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- This module contains logic to convert numbers to words in the /Dutch/ language.+module Text.Numerals.Languages.Dutch+ ( -- * Num to word algorithm+ dutch, -module Text.Numerals.Languages.Dutch (- -- * Num to word algorithm- dutch -- * Convert a cardinal number to text- , toCardinal'+ toCardinal',+ -- * Convert to ordinal- , ordinize'+ ordinize',+ -- * Constant words- , negativeWord', zeroWord', oneWord'+ negativeWord',+ zeroWord',+ oneWord',+ -- * Names for numbers- , lowWords', midWords', highWords'+ lowWords',+ midWords',+ highWords',+ -- * Merge function- , merge'- ) where+ merge',+ )+where #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text, pack, snoc)-import Data.Vector(Vector)--import Text.Numerals.Algorithm(HighNumberAlgorithm(LongScale), NumeralsAlgorithm, numeralsAlgorithm)-import Text.Numerals.Algorithm.Template(ordinizeFromDict)-import Text.Numerals.Class(ClockSegment(OClock, Past, QuarterPast, ToHalf, Half, PastHalf, QuarterTo, To), DayPart(Night, Morning, Afternoon, Evening), DaySegment(dayPart, dayHour), ClockText, hourCorrection, toCardinal, valueSplit)-import Text.Numerals.Internal(_million, _mergeWithSpace, _pluralize', _showIntegral)+import Data.Text (Text, pack, snoc)+import Data.Vector (Vector)+import Text.Numerals.Algorithm (HighNumberAlgorithm (LongScale), NumeralsAlgorithm, numeralsAlgorithm)+import Text.Numerals.Algorithm.Template (ordinizeFromDict)+import Text.Numerals.Class (ClockSegment (Half, OClock, Past, PastHalf, QuarterPast, QuarterTo, To, ToHalf), ClockText, DayPart (Afternoon, Evening, Morning, Night), DaySegment (dayHour, dayPart), hourCorrection, toCardinal, valueSplit)+import Text.Numerals.Internal (_mergeWithSpace, _million, _pluralize', _showIntegral) -$(pure (ordinizeFromDict "_ordinize'" [- ("nul", "nuld")- , ("één", "eerst")- , ("twee", "tweed")- , ("drie", "derd")- , ("vier", "vierd")- , ("vijf", "vijfd")- , ("zes", "zesd")- , ("zeven", "zevend")- , ("acht", "achtst")- , ("negen", "negend")- , ("tien", "tiend")- , ("elf", "elfd")- , ("twaalf", "twaalfd")- , ("ig", "igst")- , ("erd", "erdst")- , ("end", "endst")- , ("joen", "joenst")- , ("rd", "rdst")- ] 'id))+$( pure+ ( ordinizeFromDict+ "_ordinize'"+ [ ("nul", "nuld"),+ ("één", "eerst"),+ ("twee", "tweed"),+ ("drie", "derd"),+ ("vier", "vierd"),+ ("vijf", "vijfd"),+ ("zes", "zesd"),+ ("zeven", "zevend"),+ ("acht", "achtst"),+ ("negen", "negend"),+ ("tien", "tiend"),+ ("elf", "elfd"),+ ("twaalf", "twaalfd"),+ ("ig", "igst"),+ ("erd", "erdst"),+ ("end", "endst"),+ ("joen", "joenst"),+ ("rd", "rdst")+ ]+ 'id+ )+ ) -- | A function that converts a number in words in /cardinal/ form to /ordinal/ -- form according to the /Dutch/ language rules.@@ -63,13 +78,18 @@ ordinize' = (`snoc` 'e') . _ordinize' -- | A 'NumeralsAlgorithm' to convert numbers to words in the /Dutch/ language.-dutch :: NumeralsAlgorithm -- ^ A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+dutch ::+ -- | A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+ NumeralsAlgorithm dutch = numeralsAlgorithm negativeWord' zeroWord' oneWord' lowWords' midWords' (valueSplit highWords') merge' ordinize' shortOrdinal' clockText' -- | Convert numers to their cardinal counterpart in /Dutch/.-toCardinal' :: Integral i- => i -- ^ The number to convert to text.- -> Text -- ^ The cardinal counterpart in /Dutch/.+toCardinal' ::+ Integral i =>+ -- | The number to convert to text.+ i ->+ -- | The cardinal counterpart in /Dutch/.+ Text toCardinal' = toCardinal dutch -- | The words used to mark a negative number in the /Dutch/ language.@@ -86,41 +106,41 @@ -- | A 'Vector' that contains the word used for the numbers /two/ to /twenty/ in the /Dutch/ language. lowWords' :: Vector Text-lowWords' = [- "twee"- , "drie"- , "vier"- , "vijf"- , "zes"- , "zeven"- , "acht"- , "negen"- , "tien"- , "elf"- , "twaalf"- , "dertien"- , "veertien"- , "vijftien"- , "zestien"- , "zeventien"- , "achttien"- , "negentien"- , "twintig"+lowWords' =+ [ "twee",+ "drie",+ "vier",+ "vijf",+ "zes",+ "zeven",+ "acht",+ "negen",+ "tien",+ "elf",+ "twaalf",+ "dertien",+ "veertien",+ "vijftien",+ "zestien",+ "zeventien",+ "achttien",+ "negentien",+ "twintig" ] -- | A list of 2-tuples that contains the names of values between /thirty/ and -- /thousand/ in the /Dutch/ language. midWords' :: [(Integer, Text)]-midWords' = [- (1000, "duizend")- , (100, "honderd")- , (90, "negentig")- , (80, "tachtig")- , (70, "zeventig")- , (60, "zestig")- , (50, "vijftig")- , (40, "veertig")- , (30, "dertig")+midWords' =+ [ (1000, "duizend"),+ (100, "honderd"),+ (90, "negentig"),+ (80, "tachtig"),+ (70, "zeventig"),+ (60, "zestig"),+ (50, "vijftig"),+ (40, "veertig"),+ (30, "dertig") ] _rightAnd :: Integral i => i -> Text -> Text@@ -129,27 +149,30 @@ _leftAnd :: Integral i => i -> Text -> Text _leftAnd 1 = const "eenen"-_leftAnd n | 2 <- n = addE- | 3 <- n = addE- | otherwise = (<> "en")- where addE = (<> "ën")+_leftAnd n+ | 2 <- n = addE+ | 3 <- n = addE+ | otherwise = (<> "en")+ where+ addE = (<> "ën") -- | A merge function that is used to combine the names of words together to -- larger words, according to the /Dutch/ grammar rules. merge' :: Integral i => i -> i -> Text -> Text -> Text merge' 1 r- | r < _million = const id- | otherwise = const (_merge' 1 r "een")+ | r < _million = const id+ | otherwise = const (_merge' 1 r "een") merge' l r = _merge' l r _merge' :: Integral i => i -> i -> Text -> Text -> Text _merge' l r- | r > l && r >= _million = _mergeWithSpace- | r > l = (<>)- | r < 10 && 10 < l && l < 100 = go- | l >= _million = _mergeWithSpace- | otherwise = (<>)- where go tl tr = _leftAnd r tr <> _rightAnd l tl+ | r > l && r >= _million = _mergeWithSpace+ | r > l = (<>)+ | r < 10 && 10 < l && l < 100 = go+ | l >= _million = _mergeWithSpace+ | otherwise = (<>)+ where+ go tl tr = _leftAnd r tr <> _rightAnd l tl -- | An algorithm to obtain the names of /large/ numbers (one million or larger) -- in /Dutch/. Dutch uses a /long scale/ with the @iljoen@ and @iljard@@@ -158,9 +181,12 @@ highWords' = LongScale "iljoen" "iljard" -- | A function to convert a number to its /short ordinal/ form in /Dutch/.-shortOrdinal' :: Integral i- => i -- ^ The number to convert to /short ordinal/ form.- -> Text -- ^ The equivalent 'Text' specifying the number in /short ordinal/ form.+shortOrdinal' ::+ Integral i =>+ -- | The number to convert to /short ordinal/ form.+ i ->+ -- | The equivalent 'Text' specifying the number in /short ordinal/ form.+ Text shortOrdinal' = pack . (`_showIntegral` "e") _dayPartText :: DayPart -> Text@@ -182,9 +208,9 @@ clockText' :: ClockText clockText' OClock ds _ _ = _dayComponent " uur " 0 ds clockText' (Past m) ds _ _ = toCardinal' m <> _mins m <> "na " <> _dayComponent' 0 ds-clockText' QuarterPast ds _ _ = "kwart na " <> _dayComponent' 0 ds+clockText' QuarterPast ds _ _ = "kwart na " <> _dayComponent' 0 ds clockText' (ToHalf m) ds _ _ = toCardinal' m <> _mins m <> "voor half " <> _dayComponent' 1 ds clockText' Half ds _ _ = "half " <> _dayComponent' 1 ds clockText' (PastHalf m) ds _ _ = toCardinal' m <> _mins m <> "na half " <> _dayComponent' 1 ds-clockText' QuarterTo ds _ _ = "kwart voor " <> _dayComponent' 1 ds+clockText' QuarterTo ds _ _ = "kwart voor " <> _dayComponent' 1 ds clockText' (To m) ds _ _ = toCardinal' m <> _mins m <> "voor " <> _dayComponent' 1 ds
src/Text/Numerals/Languages/English.hs view
@@ -1,77 +1,97 @@-{-# LANGUAGE CPP, OverloadedLists, OverloadedStrings, TemplateHaskell #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -Wno-orphans #-} -{-|-Module : Text.Numerals.Languages.English-Description : A module to convert numbers to words in the /English/ language.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX--This module contains logic to convert numbers to words in the /English/ language.--}+-- |+-- Module : Text.Numerals.Languages.English+-- Description : A module to convert numbers to words in the /English/ language.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- This module contains logic to convert numbers to words in the /English/ language.+module Text.Numerals.Languages.English+ ( -- * Num to word algorithm+ english, -module Text.Numerals.Languages.English (- -- * Num to word algorithm- english -- * Convert a cardinal number to text- , toCardinal'+ toCardinal',+ -- * Convert to ordinal- , ordinize'+ ordinize',+ -- * Constant words- , negativeWord', zeroWord', oneWord'+ negativeWord',+ zeroWord',+ oneWord',+ -- * Names for numbers- , lowWords', midWords', highWords'+ lowWords',+ midWords',+ highWords',+ -- * Merge function- , merge'- ) where+ merge',+ )+where -import Data.Default.Class(Default(def))+import Data.Default.Class (Default (def)) #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text, isSuffixOf, pack)+import Data.Text (Text, isSuffixOf, pack) import qualified Data.Text as T-import Data.Vector(Vector)--import Text.Numerals.Algorithm(HighNumberAlgorithm, NumeralsAlgorithm, numeralsAlgorithm)-import Text.Numerals.Algorithm.Template(ordinizeFromDict)-import Text.Numerals.Class(ClockSegment(OClock, Past, QuarterPast, ToHalf, Half, PastHalf, QuarterTo, To), DayPart(Night, Morning, Afternoon, Evening), DaySegment(dayPart, dayHour), ClockText, hourCorrection, valueSplit, toCardinal)-import Text.Numerals.Internal(_div10, _mergeWith, _mergeWithSpace, _mergeWithHyphen, _rem10, _showIntegral)+import Data.Vector (Vector)+import Text.Numerals.Algorithm (HighNumberAlgorithm, NumeralsAlgorithm, numeralsAlgorithm)+import Text.Numerals.Algorithm.Template (ordinizeFromDict)+import Text.Numerals.Class (ClockSegment (Half, OClock, Past, PastHalf, QuarterPast, QuarterTo, To, ToHalf), ClockText, DayPart (Afternoon, Evening, Morning, Night), DaySegment (dayHour, dayPart), hourCorrection, toCardinal, valueSplit)+import Text.Numerals.Internal (_div10, _mergeWith, _mergeWithHyphen, _mergeWithSpace, _rem10, _showIntegral) _ordinizepp :: Text -> Text _ordinizepp t- | "y" `isSuffixOf` t = T.init t <> "ieth"- | otherwise = t <> "th"+ | "y" `isSuffixOf` t = T.init t <> "ieth"+ | otherwise = t <> "th" -- | A function that converts a number in words in /cardinal/ form to /ordinal/ -- form according to the /English/ language rules.-$(pure (ordinizeFromDict "ordinize'" [- ("one", "first")- , ("two", "second")- , ("three", "third")- , ("four", "fourth")- , ("five", "fifth")- , ("six", "sixth")- , ("seven", "seventh")- , ("eight", "eighth")- , ("nine", "ninth")- , ("ten", "tenth")- , ("eleven", "eleventh")- , ("twelve", "twelfth")- ] '_ordinizepp))+$( pure+ ( ordinizeFromDict+ "ordinize'"+ [ ("one", "first"),+ ("two", "second"),+ ("three", "third"),+ ("four", "fourth"),+ ("five", "fifth"),+ ("six", "sixth"),+ ("seven", "seventh"),+ ("eight", "eighth"),+ ("nine", "ninth"),+ ("ten", "tenth"),+ ("eleven", "eleventh"),+ ("twelve", "twelfth")+ ]+ '_ordinizepp+ )+ ) -- | A 'NumeralsAlgorithm' to convert numbers to words in the /English/ language.-english :: NumeralsAlgorithm -- ^ A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+english ::+ -- | A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+ NumeralsAlgorithm english = numeralsAlgorithm negativeWord' zeroWord' oneWord' lowWords' midWords' (valueSplit highWords') merge' ordinize' shortOrdinal' clockText' instance Default NumeralsAlgorithm where def = english -- | Convert numers to their cardinal counterpart in /English/.-toCardinal' :: Integral i- => i -- ^ The number to convert to text.- -> Text -- ^ The cardinal counterpart in /English/.+toCardinal' ::+ Integral i =>+ -- | The number to convert to text.+ i ->+ -- | The cardinal counterpart in /English/.+ Text toCardinal' = toCardinal english -- | The words used to mark a negative number in the /English/ language.@@ -88,50 +108,51 @@ -- | A 'Vector' that contains the word used for the numbers /two/ to /twenty/ in the /English/ language. lowWords' :: Vector Text-lowWords' = [- "two"- , "three"- , "four"- , "five"- , "six"- , "seven"- , "eight"- , "nine"- , "ten"- , "eleven"- , "twelve"- , "thirteen"- , "fourteen"- , "fifteen"- , "sixteen"- , "seventeen"- , "eighteen"- , "nineteen"- , "twenty"+lowWords' =+ [ "two",+ "three",+ "four",+ "five",+ "six",+ "seven",+ "eight",+ "nine",+ "ten",+ "eleven",+ "twelve",+ "thirteen",+ "fourteen",+ "fifteen",+ "sixteen",+ "seventeen",+ "eighteen",+ "nineteen",+ "twenty" ] -- | A list of 2-tuples that contains the names of values between /thirty/ and -- /thousand/ in the /English/ language. midWords' :: [(Integer, Text)]-midWords' = [- (1000, "thousand")- , (100, "hundred")- , (90, "ninety")- , (80, "eighty")- , (70, "seventy")- , (60, "sixty")- , (50, "fifty")- , (40, "forty")- , (30, "thirty")+midWords' =+ [ (1000, "thousand"),+ (100, "hundred"),+ (90, "ninety"),+ (80, "eighty"),+ (70, "seventy"),+ (60, "sixty"),+ (50, "fifty"),+ (40, "forty"),+ (30, "thirty") ] -- | A merge function that is used to combine the names of words together to -- larger words, according to the /English/ grammar rules. merge' :: Integral i => i -> i -> Text -> Text -> Text merge' 1 r | r < 100 = const id-merge' l r | 100 > l && l > r = _mergeWithHyphen- | l >= 100 && 100 > r = _mergeWith " and "- | r > l = _mergeWithSpace+merge' l r+ | 100 > l && l > r = _mergeWithHyphen+ | l >= 100 && 100 > r = _mergeWith " and "+ | r > l = _mergeWithSpace merge' _ _ = _mergeWith ", " -- | An algorithm to obtain the names of /large/ numbers (one million or larger)@@ -140,17 +161,21 @@ highWords' = def -- | A function to convert a number to its /short ordinal/ form in /English/.-shortOrdinal' :: Integral i- => i -- ^ The number to convert to /short ordinal/ form.- -> Text -- ^ The equivalent 'Text' specifying the number in /short ordinal/ form.+shortOrdinal' ::+ Integral i =>+ -- | The number to convert to /short ordinal/ form.+ i ->+ -- | The equivalent 'Text' specifying the number in /short ordinal/ form.+ Text shortOrdinal' i = pack (_showIntegral i (_shortOrdinalSuffix i))- where _shortOrdinalSuffix n- | _rem10 (_div10 n) == 1 = "th"- | otherwise = go' (_rem10 n)- go' 1 = "st"- go' 2 = "nd"- go' 3 = "rd"- go' _ = "th"+ where+ _shortOrdinalSuffix n+ | _rem10 (_div10 n) == 1 = "th"+ | otherwise = go' (_rem10 n)+ go' 1 = "st"+ go' 2 = "nd"+ go' 3 = "rd"+ go' _ = "th" _dayPartText :: DayPart -> Text _dayPartText Night = "at night"@@ -168,9 +193,9 @@ clockText' :: ClockText clockText' OClock ds _ _ = _dayComponent " o'clock " 0 ds clockText' (Past m) ds _ _ = toCardinal' m <> " past " <> _dayComponent' 0 ds-clockText' QuarterPast ds _ _ = "quarter past " <> _dayComponent' 0 ds+clockText' QuarterPast ds _ _ = "quarter past " <> _dayComponent' 0 ds clockText' (ToHalf _) ds _ m = toCardinal' m <> " past " <> _dayComponent' 0 ds clockText' Half ds _ _ = "half past " <> _dayComponent' 0 ds clockText' (PastHalf _) ds _ m = toCardinal' (60 - m) <> " to " <> _dayComponent' 1 ds-clockText' QuarterTo ds _ _ = "quarter to " <> _dayComponent' 1 ds+clockText' QuarterTo ds _ _ = "quarter to " <> _dayComponent' 1 ds clockText' (To m) ds _ _ = toCardinal' m <> " to " <> _dayComponent' 1 ds
src/Text/Numerals/Languages/French.hs view
@@ -1,54 +1,74 @@-{-# LANGUAGE CPP, OverloadedLists, OverloadedStrings, TemplateHaskell #-}--{-|-Module : Text.Numerals.Languages.French-Description : A module to convert numbers to words in the /French/ language.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} -This module contains logic to convert numbers to words in the /French/ language.--}+-- |+-- Module : Text.Numerals.Languages.French+-- Description : A module to convert numbers to words in the /French/ language.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- This module contains logic to convert numbers to words in the /French/ language.+module Text.Numerals.Languages.French+ ( -- * Num to word algorithm+ french, -module Text.Numerals.Languages.French (- -- * Num to word algorithm- french -- * Convert a cardinal number to text- , toCardinal'+ toCardinal',+ -- * Convert to ordinal- , ordinize'+ ordinize',+ -- * Constant words- , negativeWord', zeroWord', oneWord'+ negativeWord',+ zeroWord',+ oneWord',+ -- * Names for numbers- , lowWords', midWords', highWords'+ lowWords',+ midWords',+ highWords',+ -- * Merge function- , merge'- ) where+ merge',+ )+where #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text, isSuffixOf, pack, snoc)-import Data.Vector(Vector)--import Text.Numerals.Algorithm(HighNumberAlgorithm(LongScale), NumeralsAlgorithm, numeralsAlgorithm)-import Text.Numerals.Algorithm.Template(ordinizeFromDict)-import Text.Numerals.Class(ClockSegment(OClock, QuarterPast, Half, QuarterTo), DayPart(Night, Morning, Afternoon, Evening), DaySegment(dayPart, dayHour), ClockText, FreeMergerFunction, valueSplit, toCardinal)-import Text.Numerals.Internal(_divisable100, _mergeWith, _mergeWithSpace, _mergeWithHyphen, _million, _pluralize', _showIntegral, _stripLastIf, _thousand)+import Data.Text (Text, isSuffixOf, pack, snoc)+import Data.Vector (Vector)+import Text.Numerals.Algorithm (HighNumberAlgorithm (LongScale), NumeralsAlgorithm, numeralsAlgorithm)+import Text.Numerals.Algorithm.Template (ordinizeFromDict)+import Text.Numerals.Class (ClockSegment (Half, OClock, QuarterPast, QuarterTo), ClockText, DayPart (Afternoon, Evening, Morning, Night), DaySegment (dayHour, dayPart), FreeMergerFunction, toCardinal, valueSplit)+import Text.Numerals.Internal (_divisable100, _mergeWith, _mergeWithHyphen, _mergeWithSpace, _million, _pluralize', _showIntegral, _stripLastIf, _thousand) -$(pure (ordinizeFromDict "_ordinize'" [- ("cinq", "cinqu")- , ("neuf", "neuv")- ] 'id))+$( pure+ ( ordinizeFromDict+ "_ordinize'"+ [ ("cinq", "cinqu"),+ ("neuf", "neuv")+ ]+ 'id+ )+ ) -- | A 'NumeralsAlgorithm' to convert numbers to words in the /French/ language.-french :: NumeralsAlgorithm -- ^ A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+french ::+ -- | A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+ NumeralsAlgorithm french = numeralsAlgorithm negativeWord' zeroWord' oneWord' lowWords' midWords' (valueSplit highWords') merge' ordinize' shortOrdinal' clockText' -- | Convert numers to their cardinal counterpart in /French/.-toCardinal' :: Integral i- => i -- ^ The number to convert to text.- -> Text -- ^ The cardinal counterpart in /French/.+toCardinal' ::+ Integral i =>+ -- | The number to convert to text.+ i ->+ -- | The cardinal counterpart in /French/.+ Text toCardinal' = toCardinal french -- | The words used to mark a negative number in the /French/ language.@@ -65,62 +85,64 @@ -- | A 'Vector' that contains the word used for the numbers /two/ to /twenty/ in the /French/ language. lowWords' :: Vector Text-lowWords' = [- "deux"- , "trois"- , "quatre"- , "cinq"- , "six"- , "sept"- , "huit"- , "neuf"- , "dix"- , "onze"- , "douze"- , "treize"- , "quatorze"- , "quinze"- , "seize"- , "dix-sept"- , "dix-huit"- , "dix-neuf"- , "vingt"+lowWords' =+ [ "deux",+ "trois",+ "quatre",+ "cinq",+ "six",+ "sept",+ "huit",+ "neuf",+ "dix",+ "onze",+ "douze",+ "treize",+ "quatorze",+ "quinze",+ "seize",+ "dix-sept",+ "dix-huit",+ "dix-neuf",+ "vingt" ] -- | A list of 2-tuples that contains the names of values between /thirty/ and -- /thousand/ in the /French/ language. midWords' :: [(Integer, Text)]-midWords' = [- (1000, "mille")- , (100, "cent")- , (80, "quatre-vingts")- , (60, "soixante")- , (50, "cinquante")- , (40, "quarante")- , (30, "trente")+midWords' =+ [ (1000, "mille"),+ (100, "cent"),+ (80, "quatre-vingts"),+ (60, "soixante"),+ (50, "cinquante"),+ (40, "quarante"),+ (30, "trente") ] -- | A merge function that is used to combine the names of words together to -- larger words, according to the /French/ grammar rules. merge' :: FreeMergerFunction-merge' 1 r | r < _million = const id- | otherwise = _merge' 1 r+merge' 1 r+ | r < _million = const id+ | otherwise = _merge' 1 r merge' l r = \ta tb -> _merge' l r (_firstWithoutS l r ta) (_secondWithS l r tb) _firstWithoutS :: Integral i => i -> i -> Text -> Text _firstWithoutS l r t- | (_divisable100 (l + 20) || (_divisable100 l && l < _thousand)) && r < _million = _stripLastIf 's' t- | otherwise = t+ | (_divisable100 (l + 20) || (_divisable100 l && l < _thousand)) && r < _million = _stripLastIf 's' t+ | otherwise = t _secondWithS :: Integral i => i -> i -> Text -> Text _secondWithS l r t- | l < _thousand && r /= _thousand && _divisable100 r && not ("s" `isSuffixOf` t) = snoc t 's'- | otherwise = t+ | l < _thousand && r /= _thousand && _divisable100 r && not ("s" `isSuffixOf` t) = snoc t 's'+ | otherwise = t _merge' :: Integral i => i -> i -> Text -> Text -> Text-_merge' l r | r >= l || l >= 100 = _mergeWithSpace- | r `mod` 10 == 1 && l /= 80 = _mergeWith " et "- | otherwise = _mergeWithHyphen+_merge' l r+ | r >= l || l >= 100 = _mergeWithSpace+ | r `mod` 10 == 1 && l /= 80 = _mergeWith " et "+ | otherwise = _mergeWithHyphen -- | A function that converts a number in words in /cardinal/ form to /ordinal/ -- form according to the /French/ language rules.@@ -132,12 +154,15 @@ -- in /French/. French uses a /long scale/ with the @illion@ and @illiard@ -- suffixes. highWords' :: HighNumberAlgorithm-highWords' = LongScale "illion" "illiard"+highWords' = LongScale "illion" "illiard" -- | A function to convert a number to its /short ordinal/ form in /French/.-shortOrdinal' :: Integral i- => i -- ^ The number to convert to /short ordinal/ form.- -> Text -- ^ The equivalent 'Text' specifying the number in /short ordinal/ form.+shortOrdinal' ::+ Integral i =>+ -- | The number to convert to /short ordinal/ form.+ i ->+ -- | The equivalent 'Text' specifying the number in /short ordinal/ form.+ Text shortOrdinal' = pack . (`_showIntegral` "e") _dayPartText :: DayPart -> Text@@ -148,7 +173,8 @@ _heures :: Text -> Int -> DaySegment -> Text _heures sep dh ds = toCardinal' h <> _pluralize' "e heure" " heures" h <> sep <> _dayPartText (dayPart ds)- where h = dayHour ds + dh+ where+ h = dayHour ds + dh -- | Converting the time to a text that describes that time in /French/. clockText' :: ClockText@@ -159,5 +185,5 @@ clockText' Half ds _ _ = _heures " et demie" 0 ds clockText' QuarterTo ds _ _ = _heures " moins le quart" 1 ds clockText' _ ds _ m- | m <= 30 = _heures (" " <> toCardinal' m) 0 ds- | otherwise = _heures (" moins " <> toCardinal' (60 - m)) 1 ds+ | m <= 30 = _heures (" " <> toCardinal' m) 0 ds+ | otherwise = _heures (" moins " <> toCardinal' (60 - m)) 1 ds
src/Text/Numerals/Languages/German.hs view
@@ -1,65 +1,86 @@-{-# LANGUAGE CPP, OverloadedLists, OverloadedStrings, QuasiQuotes, TemplateHaskell #-}--{-|-Module : Text.Numerals.Languages.German-Description : A module to convert numbers to words in the /German/ language.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-} -This module contains logic to convert numbers to words in the /German/ language.--}+-- |+-- Module : Text.Numerals.Languages.German+-- Description : A module to convert numbers to words in the /German/ language.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- This module contains logic to convert numbers to words in the /German/ language.+module Text.Numerals.Languages.German+ ( -- * Num to word algorithm+ german, -module Text.Numerals.Languages.German (- -- * Num to word algorithm- german -- * Convert a cardinal number to text- , toCardinal'+ toCardinal',+ -- * Convert to ordinal- , ordinize'+ ordinize',+ -- * Constant words- , negativeWord', zeroWord', oneWord'+ negativeWord',+ zeroWord',+ oneWord',+ -- * Names for numbers- , lowWords', midWords', highWords'+ lowWords',+ midWords',+ highWords',+ -- * Merge function- , merge'- ) where+ merge',+ )+where -import Data.Bool(bool)+import Data.Bool (bool) #if __GLASGOW_HASKELL__ < 803 import Data.Semigroup((<>)) #endif-import Data.Text(Text, isSuffixOf, pack, toLower, toTitle)-import Data.Vector(Vector)--import Text.Numerals.Algorithm(HighNumberAlgorithm(LongScale), NumeralsAlgorithm, numeralsAlgorithm, valueSplit')-import Text.Numerals.Algorithm.Template(ordinizeFromDict)-import Text.Numerals.Class(ClockText, FreeMergerFunction, toCardinal)-import Text.Numerals.Internal(_mergeWith, _mergeWithSpace, _million, _showIntegral)-import Text.RE.TDFA.Text(RE, SearchReplace, (*=~/), ed)+import Data.Text (Text, isSuffixOf, pack, toLower, toTitle)+import Data.Vector (Vector)+import Text.Numerals.Algorithm (HighNumberAlgorithm (LongScale), NumeralsAlgorithm, numeralsAlgorithm, valueSplit')+import Text.Numerals.Algorithm.Template (ordinizeFromDict)+import Text.Numerals.Class (ClockText, FreeMergerFunction, toCardinal)+import Text.Numerals.Internal (_mergeWith, _mergeWithSpace, _million, _showIntegral)+import Text.RE.TDFA.Text (RE, SearchReplace, ed, (*=~/)) -$(pure (ordinizeFromDict "_ordinize'" [- ("eins", "ers")- , ("drei", "drit")- , ("acht", "ach")- , ("sieben", "sieb")- , ("ig", "igs")- , ("ert", "erts")- , ("end", "ends")- , ("ion", "ions")- , ("nen", "ns")- , ("rde", "rds")- , ("rden", "rds")- ] 'id))+$( pure+ ( ordinizeFromDict+ "_ordinize'"+ [ ("eins", "ers"),+ ("drei", "drit"),+ ("acht", "ach"),+ ("sieben", "sieb"),+ ("ig", "igs"),+ ("ert", "erts"),+ ("end", "ends"),+ ("ion", "ions"),+ ("nen", "ns"),+ ("rde", "rds"),+ ("rden", "rds")+ ]+ 'id+ )+ ) -- | A 'NumeralsAlgorithm' to convert numbers to words in the /German/ language.-german :: NumeralsAlgorithm -- ^ A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+german ::+ -- | A 'NumeralsAlgorithm' that can be used to convert numbers to different formats.+ NumeralsAlgorithm german = numeralsAlgorithm negativeWord' zeroWord' oneWord' lowWords' midWords' (valueSplit' toTitle highWords') merge' ordinize' shortOrdinal' clockText' -- | Convert numers to their cardinal counterpart in /German/.-toCardinal' :: Integral i- => i -- ^ The number to convert to text.- -> Text -- ^ The cardinal counterpart in /German/.+toCardinal' ::+ Integral i =>+ -- | The number to convert to text.+ i ->+ -- | The cardinal counterpart in /German/.+ Text toCardinal' = toCardinal german -- | The words used to mark a negative number in the /German/ language.@@ -76,41 +97,41 @@ -- | A 'Vector' that contains the word used for the numbers /two/ to /twenty/ in the /German/ language. lowWords' :: Vector Text-lowWords' = [- "zwei"- , "drei"- , "vier"- , "fünf"- , "sechs"- , "sieben"- , "acht"- , "neun"- , "zehn"- , "elf"- , "zwölf"- , "dreizehn"- , "vierzehn"- , "fünfzehn"- , "sechzehn"- , "siebzehn"- , "achtzehn"- , "neunzehn"- , "zwanzig"+lowWords' =+ [ "zwei",+ "drei",+ "vier",+ "fünf",+ "sechs",+ "sieben",+ "acht",+ "neun",+ "zehn",+ "elf",+ "zwölf",+ "dreizehn",+ "vierzehn",+ "fünfzehn",+ "sechzehn",+ "siebzehn",+ "achtzehn",+ "neunzehn",+ "zwanzig" ] -- | A list of 2-tuples that contains the names of values between /thirty/ and -- /thousand/ in the /German/ language. midWords' :: [(Integer, Text)]-midWords' = [- (1000, "tausend")- , (100, "hundert")- , (90, "neunzig")- , (80, "achtzig")- , (70, "siebzig")- , (60, "sechzig")- , (50, "fünfzig")- , (40, "vierzig")- , (30, "dreißig")+midWords' =+ [ (1000, "tausend"),+ (100, "hundert"),+ (90, "neunzig"),+ (80, "achtzig"),+ (70, "siebzig"),+ (60, "sechzig"),+ (50, "fünfzig"),+ (40, "vierzig"),+ (30, "dreißig") ] -- | A merge function that is used to combine the names of words together to@@ -124,18 +145,18 @@ _pluralize :: Text -> Text _pluralize t- | "e" `isSuffixOf` t = t <> "n"- | otherwise = t <> "en"+ | "e" `isSuffixOf` t = t <> "n"+ | otherwise = t <> "en" _merge' :: FreeMergerFunction _merge' l r- | r > l && r >= _million = (. bool id _pluralize (l > 1)) . _mergeWithSpace- | r > l = (<>)-_merge' l 1 | 10 < l && l < 100 = const . ("einund" <> )+ | r > l && r >= _million = (. bool id _pluralize (l > 1)) . _mergeWithSpace+ | r > l = (<>)+_merge' l 1 | 10 < l && l < 100 = const . ("einund" <>) _merge' l r- | r < 10 && 10 < l && l < 100 = flip (_mergeWith "und")- | l >= _million = _mergeWithSpace- | otherwise = (<>)+ | r < 10 && 10 < l && l < 100 = flip (_mergeWith "und")+ | l >= _million = _mergeWithSpace+ | otherwise = (<>) _ordinalSuffixRe :: SearchReplace RE Text _ordinalSuffixRe = [ed|(eine)? ([a-z]+(illion|illiard)ste)$///${2}|]@@ -144,20 +165,24 @@ -- form according to the /German/ language rules. ordinize' :: Text -> Text ordinize' = postprocess . (<> "te") . _ordinize' . toLower- where postprocess "eintausendste" = "tausendste"- postprocess "einhundertste" = "hundertste"- postprocess t = t *=~/ _ordinalSuffixRe+ where+ postprocess "eintausendste" = "tausendste"+ postprocess "einhundertste" = "hundertste"+ postprocess t = t *=~/ _ordinalSuffixRe -- | An algorithm to obtain the names of /large/ numbers (one million or larger) -- in /German/. German uses a /long scale/ with the @illion@ and @illiard@ -- suffixes. highWords' :: HighNumberAlgorithm-highWords' = LongScale "illion" "illiarde"+highWords' = LongScale "illion" "illiarde" -- | A function to convert a number to its /short ordinal/ form in /German/.-shortOrdinal' :: Integral i- => i -- ^ The number to convert to /short ordinal/ form.- -> Text -- ^ The equivalent 'Text' specifying the number in /short ordinal/ form.+shortOrdinal' ::+ Integral i =>+ -- | The number to convert to /short ordinal/ form.+ i ->+ -- | The equivalent 'Text' specifying the number in /short ordinal/ form.+ Text shortOrdinal' = pack . (`_showIntegral` ".") -- | Converting the time to a text that describes that time in /German/.
src/Text/Numerals/Prefix.hs view
@@ -1,75 +1,83 @@ {-# LANGUAGE OverloadedStrings #-} -{-|-Module : Text.Numerals.Prefix-Description : A module used to define /numeric prefixes/ for /long/ and /short scales/.-Maintainer : hapytexeu+gh@gmail.com-Stability : experimental-Portability : POSIX--A module that defines /Latin/ prefixes. These prefixes are used to construct names for the /long/ and /short scales/.-So the /m/, /b/, /tr/ in /million/, /billion/, /trillion/.--}--module Text.Numerals.Prefix (- -- * Latin prefixes- latinPrefixes, latinPrefixes', latinPrefix- ) where+-- |+-- Module : Text.Numerals.Prefix+-- Description : A module used to define /numeric prefixes/ for /long/ and /short scales/.+-- Maintainer : hapytexeu+gh@gmail.com+-- Stability : experimental+-- Portability : POSIX+--+-- A module that defines /Latin/ prefixes. These prefixes are used to construct names for the /long/ and /short scales/.+-- So the /m/, /b/, /tr/ in /million/, /billion/, /trillion/.+module Text.Numerals.Prefix+ ( -- * Latin prefixes+ latinPrefixes,+ latinPrefixes',+ latinPrefix,+ )+where -import Data.Text(Text)-import Data.Vector(Vector, (!?), fromList)+import Data.Text (Text)+import Data.Vector (Vector, fromList, (!?)) -- | A list of /Latin/ prefixes, used for the /long/ and /short scale/.-latinPrefixes' :: [Text] -- ^ A list of 'Text' objects. This makes explicit recursion more convenient.-latinPrefixes' = [- "m"- , "b"- , "tr"- , "quadr"- , "quint"- , "sext"- , "sept"- , "oct"- , "non"- , "dec"- , "undec"- , "duodec"- , "tredec"- , "quattuordec"- , "quindec"- , "sedec"- , "septendec"- , "octodec"- , "novendec"- , "vigint"- , "unvigint"- , "duovigint"- , "tresvigint"- , "quattuorvigint"- , "quinvigint"- , "sesvigint"- , "septemvigint"- , "octovigint"- , "novemvigint"- , "trigint"- , "untrigint"- , "duotrigint"- , "trestrigint"- , "quattuortrigint"- , "quintrigint"- , "sestrigint"- , "septentrigint"- , "octotrigint"- , "noventrigint"- , "quadragint"+latinPrefixes' ::+ -- | A list of 'Text' objects. This makes explicit recursion more convenient.+ [Text]+latinPrefixes' =+ [ "m",+ "b",+ "tr",+ "quadr",+ "quint",+ "sext",+ "sept",+ "oct",+ "non",+ "dec",+ "undec",+ "duodec",+ "tredec",+ "quattuordec",+ "quindec",+ "sedec",+ "septendec",+ "octodec",+ "novendec",+ "vigint",+ "unvigint",+ "duovigint",+ "tresvigint",+ "quattuorvigint",+ "quinvigint",+ "sesvigint",+ "septemvigint",+ "octovigint",+ "novemvigint",+ "trigint",+ "untrigint",+ "duotrigint",+ "trestrigint",+ "quattuortrigint",+ "quintrigint",+ "sestrigint",+ "septentrigint",+ "octotrigint",+ "noventrigint",+ "quadragint" ] -- | The /Latin/ prefixes in a 'Vector' for /O(1)/ lookup.-latinPrefixes :: Vector Text -- ^ A 'Vector' of 'Text' objects to allow fast lookup.+latinPrefixes ::+ -- | A 'Vector' of 'Text' objects to allow fast lookup.+ Vector Text latinPrefixes = fromList latinPrefixes' -- | Lookup the given /Latin/ prefix for the given value.-latinPrefix :: Integral i- => i -- ^ The value to map on a Latin prefix.- -> Maybe Text -- ^ The corresponding Latin prefix, given this exists.+latinPrefix ::+ Integral i =>+ -- | The value to map on a Latin prefix.+ i ->+ -- | The corresponding Latin prefix, given this exists.+ Maybe Text latinPrefix n = latinPrefixes !? (fromIntegral n - 1)