Subtypes for natural language types

I am a linguist working on the formal syntax / semantics of Natural Languages. I started using Haskell just recently, and very soon I realized that I needed to add subtyping. For example, given the types of Human and animals, I would like Human to be a subtype of Animal. I have found that this is possible with the coerce function, where instances are declared by the user, but I don’t know how to define coercion in cases of interest to you. So basically I don’t know what to add after “coerce =" so that it works. Here is the code to this 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 ab where coerce :: a->b instance Subtype Man Animal where coerce= animal:: [Animal] animal = [minBound..maxBound] man:: [Man] man = [minBound..maxBound] 

Thanks in advance

+6
source share
6 answers

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

 coerce :: Man -> Animal 

This means that all you have to do is write a reasonable function that converts each of your Man constructors (i.e. C | D | E | K ) to the corresponding Animal constructor (i.e. A | B ). This means that it means a subtype where you define some function that maps the type "under" to the original type.

Of course, you can imagine that since you have four constructors for your Man type and only two constructors for your Animal type, then you will get more than one Man constructor constructor for the same Animal constructor. There is nothing wrong with that, and it just means that the coercive function is not reversible. I can no longer comment on this without knowing what these constructors mean.

A more general answer to your question is that there is no way to automatically find out which constructors in Man should map to constructors in Animal . That's why you need to write a coercive function to tell her what the relationship between men and animals is.

Also note that there is nothing special about the Subtype and Coercion features. You can simply skip them and write the "manToAnimal" function. After all, there is no built-in language or compiler support for typing, and a subtype is another class that some random guy came across (and, frankly, subtyping is not really idiomatic for Haskell, but you really didn't ask about it) , Everything that defines an instance of the class allows you to overload the coerce function to work with the Man type.

I hope this helps.

+11
source

What level of abstraction do you work when you need to add subtyping?

  • Are you trying to create a world model for your program encoded by Haskell types? (I see this if your types are actually Animal , Dog , etc.)
  • Are you trying to create more general software and think that subtypes will be a good design?
  • Or you just study haskell and play with things.

If (1), I think this will not work for you so well. Haskell does not have very good reflective abilities, that is, a way to drag type logic into execution logic. Your model will be pretty much confused in implementation. I would suggest creating a "model of the world" (set), rather than a set of types corresponding to a specific world model. Ie answer this question for Haskell: what is the world model?

If (2), think again :-). Subtyping is part of the design tradition in which Haskell is not involved. There are other ways to develop your program, and they will ultimately play better with functional thinking, and then subtyping will have. It takes time to develop your functional design, so be patient. Just remember: keep it simple, stupid. Use data types and functions above them (but remember to use higher-order functions to generalize and share code). If you achieve advanced functions (even the cool classes are pretty advanced in the sense, I mean), you're probably wrong.

If (3), see Doug's answer and play with things. There are many ways to fake it, and they all suck in the end.

+8
source

I don’t know much about natural languages, so my suggestion may be missing a point, but it 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 ab 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 what you shoot for? Haskell has no built-in subtyping, but this may be related to work. Admittedly, there are probably better ways to do this.

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

+5
source

You cannot write the coerce function you are looking for, at least not reasonably. In Animal there are no values ​​corresponding to those in Man , so you cannot write a definition for coerce .

Haskell does not have subtypes as an explicit design decision for various reasons (it allows you to work with the output type better, and allows subtyping greatly complicates the language type system). Instead, you should express such relationships 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 , as you would like coerce have.

+4
source

If I understand you correctly, this is entirely possible. We will use type classes and generalized algebraic data types to implement this functionality.

If you want to do something like this (where you can feed animals and people, but only people can think):

 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) 

Then it is possible, for example:

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

A few comments:

  • You can make instances of AnyAnimal and AnyHuman for your classes for convenience (you first need to unpack them and pack them later).

  • We can have one GADT AnyAnimal, like this (both approaches use them, I would guess):

     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 
+2
source

It's pretty advanced, but look at Edward Kmett's work on using the new Constraint types for this kind of function.

+1
source

Source: https://habr.com/ru/post/907998/


All Articles