2012-02-09 25 views
6

Sono un linguista che lavora sulla sintassi formale/semantica dei linguaggi naturali. Ho iniziato con lo usando Haskell abbastanza recentemente e molto presto ho capito che avevo bisogno di aggiungere il sottotipo. Ad esempio, dati i tipi Human e Animal, mi piacerebbe avere Human come sottotipo di Animal. Ho scoperto che questo è possibile utilizzando una funzione coerce in cui le istanze sono dichiarate dall'utente, ma non so come definire la coercizione nelle istanze a cui sono interessato. Quindi in sostanza non so cosa aggiungere dopo 'coerce = 'per farlo funzionare'. Ecco il codice fino a quel momento:Sottotipi per i tipi di linguaggio naturale

{-# OPTIONS 

-XMultiParamTypeClasses 
-XFlexibleInstances 
-XFunctionalDependencies 
-XRankNTypes 
-XTypeSynonymInstances 
-XTypeOperators 
#-} 

module Model where 

import Data.List 



data Animal = A|B deriving (Eq,Show,Bounded,Enum) 

data Man = C|D|E|K deriving (Eq,Show,Bounded,Enum) 

class Subtype a b where 
coerce :: a->b 

instance Subtype Man Animal where 
coerce= 




animal:: [Animal] 
animal = [minBound..maxBound] 

man:: [Man] 
man = [minBound..maxBound] 

Grazie in anticipo

+3

Se davvero bisogno subtyping, quindi utilizzare un linguaggio che lo supporta. Quindi stai cercando linguaggi ibridi OO-funzionali. Suggerirei di dare un'occhiata a Scala, che ha la maggior parte delle funzioni di Haskell (solo un po 'più prolisso) e un eccellente sistema di tipo OO. Avere sottotitoli in Haskell è certamente possibile, ma è solo una stampella. – Landei

risposta

4

Non si può scrivere la funzione coerce che stai cercando - almeno, non è sensato. Non ci sono valori in Animal corrispondenti ai valori in Man, quindi non è possibile scrivere una definizione per coerce.

Haskell non ha sottotipi come una decisione esplicita di progettazione, per vari motivi (consente l'inferenza di tipo per funzionare meglio e consentire la sottotipizzazione complica enormemente il sistema di tipi della lingua). Invece, si dovrebbe esprimere i rapporti come questo usando aggregazione:

data Animal = A | B | AnimalMan Man deriving (Eq, Show, Bounded, Enum) 
data Man = C | D | E | K deriving (Eq, Show, Bounded, Enum) 

AnimalMan ha ora il tipo Man -> Animal, esattamente come si voleva coerce avere.

+0

Mi piace AnimalMan :) – Ingo

+0

Molto interessante, darò un'occhiata. Grazie mille – user1198580

5

Non so molto di Lingue naturali, quindi il mio suggerimento potrebbe mancare il punto, ma questo potrebbe essere quello che stai cercando.

{-# OPTIONS 
    -XMultiParamTypeClasses 
    -XFlexibleContexts 
#-} 
module Main where 

data Animal = Mammal | Reptile deriving (Eq, Show) 
data Dog = Terrier | Hound deriving (Eq, Show) 
data Snake = Cobra | Rattle deriving (Eq, Show) 

class Subtype a b where 
    coerce :: a -> b 

instance Subtype Animal Animal where 
    coerce = id 

instance Subtype Dog Animal where 
    coerce _ = Mammal 

instance Subtype Snake Animal where 
    coerce _ = Reptile 

isWarmBlooded :: (Subtype a Animal) => a -> Bool 
isWarmBlooded = (Mammal ==) . coerce 

main = do 
    print $ isWarmBlooded Hound 
    print $ isWarmBlooded Cobra 
    print $ isWarmBlooded Mammal 

Ti dà:

True 
False 
True 

è quel tipo di quello che si sono riprese per? Haskell non ha subtyping integrato, ma potrebbe funzionare come soluzione. Certo, ci sono probabilmente modi migliori per farlo.

Nota: Questa risposta non è intesa a indicare il modo migliore, corretto o idematico per risolvere il problema in questione. È inteso per rispondere alla domanda che era "cosa aggiungere dopo 'coerce =' per farlo funzionare."

+0

Grazie mille, stavo girando per qualcosa di simile sì. Io fondamentalmente usato un approccio simile alla classe vostro sottotipo A b dove costringere :: a -> b esempio sottotipo Human Animal in cui costringere DD = AA – user1198580

1

È piuttosto avanzato, ma date un'occhiata a work di Edward Kmett sull'utilizzo dei nuovi tipi di Constraint per questo tipo di funzionalità.

+0

lo farà, grazie – user1198580

8

Quale livello di astrazione stai lavorando dove "devi aggiungere sottotipizzazione"?

  1. Stai cercando di creare un modello mondiale per il tuo programma codificato dai tipi Haskell? (Posso vedere questo se i tuoi tipi sono in realtà Animal, Dog, etc.)
  2. Stai cercando di creare un software più generale, e si pensa sottotipi sarebbe un buon progetto?
  3. Oppure stai semplicemente imparando haskell e giocando con le cose.

Se (1), penso che non funzionerà per voi così bene. Haskell non ha abilità riflettenti molto buone - es.capacità di tessere la logica del tipo in logica runtime. Il tuo modello finirebbe per essere coinvolto in maniera molto profonda con l'implementazione. Suggerirei di creare un tipo di "modello mondiale" (set di), al contrario di un insieme di tipi corrispondenti a uno specifico modello mondiale. Io rispondo a questa domanda per Haskell: che cos'è un modello mondiale?

Se (2), ripensateci :-). Subtyping fa parte di una tradizione di design in cui Haskell non partecipa. Ci sono altri modi per progettare il tuo programma, e finiranno per giocare meglio con la mentalità funzionale che avrebbe allora la sottotipizzazione. Occorrono tempi per sviluppare il senso del design funzionale, quindi abbi pazienza. Ricorda solo: tienilo semplice, stupido. Utilizzare tipi di dati e funzioni su di essi (ma ricordarsi di utilizzare le funzioni di ordine superiore per generalizzare e condividere il codice). Se stai cercando funzionalità avanzate (anche se i typeclass sono abbastanza avanzati nel senso che intendo), probabilmente stai sbagliando.

Se (3), vedere la risposta di Doug e giocare con roba. Ci sono molti modi per fingere, e tutti alla fine fanno schifo.

+0

Fondamentalmente sto iniziando con (3). Non è passata più di una settimana da quando ho iniziato a lavorare su Haskell (ho fatto qualche Prolog/Python nell'università, ma non era molto rilevante e anche molti anni fa). Grazie mille per la tua risposta – user1198580

11

Ignora semplicemente la classe Sottotipo per un secondo ed esamina il tipo di funzione di coercizione che stai scrivendo. Se il a è un Man e b è un Animal, poi il tipo della funzione coerce si sta scrivendo dovrebbe essere:

coerce :: Man -> Animal 

Ciò significa che tutto quello che dovete fare è scrivere una funzione sensibile che converte ognuno dei tuoi costruttori Man (ovvero C | D | E | K) a un costruttore Animal corrispondente (ovvero A | B). Questo è ciò che significa sottotipo, in cui si definisce una funzione che associa il tipo "sub" al tipo originale.

Naturalmente, si può immaginare che perché si dispone di quattro costruttori per il vostro tipo di Man e solo due costruttori per il vostro tipo di Animal allora si finirà con più di una mappatura Man costruttore per lo stesso Animal costruttore. Non c'è niente di sbagliato in questo e significa solo che la funzione coercitiva non è reversibile. Non posso commentare di più senza sapere esattamente cosa volevano rappresentare quei costruttori.

La risposta più generale alla tua domanda è che non c'è modo di sapere automaticamente quali costruttori in Man devono mappare a quali costruttori in Animal. Ecco perché devi scrivere la funzione coercitiva per dirti quale sia la relazione tra uomini e animali.

Si noti inoltre che non vi è nulla di speciale nella classe "Sottotipo" e nella funzione "coercizione". Puoi semplicemente saltarli e scrivere una funzione 'manToAnimal'. Dopotutto, non esiste un linguaggio incorporato o il supporto del compilatore per la sottotitolazione e Sottotipo è solo un'altra classe che è venuta fuori da un tizio a caso (e francamente, la sottotitolazione non è propriamente idiota di Haskell, ma in realtà non lo avete chiesto) . Tutto ciò che definisce l'istanza di classe è che consente di sovraccaricare la funzione coerce per funzionare sul tipo Man.

Spero che questo aiuti.

+0

Ah, ottimo padre. Mi stavo concentrando sui problemi di progettazione che probabilmente avrebbe dovuto affrontare presto, ma hai visto che il suo problema poteva essere alla superficie - il che, ora che lo vedo, credo sia più probabile. Buon occhio – luqui

+1

Grazie per la risposta. Ho bisogno di una funzione generale del tyoe a-> b visto che userò anche altre coercizioni (ad esempio Man-> Human, Animal-> Entity e così via). Quindi fondamentalmente inizierò dichiarando le istanze in ogni caso. Grazie mille, lo apprezzo davvero – user1198580

2

Se ho capito bene, è abbastanza possibile. Utilizzeremo classi di tipi e tipi di dati algebrici generalizzati per implementare questa funzionalità.

Se si vuole essere in grado di fare qualcosa di simile (dove gli animali e gli esseri umani possono essere alimentati, ma solo gli esseri umani possono pensare):

animals :: [AnyAnimal] 
animals = (replicate 5 . AnyAnimal $ SomeAnimal 10) ++ (replicate 5 . AnyAnimal $ SomeHuman 10 10) 

humans :: [AnyHuman] 
humans = replicate 5 . AnyHuman $ SomeHuman 10 10 

animals' :: [AnyAnimal] 
animals' = map coerce humans 

animals'' :: [AnyAnimal] 
animals'' = (map (\(AnyAnimal x) -> AnyAnimal $ feed 50 x) animals) ++ 
      (map (\(AnyAnimal x) -> AnyAnimal $ feed 50 x) animals') ++ 
      (map (\(AnyHuman x) -> AnyAnimal $ feed 50 x) humans) 

humans' :: [AnyHuman] 
humans' = (map (\(AnyHuman x) -> AnyHuman . think 100 $ feed 50 x) humans) 

Poi è possibile, ad esempio:

{-# LANGUAGE GADTs     #-} 
{-# LANGUAGE MultiParamTypeClasses #-} 

-- | The show is there only to make things easier 
class (Show a) => IsAnimal a where 
    feed :: Int -> a -> a 
    -- other interface defining functions 

class (IsAnimal a) => IsHuman a where 
    think :: Int -> a -> a 
    -- other interface defining functions 

class Subtype a b where 
    coerce :: a -> b 

data AnyAnimal where 
    AnyAnimal :: (IsAnimal a) => a -> AnyAnimal 
instance Show AnyAnimal where 
    show (AnyAnimal x) = "AnyAnimal " ++ show x 

data AnyHuman where 
    AnyHuman :: (IsHuman a) => a -> AnyHuman 
instance Show AnyHuman where 
    show (AnyHuman x) = "AnyHuman " ++ show x 

data SomeAnimal = SomeAnimal Int deriving Show 
instance IsAnimal SomeAnimal where 
    feed = flip const 

data SomeHuman = SomeHuman Int Int deriving Show 
instance IsAnimal SomeHuman where 
    feed = flip const 
instance IsHuman SomeHuman where 
    think = flip const 

instance Subtype AnyHuman AnyAnimal where 
    coerce (AnyHuman x) = AnyAnimal x 

animals :: [AnyAnimal] 
animals = (replicate 5 . AnyAnimal $ SomeAnimal 10) ++ (replicate 5 . AnyAnimal $ SomeHuman 10 10) 

humans :: [AnyHuman] 
humans = replicate 5 . AnyHuman $ SomeHuman 10 10 

animals' :: [AnyAnimal] 
animals' = map coerce humans 

paio di osservazioni:

  • È possibile effettuare le istanze AnyAnimal e AnyHuman delle loro rispettive classi per comodità (atm. devi prima svuotarli e imballarli in seguito).

  • avremmo potuto singolo GADT AnyAnimal come questo (entrambi gli approcci hanno il loro uso direi):

    data AnyAnimal where 
        AnyAnimal :: (IsAnimal a) => a -> AnyAnimal 
        AnyHuman :: (IsHuman a) => a -> AnyAnimal 
    instance Show AnyHuman where 
        show (AnyHuman x) = "AnyHuman " ++ show x 
        show (AnyAnimal x) = "AnyAnimal " ++ show x 
    
    instance Subtype AnyAnimal AnyAnimal where 
        coerce (AnyHuman x) = AnyAnimal x 
        coerce (AnyAnimal x) = AnyAnimal x 
    
+0

Non riesco a capire perché 'TypeFamilies' è richiesto? – is7s

+0

Sì, hai ragione, non li ho usati alla fine. Modificato. – Palmik

+0

Grazie mille per la risposta – user1198580

Problemi correlati