2017-01-02 39 views
11

Chciałbym móc tworzyć instancje IsString przy użyciu rozszerzenia GHC OverloadedStrings tak, że moja instancja odrzuca niektóre literały jako nieważne i takie, że odrzucenie ma miejsce podczas kompilacji, więc błędy programowania nie wchodzą w kod I dać moim użytkownikom.Jak mogę sprawdzić poprawność literałów podczas kompilacji w instancjach IsString?

Mam kilka przypadków użycia, w których mam typ Name, który dopuszcza tylko niektóre ciągi. na przykład

module Name (Name(getName), makeName) where 

import Data.Text (Text) 
import qualified Data.Text as Text 

-- | A guaranteed non-empty name. 
newtype Name = Name { getName :: Text } deriving (Eq, Show, Ord) 

makeName :: Text -> Maybe Name 
makeName name 
    | Text.null name = Nothing 
    | otherwise = Just name 

W prawdziwym przypadku użyłbym sprawdzenia poprawnych znaków, nie zaczynając od cyfry, tego rodzaju rzeczy.

Chodzi o to, że nie eksportujemy konstruktora Name, co oznacza, że ​​każdy, kto używa wartości Name, może ufać, że ma określone właściwości (w tym przypadku niepuste).

Moim problemem jest to, że chciałbym używać dosłownych nazw w wielu miejscach. na przykład

programName :: Name 
programName = fromJust $ makeName "the-great-and-powerful-turtle" 

Ponieważ robię to dużo, ja zdefiniował unsafeMakeName pomocnika, który ma prawie to samo:

unsafeMakeName :: Text -> Name 
unsafeMakeName name = fromMaybe (error $ "Invalid name: " <> Text.unpack name) (makeName name) 

Problem z tego podejścia jest to, że mimo, że przyczyną błędu jest błędem programistycznym, nie dowiedziałem się o tym do czasu uruchomienia.

Co chcę zrobić, to napisać instancję IsString dla Name, która wykonuje to sprawdzenie, np.

instance IsString Name where 
    fromString = unsafeMakeName . Text.pack 

... ale aby uzyskać błąd dotyczący nieprawidłowych nazw w literałach podczas kompilacji.

Gdy próbuję tego, wydaje mi się, że błędy występują tylko w czasie wykonywania, gdy używana jest wartość literału. Jest to mniej niż idealne, ponieważ jest to błąd w moim rzeczywistym kodzie.

Czy mogę to zrobić? Czy jest to coś, co można naprawić w GHC? Zauważ, że już tam jest filed a bug.

+2

Myślę, że jedynym sposobem, aby to zrobić, byłoby użycie szablonu Haskella do sprawdzania poprawności poprzez łączenie w czasie kompilacji. –

+0

Dla czegoś ciężkiego, możesz spróbować użyć sprawdzania pełnej płynności-haskell.Przynajmniej byłoby fajnie. –

+0

Byłoby świetnie na przykład dla 'printf' i jego łańcucha formatującego. – chi

Odpowiedz

16

Naprawdę brzmi to, co chcesz to quasiquoter, a nie OverloadedStrings. Logika walidacji przechodzi następnie do monady Q, która działa w czasie kompilacji. Dla Państwa prostym przykładzie powyżej:

{-# LANGUAGE QuasiQuotes, TemplateHaskell #-} 

module Name (Name(getName), name) where 

import Data.Text (Text) 
import qualified Data.Text as Text 

import Language.Haskell.TH.Quote hiding (Name) 
import Language.Haskell.TH hiding (Name) 

-- | A guaranteed non-empty name. 
newtype Name = Name { getName :: Text } deriving (Eq, Show, Ord) 

makeName :: String -> Q Exp 
makeName name 
    | null name = fail "Invalid name" 
    | otherwise = [| Name (Text.pack name) |] 

name :: QuasiQuoter 
name = QuasiQuoter { quoteExp = makeName } 

Następnie w innym module, następujące kompiluje:

{-# LANGUAGE QuasiQuotes #-} 
import Name 

main = print [name|valid-name|] 

Ale po nie, i wypluwa wiadomość Invalid name błędzie.

{-# LANGUAGE QuasiQuotes #-} 
import Name 

main = print [name||] 

pamiętać, że można dostać quasiquoters które działają wzorców też (tak coś myFunc [name|valid-name|] = True może być ważna definicja funkcji)!