{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -- | Another way to desugar overloaded string literals. See 'FromSymbol'. module Overloaded.Symbols ( FromSymbol (..), ) where import Data.Proxy (Proxy (..)) import Data.String (fromString) import Data.Symbol.Ascii (ToList) import Data.Time.Calendar (Day, fromGregorian) import GHC.Exts (Constraint) import GHC.TypeLits (ErrorMessage (..), KnownSymbol, Symbol, TypeError, symbolVal) import GHC.TypeNats import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import qualified Data.Text as T import qualified Data.Text.Lazy as TL -- | Another way to desugar overloaded string literals using this class. -- -- A string literal @"example"@ is desugared to -- -- @ -- 'fromSymbol' \@"example" -- @ -- -- Enabled with: -- -- @ -- {-\# OPTIONS -fplugin=Overloaded -fplugin-opt=Overloaded:Symbols #-} -- @ -- class FromSymbol (s :: Symbol) a where fromSymbol :: a ------------------------------------------------------------------------------- -- base ------------------------------------------------------------------------------- instance (KnownSymbol s, a ~ Char) => FromSymbol s [a] where fromSymbol = symbolVal (Proxy :: Proxy s) ------------------------------------------------------------------------------- -- text ------------------------------------------------------------------------------- instance KnownSymbol s => FromSymbol s T.Text where fromSymbol = fromString (symbolVal (Proxy :: Proxy s)) instance KnownSymbol s => FromSymbol s TL.Text where fromSymbol = fromString (symbolVal (Proxy :: Proxy s)) ------------------------------------------------------------------------------- -- bytestring ------------------------------------------------------------------------------- type family SeqList (xs :: [Symbol]) :: Constraint where SeqList '[] = () SeqList (x ': xs) = SeqList xs SeqList xs = TypeError ('Text "Cannot reduce list " ':$$: 'ShowType xs) instance (KnownSymbol s, SeqList (ToList s)) => FromSymbol s BS.ByteString where fromSymbol = fromString (symbolVal (Proxy :: Proxy s)) instance (KnownSymbol s, SeqList (ToList s)) => FromSymbol s BSL.ByteString where fromSymbol = fromString (symbolVal (Proxy :: Proxy s)) ------------------------------------------------------------------------------- -- time ------------------------------------------------------------------------------- instance (KnownNat y, KnownNat m, KnownNat d, ParseDay s ~ '(y, m, d)) => FromSymbol s Day where fromSymbol = fromGregorian (integralVal @y) (integralVal @m) (integralVal @d) integralVal :: forall n m. (KnownNat n, Num m) => m integralVal = fromIntegral (natVal (Proxy :: Proxy n)) type family ParseDay (sym :: Symbol) :: (Nat, Nat, Nat) where ParseDay sym = ParseDay1 sym (ToList sym) type a ** b = a GHC.TypeNats.* b infixl 7 ** type family ParseDay1 (sym :: Symbol) (cs :: [Symbol]) :: (Nat, Nat, Nat) where ParseDay1 sym (y1 ': y2 ': y3 ': y4 ': "-" ': m1 ': m2 ': "-" ': d1 ': d2 :' []) = ParseDay2 sym (1000 ** TD y1 + 100 ** TD y2 + 10 ** TD y3 + TD y4) (10 ** TD m1 + TD m2) (10 ** TD d1 + TD d2) ParseDay1 sym cs = TypeError ('ShowType sym ':<>: 'Text " doesn't look like a date (yyyy-mm-dd)") -- To Digit type family TD (c :: Symbol) :: Nat where TD "0" = 0 TD "1" = 1 TD "2" = 2 TD "3" = 3 TD "4" = 4 TD "5" = 5 TD "6" = 6 TD "7" = 7 TD "8" = 8 TD "9" = 9 TD c = TypeError ('ShowType c ':<>: 'Text " is not a digit") type family ParseDay2 (sym :: Symbol) (y :: Nat) (m :: Nat) (d :: Nat) :: (Nat, Nat, Nat) where ParseDay2 sym y m 0 = TypeError ('Text "Zero-day in " ':<>: 'ShowType sym) ParseDay2 sym y 0 d = TypeError ('Text "Zero-month in " ':<>: 'ShowType sym) ParseDay2 sym y m d = ParseDay3 sym y m d (d <=? DaysIn y m) type family ParseDay3 (sym :: Symbol) (y :: Nat) (m :: Nat) (d :: Nat) (check :: Bool) :: (Nat, Nat, Nat) where ParseDay3 sym y m d 'True = '(y, m , d) ParseDay3 sym y m d 'False = TypeError ('Text "There are only " ':<>: 'ShowType (DaysIn y m) ':<>: 'Text " days in year-of-month of " ':<>: 'ShowType sym) type family DaysIn (y :: Nat) (m :: Nat) where DaysIn y 1 = 31 DaysIn y 2 = Leap (Mod y 4) (Mod y 100) (Mod y 400) DaysIn y 3 = 31 DaysIn y 4 = 30 DaysIn y 5 = 31 DaysIn y 6 = 30 DaysIn y 7 = 31 DaysIn y 8 = 31 DaysIn y 9 = 30 DaysIn y 10 = 31 DaysIn y 11 = 30 DaysIn y 12 = 31 DaysIn y m = TypeError ('Text "Overflowed month " ':<>: 'ShowType m) type family Leap (a :: Nat) (b :: Nat) (c :: Nat) :: Nat where Leap a b 0 = 29 Leap a 0 c = 28 Leap 0 b c = 29 Leap a b c = 28