给定类型X = X Int Int
,我想定义一个函数toX :: [String] -> X
,它在运行时使用泛型构造X
。
当我这样写下来时,这很简单:
toX :: [String] -> X
toX (x:[y]) = to (M1 (M1 (M1 (K1 $ read x) :*: (M1 (K1 $ read y)))))
但我不知道如何递归(如果我们有两个以上的参数)。我的第一次尝试是这样的:
toX xs = to (M1 (M1 (toX' xs)))
toX' (x:[]) = M1 (K1 x)
toX' (x:xs) = M1 (K1 x) :*: (toX' xs)
当然(当然)因类型错误而失败。查看(:*:)
的类型让我更加困惑:(:*:) :: f p -> g p -> (:*:) f g p
。我完全不知道这种类型应该是什么意思以及如何从这里开始。
任何提示?
#!/usr/bin/env stack
{- stack --resolver lts-8.4 runghc-}
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
data X = X Int Int deriving (Generic, Show)
main :: IO ()
main = do
print $ toXeasy ["2","4"]
-- print $ toX ["2","4"]
toXeasy :: [String] -> X
toXeasy (x:[y]) = to (M1 (M1 (M1 (K1 $ read x) :*: (M1 (K1 $ read y)))))
--toX :: [String] -> X
--toX xs = to (M1 (M1 (toX' xs)))
--toX' (x:[]) = M1 (K1 x)
--toX' (x:xs) = M1 (K1 x) :*: (toX' xs)
答案 0 :(得分:3)
这为任何只有一个构造函数(至少有一个字段)的readFields :: [String] -> Maybe X
数据类型Generic
定义了一个函数X
。
readFields
是使用通用版本gReadFields
定义的,它使用泛型表示(即使用GHC.Generics
中显示的类型构造函数构造的类型:M1
,{{1 },(:*:)
...)。
K1
只是为了好玩,这是一种实现类似结果的方法,不使用泛型。用户必须提供构造函数(或函数),类型类负责使用从字符串列表中读取的值填充其所有参数。
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeOperators #-}
module A where
import GHC.Generics
import Control.Monad.Trans.State
import Text.Read
data X = X Int Int deriving (Generic, Show)
main = print (readFields ["14", "41"] :: Maybe X)
readFields :: (Generic a, GReadableFields (Rep a)) => [String] -> Maybe a
readFields xs = fmap to (evalStateT gReadFields xs)
class GReadableFields f where
gReadFields :: StateT [String] Maybe (f p)
instance GReadableFields f => GReadableFields (M1 i c f) where
gReadFields = fmap M1 gReadFields
-- When your type is a large product, you cannot assume that
-- the generic product structure formed using `(:*:)` is list-
-- like (field1 :*: (field2 :*: (field3 ...)), so it is not
-- clear how to split the input list of strings to read each
-- component. For that reason we use `State`. Another possible way
-- is to compute the number of fields of the two operands `f` and `g`.
instance (GReadableFields f, GReadableFields g) => GReadableFields (f :*: g) where
gReadFields = do
f <- gReadFields
g <- gReadFields
return (f :*: g)
instance Read c => GReadableFields (K1 i c) where
gReadFields = StateT $ \(x : xs) -> do
c <- readMaybe x
return (K1 c, xs)
使用{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
module A where
data X = X Int Int deriving Show
main = print (readFields X ["14", "41"])
type family Result a where
Result (a -> b) = Result b
Result a = a
class ReadableFields a where
readFields :: a -> [String] -> Maybe (Result a)
instance {-# OVERLAPPING #-} (ReadableFields b, Read a) => ReadableFields (a -> b) where
readFields f (x : xs) = do
a <- readMaybe x
readFields (f a) xs
readFields _ _ = Nothing
instance (Result a ~ a) => ReadableFields a where
readFields a _ = Just a
非常简单,基础模式打包在one-liner中。
Generic
定义读取单个字段的操作。重要的是有一个实例{-# LANGUAGE FlexibleContexts #-}
import Generics.OneLiner
import Control.Monad.Trans.State
import Text.Read
,以便它可以被组合。
Applicative (StateT [String] Maybe)
现在使用单线程库中的-- Takes a string from the state and reads it out.
readM :: Read a => StateT [String] Maybe a
readM = StateT readM'
where
readM' (x : xs) | Just a <- readMaybe x = Just (a, xs)
readM' _ = Nothing
进行单线程。
createA
答案 1 :(得分:2)
以下是使用generics-sop的解决方案:
{-# LANGUAGE DataKinds, TypeFamilies, FlexibleContexts, TypeApplications #-}
{-# LANGUAGE TemplateHaskell #-}
module ReadFields where
import Data.Maybe
import Generics.SOP
import Generics.SOP.TH
readFields ::
(Generic a, Code a ~ '[ xs ], All Read xs) => [String] -> Maybe a
readFields xs =
to . SOP . Z . hcmap (Proxy @Read) (I . read . unK) <$> fromList xs
data X = X Int Int
deriving Show
deriveGeneric ''X
测试:
GHCi> readFields @X ["3", "4"]
Just (X 3 4)
GHCi> readFields @X ["3"]
Nothing