Deconstructing an existential type

后端 未结 3 1829
梦谈多话
梦谈多话 2021-01-15 12:58

I am using an existential type as a wrapper. At a point in my code where I know the enclosed type, I want to do something with it that is specific to the enclosed type. This

3条回答
  •  悲哀的现实
    2021-01-15 13:18

    Could not deduce (a ~ Bug).

    We can, but the compiler can't.
    We know that agentId is meant to be injective, so that two instances of different types have the same agentId String, but the compiler can't deduce that. Applying a function Agent a -> String loses whatever type information you had about a, and you didn't have much because it was existentially qualified.

    Problem 1: Existential data types stop the compiler from using the type of the data. This is the heart of your problems. You decided you wanted them to be different types and then you decided you wanted them to be all one type.

    Problem 2: Strings aren't types, types are. Better than Strings are user-defined types, eg

    data Species = Bug | Saurapod | ....
    

    but better than data is an actual type, don't make it then hide it.

    Solution 1:

    Avoid existential types. Instead of having a type class Agent, have a record type data Agent, making all Agents uniform.

    data Agent = Agent {
        agentId :: String,
        speciesId :: Species,
        -- ...other stuff agents need. 
        -- Species-specific data is an illusion; 
        -- make Agent widely useful, catering for the eventualities
        }
    

    Solution 2:

    Avoid existential types. Instead of having a type class providing an interface for agents, have a data type consisting of the necessary bits:

    data Agent = Agent {
        agentId :: String,
        speciesId :: Species,
        -- ...other stuff _all_ agents need. 
        }
    
    class IsAgent a where
      agent :: a -> Agent
    

    Now you can have

    agents::[Agent]
    agents = map agent bugs 
          ++ map agent saurapods 
          ++ ...
    

    Solution 3:

    Avoid existential types. Instead of having existential Agents, have a union type of Agents

    class Agent a where
       -- all the stuff you want
    instance Agent Bug where
       ...
    instance Agent Saurapod where
       ...
    data AnyAgent = ABug Bug | ASaurapod Saurapod | ... 
       -- ensure you have an agent instance for each type you need
    
    instance Agent AnyAgent where
       -- much boilerplate code unwrapping and applying
    
    agents :: [AnyAgent]
    agents = map ABug bugs ++ map ASaurapod saurapods ++ ....
    

    Solution 4:

    Avoid existential types. Instead of having existential Agents, separate out generic Agent code, and have a union type of Agents including this

    data Agent = Agent {
        agentId :: String,
        -- ...other stuff _all_ agents need. 
        }
    
    data Bug = Bug --..... Bug-specific data
    data Saurapod = Saurapod --... Saurapod-specific data
    
    data AnyAgent = ABug Agent Bug | ASaurapod Agent Saurapod | ... 
    
    agent :: AnyAgent -> Agent
    agent (ABug a _) = a
    agent (ASaurapod a _) = a
    ...
    
    agents :: [AnyAgent]
    agents = [ABug (Agent {agentId="007", ...}) (Bug ....),
              ASaurapod (Agent {agentId="Pat", ...}) (Saurapod ....),
              ...]
    

    Solution 5

    Refuse to give up on existential types, choose to leave the joyous ease of static typing and use Dynamic or Typable or something else unfun to recover some type information.

提交回复
热议问题