2012-02-09 7 views
6

Soy un lingüista que trabaja en la sintaxis/semántica formal de las lenguas naturales. Empecé con usando Haskell hace poco y muy pronto me di cuenta de que necesitaba agregar subtipado. Por ejemplo, dados los tipos Humanos y Animal, Me gustaría tener Humanos como un subtipo de Animal. Descubrí que esto es posible usando una función de coerción donde las instancias son declaradas por el usuario, pero no sé cómo definir coerción en las instancias que me interesan. Así que, básicamente, no sé qué agregar después de 'coerce = 'para que funcione'. Aquí está el código hasta ese momento:Subtipos para los tipos de lenguaje natural

{-# 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] 

Gracias de antemano

+3

Si realmente necesita subtipos, a continuación, utilizar un lenguaje que lo soporta. Así que estás buscando lenguajes híbridos OO-funcionales. Sugiero echarle un vistazo a Scala, que tiene la mayoría de las características que tiene Haskell (un poco más detallado) y un excelente sistema de tipo OO. Tener subtipos en Haskell es ciertamente posible, pero es solo una muleta. – Landei

Respuesta

4

No se puede escribir la función coerce que está buscando - al menos, no con sensatez. No hay ningún valor en Animal que se corresponda con los valores en Man, por lo que no puede escribir una definición para coerce.

Haskell no tiene la subtipificación como una decisión de diseño explícita, por varias razones (permite que la inferencia de tipos funcione mejor, y permitir la subtipificación complica enormemente el sistema de tipos del idioma). En su lugar, usted debe expresar relaciones como esto utilizando agregación:

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

AnimalMan tiene ahora el tipo Man -> Animal, exactamente como lo hubiera querido tener coerce.

+0

Me gusta AnimalMan :) – Ingo

+0

Muy interesante, voy a echar un vistazo. Muchas gracias – user1198580

5

No sé mucho sobre los idiomas naturales, por lo que mi sugerencia puede estar perdiendo sentido, pero esto puede ser lo que estás buscando.

{-# 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 

le ofrece:

True 
False 
True 

es ese tipo de lo que está disparando para? Haskell no tiene incorporado el subtipado, pero esto podría funcionar como una solución alternativa. Es cierto que probablemente haya mejores formas de hacerlo.

Nota: Esta respuesta no pretende señalar la forma mejor, correcta o idónea para resolver el problema en cuestión. Tiene la intención de responder a la pregunta que era "qué agregar después de 'coerce =' para que funcione".

+0

Muchas gracias, estaba filmando para algo similar, sí. Básicamente utilizado un enfoque similar a la suya clase subtipo A b donde coaccionar :: a -> b ejemplo Subtipo animal humano, donde coaccionar DD = AA – user1198580

1

Es bastante avanzado, pero eche un vistazo a work de Edward Kmett sobre el uso de los nuevos tipos de restricción para este tipo de funcionalidad.

+0

lo hará, gracias – user1198580

8

¿Qué nivel de abstracción está trabajando en donde "necesita agregar subtipado"?

  1. ¿Está tratando de crear un modelo mundial para su programa codificado por tipos de Haskell? (Puedo ver esto si sus tipos son en realidad Animal, Dog, etc.)
  2. ¿Estás tratando de crear software más general, y usted piensa subtipos serían un buen diseño?
  3. ¿O simplemente estás aprendiendo haskell y jugando con cosas?

Si (1), creo que no funcionará para usted tan bien. Haskell no tiene muy buenas habilidades de reflexión, es decircapacidad de entrelazar la lógica de tipo en lógica de tiempo de ejecución. Su modelo terminaría muy enredado con la implementación. Sugeriría crear un "modelo mundial" (conjunto de) tipos, a diferencia de un conjunto de tipos que corresponden a un modelo mundial específico. Es decir, responda esta pregunta para Haskell: ¿qué es un modelo mundial?

Si (2), piénselo de nuevo :-). Subtipo es parte de una tradición de diseño en la que Haskell no participa. Hay otras maneras de diseñar su programa, y ​​terminarán jugando mejor con la mentalidad funcional que tendría la subtipificación. Lleva tiempo desarrollar su sentido de diseño funcional, así que sea paciente con él. Solo recuerda: mantenlo simple, estúpido. Use tipos de datos y funciones sobre ellos (pero recuerde usar funciones de orden superior para generalizar y compartir código). Si busca funciones avanzadas (incluso las clases de tipos son bastante avanzadas en el sentido que quiero decir), probablemente lo esté haciendo mal.

Si (3), vea la respuesta de Doug y juegue con cosas. Hay muchas formas de fingir, y todos chupan con el tiempo.

+0

Básicamente estoy empezando con (3). No ha pasado más de una semana desde que comencé a trabajar en Haskell (hice un poco de Prolog/Python en la Uni pero eso no fue muy relevante y también hace muchos años). Muchas gracias por su respuesta – user1198580

11

Simplemente ignore la clase Subtipo por un segundo y examine el tipo de función de coerción que está escribiendo. Si el a es una Man y la b es un Animal, entonces el tipo de la función coerce que está escribiendo debe ser:

coerce :: Man -> Animal 

Esto significa que todo lo que tiene que hacer es escribir una función razonable que convierte cada uno de sus constructores Man (es decir, C | D | E | K) a un constructor Animal correspondiente (es decir, A | B). Eso es lo que significa subtipificar, donde defines alguna función que mapea el tipo "sub" en el tipo original.

Por supuesto, se puede imaginar que debido a que tiene cuatro constructores para su tipo Man y sólo dos constructores para su tipo Animal entonces usted va a terminar con más de un mapeo Man constructor para el mismo Animal constructor. No tiene nada de malo y solo significa que la función de coerción no es reversible. No puedo comentar más sobre eso sin saber exactamente qué se suponía que representaban esos constructores.

La respuesta más general a su pregunta es que no hay forma de saber automáticamente qué constructores en Man deben asignar a qué constructores en Animal. Es por eso que tienes que escribir la función de coerción para decirle cuál es la relación entre hombres y animales.

Tenga en cuenta también que no hay nada especial acerca de la clase 'Subtipo' y la función 'coerción'. Puede omitirlas y escribir una función 'manToAnimal'. Después de todo, no hay un lenguaje incorporado o compatibilidad con el compilador para el subtipo y Subtipo es simplemente otra clase que se le ocurrió a un tipo aleatorio (y, francamente, el subtipaje no es realmente idiomático Haskell, pero en realidad no preguntaste al respecto) . Todo lo que define la instancia de la clase es permitirle sobrecargar la función coerce para trabajar en el tipo Man.

Espero que ayude.

+0

Ah, muy buen señor. Me estaba enfocando en los problemas de diseño que probablemente enfrentaría pronto, pero viste que su problema podría haber estado en la superficie, lo cual, ahora que lo veo, creo que es más probable. Buen ojo. – luqui

+1

Gracias por la respuesta. Necesito una función general del tyoe a-> b ya que voy a usar otras coerciones también (por ejemplo, Man-> Human, Animal-> Entity, etc.). Así que básicamente comenzaré declarando las instancias en cada caso. De nuevo, muchas gracias, realmente lo aprecio. – user1198580

2

Si te entendí correctamente, es bastante posible. Usaremos clases de tipos y tipos de datos algebraicos generalizados para implementar esta funcionalidad.

Si usted quiere ser capaz de hacer algo como esto (donde los animales y los seres humanos pueden ser alimentados, pero sólo los seres humanos pueden pensar):

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) 

Entonces es posible, por ejemplo:

{-# 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 

algunos comentarios:

  • puede crear instancias AnyAnimal y AnyHuman de sus respectivas clases por conveniencia (atm. tienes que desempaquetarlos primero y empacarlos luego).

  • Podríamos tener un solo GADT AnyAnimal como esto (ambos enfoques tienen su uso Conjeturaría):

    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

No veo por qué 'TypeFamilies' es obligatorio. – is7s

+0

Sí, tienes razón, al final no los utilicé. Editado – Palmik

+0

Muchas gracias por su respuesta – user1198580

Cuestiones relacionadas