2016-02-06 26 views
9

Kontekst: Zbliżam się do Haskella z punktu widzenia konwersji błędów środowiska wykonawczego na błędy podczas kompilacji. Moja hipoteza jest taka, że ​​jest to możliwe, jeśli można skodyfikować logikę biznesową w samych typach programu.Uszkodzona obecność/brak uwierzytelnienia na poziomie typu

Piszę bota Telegram, który powinien być dostępny dla użytkowników w mojej firmie. Aby osiągnąć to "ograniczenie", za każdym razem, gdy ktoś zacznie czatować z botem, wyszuka w tabeli chat_id i sprawdzi, czy istnieje ważny oauth_token. W przeciwnym razie użytkownik otrzyma najpierw link do wypełnienia Google OAuth (poczta e-mail naszej firmy jest hostowana w Google Apps dla Firm).

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| 
VLUser 
    email String 
    chatId Integer 
    tgramUserId Integer 
    tgramFirstName String 
    tgramLastName String Maybe 
    tgramUsername String Maybe 
    oauthToken String Maybe 
    deriving Show 
|] 

Użytkownicy posiadający ważną oauth_token będzie w stanie dać bot telegram kilka poleceń, które nieuwierzytelnionych użytkowników nie powinny być w stanie dać.

Teraz próbuję skodyfikować tę logikę na samym poziomie typu. W moim kodzie Haskella będą dostępne pewne funkcje, które będą w stanie przyjąć, jako argumenty, zarówno uwierzytelnione, jak i niezaufane; podczas gdy niektóre funkcje powinny akceptować tylko uwierzytelnionych użytkowników.

Jeśli przechodzę obok obiektów użytkowników tego samego typu, tj. VLUser wszędzie, to będę musiał uważać, aby sprawdzić, czy w każdej funkcji znajduje się oauthToken. Czy istnieje sposób, aby utworzyć dwa rodzaje użytkowników - VLUser i VLUserAuthenticated gdzie:

  1. Zarówno mapa do tej samej tabeli podstawowej
  2. VLUserAuthenticated można instancji tylko wtedy, gdy ma oauthToken
+0

Wydaje podobny do problemu mutex. Chcesz określić na poziomie typu, czy funkcja wymaga blokady, czy nie. Możesz zobaczyć jeden z możliwych sposobów radzenia sobie z tym [tutaj] (http://dev.stephendiehl.com/hask/#indexed-monads) spójrz na typ 'IState'. Wymienić 'Locked' i' Unlocked' z 'Authenticated' i' UnAuthenticated' i zapewniają użytkownikowi 'funkcję login' że kontrole na' oauth' token, który jest jedynym sposobem uzyskania 'wartość Authenticated'. – Bakuriu

Odpowiedz

8

Phantom types to the rescue! jest Bryan O'Sullivan przykład implementacji odczytu i odczytu a dostęp do odczytu/zapisu na poziomie typu przy użyciu phantom types.

Podobnie, dla przypadków użycia:

data Unknown  -- unknown users 
data Authenticated -- verified users 

newtype User a i = Id i deriving Show 

Jest ważne że konstruktor dane Id jest nie narażone na użytkownika, ale moduł udostępnia funkcje do inicjacji i uwierzytelniania użytkowników:

-- initializes an un-authenticated user 
newUser :: i -> User Unknown i 
newUser = Id 

-- authenticates a user 
authUser :: (User a i) -> User Authenticated i 
authUser (Id i) = Id i -- dummy implementation 

, możesz kontrolować dostęp na poziomie typu bez powielania kodu, bez kontroli run-time i bez okresie czasu kosztował:

-- open to all users 
getId :: User a i -> i 
getId (Id i) = i 

-- only authenticated users can pass through 
getId' :: User Authenticated i -> i 
getId' (Id i) = i 

Na przykład, jeśli

\> let jim = newUser "jim" 
\> let joe = authUser $ newUser "joe" 

joe jest uwierzytelniony użytkownik i mogą być przekazywane do obu funkcji:

\> getId joe 
"joe" 
\> getId' joe 
"joe" 

podczas gdy dostaniesz błąd kompilacji jeśli zadzwonisz getId' z jim:

\> getId jim 
"jim" 
\> getId' jim -- compile-time error! not run-time error! 

<interactive>:28:8: 
    Couldn't match type ‘Unknown’ with ‘Authenticated’ 
    Expected type: User Authenticated [Char] 
     Actual type: User Unknown [Char] 
    In the first argument of ‘getId'’, namely ‘jim’ 
    In the expression: getId' jim 
+0

Chociaż wydaje się to rozwiązaniem praktycznym, czy jest to jedyne podejście? To po prostu __wyważa____ jak wiele płyt kotłowych na coś, co można łatwo rozwiązać przez dziedziczenie w innych językach. –

+0

Jeśli ta skala jest konieczna, czy istnieje makro, które może przekształcić je w jednolinijkę? –

+0

@SaurabhNanda nie zgadzam się! w rzeczywistości jest odwrotnie: brak kodu do sprawdzania czasu pracy i bez powielonego kodu do implementacji tej samej logiki dla różnych typów użytkowników –