Heterogeneous indexed structure without existential types?

I am trying to create a heterogeneous indexed structure and came up with the following solution, but I was told not to use existential types .

Can you see a better solution?

I would like to keep the separation between the definition of interfaces ( type and class ) and the concrete implementation ( data and instance ). Change after @hammar comment: in a real application, the values ​​are not Show n, but simply stored in the request; also myData is more complicated with extra records.

If this can lead to a better solution, then the exact requirement is to build a map of cards (internal cards). Each internal map is homogeneous and has the form Map String a , however, each internal map can use a different type for its values. You can also think of it as a two-level indexed structure. The implementation should not use Data.Map , but should be effective.

 {-# LANGUAGE ExistentialQuantification #-} module Scratch.SO_ExtistentialTypes where import Data.Map data HeteroValue = forall a. Show a => HV a instance Show HeteroValue where show (HV b) = show b type MyMap = Map String HeteroValue class MyClass c where getMyMap :: c -> MyMap data MyData = MyData { myMap :: MyMap } instance MyClass MyData where getMyMap = myMap 

This snippet can be run with ghci

 let myMap = fromList [("key1", HV "abc"), ("key2", HV 123)] :: MyMap let myData = MyData myMap getMyMap myData 
+4
source share
3 answers

One way to make "heterogeneous collections" is Data.Dynamic .

 module Scratch.SO_Dyn where import Data.Dynamic import Data.Map type MyMap = Map String Dynamic class MyClass c where getMyMap :: c -> MyMap data MyData = MyData { myMap :: MyMap } instance MyClass MyData where getMyMap = myMap 

The data you want to put on this card should get Typeable.
Use {-# LANGUAGE DeriveDataTypeable #-} and deriving (Data, Typeable) , see also http://www.haskell.org/ghc/docs/7.6.1/html/users_guide/deriving.html#deriving-typeable .

You can then pass your data to the Dynamic type using toDyn and safely apply it to the Dynamic type using fromDynamic .


Although this is a completely correct approach, I and many other Haskellers strongly recommend that you consider creating a custom data type, rather than using a truly heterogeneous collection. Suppose (in the spirit of Halloween) that you know that the only things you put on this card are Cat s, Witch es, and Ghoul s.

 data Cat = ... data Witch = ... data Ghoul = ... 

Just by ticking every possible option, you can later determine what each thing is.

 data HeteroValue = DarkOmen Cat | Hag Witch | Haunting Ghoul case (Map.lookup "Midnight visitor" theMap) of Just (DarkOmen cat) -> hiss cat Just (Hag witch) -> cackle witch Just (Haunting ghoul) -> spook ghoul Nothing -> error ... 
+4
source

This is a good model for object oriented languages, but it is the well-known Haskell antipattern . Read this article. I want you to read this most of all what I say.

See also this answer , but on the condition that I believe that GADTs are more elegant than existential types (see below).


Please try to find the best functional ways to program your program, rather than the best functional programming options for reintroducing object-oriented programming. You still haven't made any purpose for your code other than your hope for OO-style programming.

(Imagine Craig, a C programmer, new to Java, who is trying to find how to make a pointer to an array of structures or get stuck trying to make a method with malloc functionality, or get frustrated because there is no Arithmetic pointer. Janet, a Java programmer, answered would you ask Craig why he wants to make pointers himself, what’s wrong with garbage collection, and why would anyone ever want pointer arithmetic when they have arrays with built-in constraints? Indeed Craig would be better off learning to program go Automatic Java first, before deciding what C functions it really cannot do, OO is a different paradigm for C-people as close as possible to a machine, while a relatively-machine-independent philosophy still exists. Craig must learn a new paradigm in as his first priority, which could make him a better C. programmer. Don't visit France to speak only English, watch CNN and have McDonalds! There's a point in which you should be trying to write your code in the most functional way possible.)


If you really want to have other information about your data, except that it is subject to your contract, one of the ways is to use GADT. You should know that Haskell will guide you to your claim; there is no casting to get you out of a mindless design decision. (Casting is a way to turn compile-time checks into runtime checks or, in other words, a way to turn compile-time errors into run-time errors. I don't see anything good about it.)

 {-# LANGUAGE GADTs #-} class Contract a where toString :: a -> String fromInts :: Int -> Int -> a alter :: a -> a -- other functionality data Encapsulated where Encapsulate :: Contract a => a -> Encapsulated 

Now that you have encapsulated your data, you can do whatever you like, as if it were a regular data type, and restore any of the following Contract ed functions:

 munged :: Encapsulated -> String munged (Encapsulate a) = toString.alter.alter.alter $ a 

If you like it so much, you can store a whole bunch of Encapsulated data on the map, there is no need to do anything special or redefine Data.Map for your existential, because there is a powerful functional paradigm: Data.Map makes no assumptions about your data. This is parametric polymorphism and works for anything. Anything, even functions. The only thing it does is that your keys are sorted ( Ord k => ) and your data is homogeneous (and our GADT is homogeneous, despite the fact that it is made from heterogeneous data).


This is one way to do what you asked for, but if we knew what you wanted it for, we could give you the best advice. (Perhaps another new question!) Please really read the article I am involved with and see if you can implement your class as a data type full of functions / results, and your instances as functions for this data type.

+6
source

Apparently the only thing you can do with HeteroValue is show it, i.e. convert it to String . With that in mind, it makes no sense to store values, you could just save the converted string:

 type MyMap = Map String String 

or

 data HeteroData = HD { getShow :: String } type MyMap = Map String HeteroData 

This can be easily applied to other classes.

If you are instead doing things like pattern matching on HeteroValue , existential types are a good solution.

0
source

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


All Articles