映射到字符串

我是Haskell的新手,所以也许我在这里错过了一些基本概念(或者可能找不到合适的扩展名)。 我想知道是否有一种方法来优化或进一步提取以下方案。 这段代码看起来非常冗余。

假设我有以下数据类:

data Person = Person
              { personName :: !String
              , personAge  :: !Int
              } deriving Show

data Dog = Dog
           { dogName :: !String
           , dogAge  :: !Int
           } deriving Show

比方说,我有一个服务,我只关心输出记录作为字符串。 实际上,这些字符串可能是JSON和从DB中获取的记录,但让我们来看一个更简单的情况。 我基本上需要一个URL标记来获取适当的对象(比如,“dog”字符串会给我一个Dog,甚至只是Haskell“show”字符串,而不会明确声明它为(value):: Dog)。

我试图用几种方法来实现它......似乎工作的唯一一件事是:

data Creature =  DogC    Dog
               | PersonC Person  
               deriving Show

fromString :: String -> Maybe Creature
fromString "dog" =    Just $ DogC    $ Dog "muffin" 8
fromString "person" = Just $ PersonC $ Person "John" 22
fromString   _    = Nothing

main :: IO ()
main = do
       putStrLn $ show $ fromString "dog"

我并不完全喜欢新类型,也不喜欢fromString声明的列表。 为了从原始数据声明中受益,我可能需要编写一个类似繁琐的表达式(例如“fromCreature”)来将Creature恢复为我的原始类型。 这些信息可能会改变,所以我可能需要TH来做一些声明......

有没有解决这个问题的方法? 我摆弄GADT和类,但都似乎依赖于类型而不是基于值的多态(字符串标识符往往会导致模糊实例的问题)。 将构造函数映射到一个字符串(用Data.Map说)会很好,但构造函数通常有不同的类型。

更新

所以,我采取了一种与我所问的问题不完全相关的方法,但这对某个人可能有用。 我确实想保留一些记录类型,但大多数并没有增加太多价值,并且正在阻碍我。 我遵循的步骤如下所示:

  • 使用不同的/更低级别的DB驱动程序,返回可工作的类型(例如[ColumnDef]和[[SQLValue]],而不是元组和记录...)。
  • 为SQLValue创建ToJSON实例 - 除了一些ByteString类型外,大部分类型都被覆盖了,我必须处理SQLNull到Null的转换。 为了保持与某些记录类型的兼容性,我的默认处理程序如下所示: toJSON = genericToJSON defaultOptions { sumEncoding = UnTaggedValue}如果需要,未标记的值应允许将JSON读入定义的数据类型(例如,Dog / Person)。
  • 鉴于列名可从ColumnDef访问,我写了一个表达式,将[ColumnDef]和[SqlValue]拉到与Aeson兼容的键值对的列表中,例如toJsPairs :: [ColumnDef] -> [SqlValue] -> [(Text,Value)]
  • 然后,我写了一个表达式来从表名中获取JSON,这或多或少地作为我的“通用调度程序”。 它引用了授权表的列表,所以它不像听起来那么疯狂。
  • 代码看起来有点像这样(使用mysql-haskell)。

    {-# LANGUAGE OverloadedStrings #-}
    
    import qualified Control.Applicative as App
    import Database.MySQL.Base
    import qualified System.IO.Streams as Streams
    import Data.Aeson (FromJSON, ToJSON)
    import Data.Aeson.Encode.Pretty (encodePretty)
    import Data.Aeson.Types
    import Data.Text.Encoding
    import Data.String (fromString)
    import Data.ByteString.Internal
    import qualified Data.ByteString.Lazy.Internal as BLI
    import Data.HashMap.Strict (fromList)
    
    appConnectInfo = defaultConnectInfo {
                       ciUser = "some_user"
                     , ciPassword = "some_password"
                     , ciDatabase = "some_db"
                 }
    
    instance FromJSON ByteString where
      parseJSON (String s) = pure $ encodeUtf8 s
      parseJSON _ = App.empty
    
    instance ToJSON ByteString where
        toJSON  = String . decodeUtf8 
    
    instance ToJSON MySQLValue where
        toJSON (MySQLNull) = Null
        toJSON x = genericToJSON defaultOptions
                           { sumEncoding = UntaggedValue } x 
    
    -- This expression should fail on dimensional mismatch.
    -- It's stupidly lenient, but really dimensional mismatch should
    -- never occur...
    
    toJsPairs :: [ColumnDef] -> [MySQLValue] -> [(Text,Value)]
    toJsPairs [] _ = []
    toJsPairs _ [] = []
    toJsPairs (x:xs) (y:ys) = (txt x, toJSON y):toJsPairs xs ys
                        where
                             -- Implement any modifications to the key names here
                             txt = decodeUtf8.columnName
    
    listRecords :: String -> IO BLI.ByteString 
    listRecords tbl = do
        conn <- connect appConnectInfo
    
        -- This is clearly an injection vulnerability.
        -- Implemented, however, the values for 'tbl' are intensely
        -- vetted.  This is just an example.
    
        (defs, is) <- query_ conn $ fromString ( "SELECT * FROM `" ++ tbl ++ "` LIMIT 100")
        rcrds <- Streams.toList is
        return $ encodePretty $ map (jsnobj defs) rcrds
            where
                jsnobj :: [ColumnDef] -> [MySQLValue] -> Value
                jsnobj defs x = Object $ fromList $ toJsPairs defs x
    

    如果最后想要使用的是json值 - 使用aeson库将结果表示为json值可能是有意义的:

    {-# LANGUAGE DeriveGeneric #-}
    
    import Data.Aeson
    import GHC.Generics
    
    data Dog = Dog Int String deriving (Show, Generic)
    data Cat = Cat Int String deriving (Show, Generic)
    
    -- here I'm using instance derived with generics, but you can write one by
    -- hands
    instance ToJSON Dog
    instance ToJSON Cat
    
    -- actions to get stuff from db
    getDog :: Monad m => Int -> m Dog
    getDog i = return (Dog i (show i))
    
    getCat :: Monad m => Int -> m Cat
    getCat i = return (Cat i (show i))
    
    -- dispatcher - picks which action to use
    getAnimal :: Monad m => String -> Int -> m (Maybe Value)
    getAnimal "dog" i = Just . toJSON <$> getDog i
    getAnimal "cat" i = Just . toJSON <$> getCat i
    getAnimal _ _ = return Nothing
    
    
    main :: IO ()
    main = do
        getAnimal "dog" 2 >>= print
        getAnimal "cat" 3 >>= print
        getAnimal "chupakabra" 12 >>= print
    

    高能魔法版本

    class Monad m => MonadAnimal m where
        -- basically you want something that fetches extra argumets from HTTP or
        -- whatevere, perform DB query and so on.
    
    class Animal a where
        animalName :: Proxy a -> String
        animalGetter :: MonadAnimal m => m a
    
    locateAnimals :: MonadAnimal m => Q [(String, m Value)]
    locateAnimals -- implement using TH (reify function is your friend). It should look for
    -- all the animal instances in scope and make a list from them with serialized
    -- fetcher.
    
    -- with that in place dispatcher should be easy to implement
    
    链接地址: http://www.djcxy.com/p/43093.html

    上一篇: Mapping to String

    下一篇: Haskell just using the read function signals an error