2012-05-11 15 views
7

Diciamo che ho il seguente codiceHaskell: sicurezza Tipo con logicamente diversi valori booleani

type IsTall = Bool 
type IsAlive = Bool 

is_short_alive_person is_tall is_alive = (not is_tall) && is_alive 

Say, in seguito, ho il seguente

a :: IsAlive 
a = False 

b :: IsTall 
b = True 

e chiamare il seguente , ottenendo i due argomenti in modo errato:

is_short_alive_person a b 

Questo compilazione riesce con successo, e in fase di esecuzione morti alti sono invece trovati invece di persone corte e vive.

Vorrei che l'esempio precedente non fosse compilato.

Il mio primo tentativo è stato:

newtype IsAlive = IsAlive Bool 
newtype IsTall = IsTall Bool 

Ma poi non può fare qualcosa di simile.

switch_height :: IsTall -> IsTall 
switch_height h = not h 

Come not non è definita su IsTall s, solo Bool s.

È possibile estrarre esplicitamente il Bool s tutto il tempo, ma questo in gran parte vanifica lo scopo.

sostanza, voglio IsTall s di interagire con altri IsTall s, proprio come se fossero Bool s, tranne che non potranno interagire con Bool s e IsAlive s senza un cast esplicito.

Qual è il modo migliore per raggiungere questo obiettivo.


p.s. Credo di aver raggiunto questo obiettivo con i numeri facendo in GHC:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

newtype UserID = UserID Int deriving (Eq, Ord, Num) 
newtype GroupID = GroupID Int deriving (Eq, Ord, Num) 

(cioè UserID e di GroupID di non dovrebbero interagire)

ma io non riesco a farlo con Bool s (derivante Bool non funziona). Non sono nemmeno sicuro che quanto sopra sia l'approccio migliore comunque.

+1

I tipi 'IsTall' e' IsAlive' desiderati sono una pessima idea. È una falsa generalizzazione dell'idea di solito decente di utilizzare tipi disgiunti per garantire la sicurezza del tipo. Confronta questo con il tuo 'UserID' e' GroupID'; in questo caso ha senso avere tipi separati perché non ha senso passare un ID utente dove è necessario un 'GroupID', o aggiungerne uno all'altro (anche se probabilmente nessuno dei due dovrebbe implementare' Num'). Tuttavia, ha senso verificare se una persona è alta * e * viva, alta * o * viva, * non * alta * e * viva, ecc. –

+0

sacundim: In larga misura ho cambiato idea a questo punto di vista. Ho finito per usare cose come 'newtype Height = Tall | Short' e quindi 'x == Tall' ecc. Un po 'più di digitazione, ma ho pensato che rendesse il codice più leggibile e digitato. – Clinton

risposta

8

È possibile ottenere in qualche modo verso questa, utilizzando newtype s ed una classe se si importa il Preludio che nasconde le funzioni booleane che si desidera utilizzare con i valori IsTall e IsAlive. Si ridefiniscono le funzioni booleane come metodi nella classe, per cui si creano quindi istanze per tutti e 3 i tipi , e IsAlive. Se si utilizza GeneralizedNewtypeDeriving, è anche possibile ottenere le istanze IsTall e IsAlive senza dover scrivere a mano il wrapping/unwrapping.

Ecco uno script di esempio in realtà ho provato a ghci:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

import Prelude hiding ((&&), (||), not) 
import qualified Prelude 

class Boolish a where 
    (&&) :: a -> a -> a 
    (||) :: a -> a -> a 
    not :: a -> a 

instance Boolish Bool where 
    (&&) = (Prelude.&&) 
    (||) = (Prelude.||) 
    not = Prelude.not 

newtype IsTall = IsTall Bool 
    deriving (Eq, Ord, Show, Boolish) 

newtype IsAlive = IsAlive Bool 
    deriving (Eq, Ord, Show, Boolish) 

È ora possibile &&, ||, e not valori di uno dei tre tipi, ma non insieme. E sono tipi separati, quindi le firme delle funzioni ora possono limitare quale dei 3 vogliono accettare.

superiore funzioni di ordine definiti in altri moduli funzionano bene con questo, come in:

*Main> map not [IsTall True, IsTall False] 
[IsTall False,IsTall True] 

Ma non sarà in grado di passare un IsTall a qualsiasi altra funzione definita altrove che si aspetta un Bool, perché l'altro modulo continuerà a utilizzare la versione Prelude delle funzioni booleane. Anche i costrutti linguistici come if ... then ... else ... saranno ancora un problema (anche se un commento di hammar sulla risposta di Norman Ramsey dice che è possibile risolvere questo problema con un'altra estensione GHC). Probabilmente aggiungerei un metodo toBool a quella classe per aiutare a convertire uniformemente a Bool s regolare per aiutare a mitigare tali problemi.

+0

http://hackage.haskell.org/package/cond è utile se si desidera adottare questo approccio. –

8

Le opzioni sono o per definire i tipi di dati algebrici come

data Height = Tall | Short 
data Wiggliness = Alive | Dead 

o per definire nuovi operatori, per esempio, &&&, |||, complement e sovraccaricare loro sul tipo di vostra scelta. Ma anche con sovraccarico non sarai in grado di usarli con if.

Non sono sicuro che le operazioni booleane in altezza abbiano comunque senso. Come giustifica una conclusione secondo cui "alti e corti sono uguali a breve" ma "alti o bassi uguali alti"?

Ti suggerisco di cercare nomi diversi per i tuoi connettivi, che puoi sovraccaricare.

P.S. Haskell ha sempre nuove funzionalità, quindi il meglio che posso dire è che se riesci a sovraccaricare lo if non ne sono a conoscenza.Per dire di Haskell che "tale non si può fare" è sempre pericoloso ...

+3

È possibile sovraccaricare 'if' usando l'estensione' RebindableSyntax' nelle versioni recenti di GHC. [Esempio rapido] (https://gist.github.com/2657492). – hammar

+0

@Norman: Ho provato la risposta di Ben, che ha funzionato abbastanza bene, ma alla fine ho deciso che era più semplice definire Enum come da lei suggerito. Mi sono reso conto che potevo fare ciò che stavo facendo in gran parte senza operazioni logiche. – Clinton

11

Se si modifica leggermente il tipo di dati, è possibile renderlo un'istanza di Functor e quindi è possibile utilizzare fmap per eseguire operazioni sul booleano

import Control.Applicative 

newtype IsAliveBase a = IsAlive a 
newtype IsTallBase a = IsTall a 

type IsAlive = IsAliveBase Bool 
type IsTall = IsTallBase Bool 

instance Functor IsAliveBase where 
    fmap f (IsAlive b) = IsAlive (f b) 

instance Functor IsTallBase where 
    fmap f (IsTall b) = IsTall (f b) 

switch_height :: IsTall -> IsTall 
switch_height h = not <$> h -- or fmap not h 

- EDIT

per le operazioni come & & si può fare un esempio di applicativo

instance Applicative IsAliveBase where 
    pure = IsAlive 
    (IsAlive f) <*> (IsAlive x) = IsAlive (f x) 

e allora si può fare (& &) utilizzando liftA2

esempio:

*Main> let h = IsAlive True 
*Main> liftA2 (&&) h h 
IsAlive True 

si può leggere di più su questo a http://en.wikibooks.org/wiki/Haskell/Applicative_Functors

+0

Come si fa h1 && h2? – Clinton

+0

Aggiornamento della risposta con un esempio per && – Phyx

Problemi correlati