首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何对参数进行映射?

如何对参数进行映射?
EN

Stack Overflow用户
提问于 2017-03-08 14:45:04
回答 2查看 132关注 0票数 1

给定X = X Int Int类型,我想定义一个函数toX :: [String] -> X,它在运行时用泛型构造X

当我把它写下来的时候,这很简单:

代码语言:javascript
复制
toX :: [String] -> X
toX (x:[y]) = to (M1 (M1 (M1 (K1 $ read x) :*: (M1 (K1 $ read y)))))

但我不知道如何进行递归(万一我们有两个以上的参数)。我的第一次尝试是这样的:

代码语言:javascript
复制
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。我完全不知道这种类型是什么意思,以及如何从这里开始。有什么暗示吗?

代码语言:javascript
复制
#!/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)
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-03-08 19:01:49

这为只有一个构造函数(至少有一个字段)的任何readFields :: [String] -> Maybe X数据类型X定义了一个函数X

readFields是使用泛型表示(即使用GHC.Generics中出现的类型构造函数构造的类型:M1(:*:)K1.)定义的泛型版本gReadFields

代码语言:javascript
复制
{-# 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)

为了好玩,这里有一个实现类似结果的方法,它不使用泛型。用户必须提供构造函数(或函数),类型类负责用从字符串列表读取的值填充其所有参数。

代码语言:javascript
复制
{-# 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

编辑

Generic的使用非常简单,因此底层模式被打包在单衬垫中。

代码语言:javascript
复制
{-# LANGUAGE FlexibleContexts #-}

import Generics.OneLiner
import Control.Monad.Trans.State
import Text.Read

定义一个操作来读取单个字段。重要的是要有一个实例Applicative (StateT [String] Maybe),以便能够对其进行组合。

代码语言:javascript
复制
-- 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从一个线性库.

代码语言:javascript
复制
readFields xs = evalStateT (createA (For :: For Read) readM) xs

main = print (readFields ["14", "42"] :: Maybe (Int, Int))
票数 3
EN

Stack Overflow用户

发布于 2017-03-09 11:08:06

下面是使用仿制药-sop的解决方案

代码语言:javascript
复制
{-# 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

测试:

代码语言:javascript
复制
GHCi> readFields @X ["3", "4"]
Just (X 3 4)
GHCi> readFields @X ["3"]
Nothing
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42674354

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档