如何映射参数?

时间:2017-03-08 14:45:04

标签: haskell generics

给定类型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)

2 个答案:

答案 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