Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subtypes for natural language types

Tags:

haskell

I'm a linguist working on the formal syntax/semantics of Natural Languages. I've started using Haskell quite recently and very soon I realized that I needed to add subtyping. For example given the types Human and Animal, I would like to have Human as a subtype of Animal. I found that this is possible using a coerce function where the instances are declared by the user, but I do not know how to define coerce in the instances I'm interested in. So basically I do not know what to add after 'coerce =' to make it work'. Here is the code up to that point:

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

Thanks in advance

like image 456
user1198580 Avatar asked Feb 09 '12 01:02

user1198580


4 Answers

Just ignore the Subtype class for a second and examine the type of the coerce function you are writing. If the a is a Man and the b is an Animal, then the type of the coerce function you are writing should be:

coerce :: Man -> Animal

This means that all you have to do is write a sensible function that converts each one of your Man constructors (i.e. C | D | E | K) to a corresponding Animal constructor (i.e. A | B). That's what it means to subtype, where you define some function that maps the "sub" type onto the original type.

Of course, you can imagine that because you have four constructors for your Man type and only two constructors for your Animal type then you will end up with more than one Man constructor mapping to the same Animal constructor. There's nothing wrong with that and it just means that the coerce function is not reversible. I can't comment more on that without knowing exactly what those constructors were meant to represent.

The more general answer to your question is that there is no way to automatically know which constructors in Man should map to which constructors in Animal. That's why you have to write the coerce function to tell it what the relationship between men and animals is.

Note also that there is nothing special about the 'Subtype' class and 'coerce' function. You can just skip them and write an 'manToAnimal' function. After all there is no built-in language or compiler support for sub-typing and Subtype is just another class that some random guy came up with (and frankly, subtyping is not really idiomatic Haskell, but you didn't really ask about that). All that defining the class instance does is allow you to overload the function coerce to work on the Man type.

I hope that helps.

like image 97
Gabriella Gonzalez Avatar answered Nov 05 '22 01:11

Gabriella Gonzalez


What level of abstraction are you working where you "need to add subtyping"?

  1. Are you trying to create world model for your program encoded by Haskell types? (I can see this if your types are actually Animal, Dog, etc.)
  2. Are you trying to create more general software, and you think subtypes would be a good design?
  3. Or are you just learning haskell and playing around with things.

If (1), I think that will not work out for you so well. Haskell does not have very good reflective abilities -- i.e. ability to weave type logic into runtime logic. Your model would end up pretty deeply entangled with the implementation. I would suggest creaing a "world model" (set of) types, as opposed to a set of types corresponding to a specific world model. I.e., answer this question for Haskell: what is a world model?

If (2), think again :-). Subtyping is part of a design tradition in which Haskell does not participate. There are other ways to design your program, and they will end up playing nicer with the functional mindset then subtyping would have. It takes times to develop your functional design sense, so be patient with it. Just remember: keep it simple, stupid. Use data types and functions over them (but remember to use higher-order functions to generalize and share code). If you are reaching for advanced features (even typeclasses are fairly advanced in the sense I mean), you are probably doing it wrong.

If (3), see Doug's answer, and play with stuff. There are lots of ways to fake it, and they all kind of suck eventually.

like image 35
luqui Avatar answered Nov 05 '22 01:11

luqui


I don't know much about Natural Languages so my suggestion may be missing the point, but this may be what you are looking for.

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

Gives you:

True
False
True

Is that kind of what you are shooting for? Haskell doesn't have subtyping built-in, but this might do as a work-around. Admittedly, there are probably better ways to do this.

Note: This answer is not intended to point out the best, correct or idomatic way to solve the problem at hand. It is intended to answer the question which was "what to add after 'coerce=' to make it work."

like image 5
Doug Moore Avatar answered Nov 05 '22 00:11

Doug Moore


You can't write the coerce function you're looking for — at least, not sensibly. There aren't any values in Animal that correspond with the values in Man, so you can't write a definition for coerce.

Haskell doesn't have subtyping as an explicit design decision, for various reasons (it allows type inference to work better, and allowing subtyping vastly complicates the language's type system). Instead, you should express relationships like this using aggregation:

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

AnimalMan now has the type Man -> Animal, exactly as you wanted coerce to have.

like image 4
ehird Avatar answered Nov 05 '22 00:11

ehird