gpt4 book ai didi

haskell - 简单 ADT 到数组的通用转换

转载 作者:行者123 更新时间:2023-12-01 12:08:02 25 4
gpt4 key购买 nike

给定一个总和类型:

type PlayerId = String
data Location = Deck | Hand PlayerId

我如何编写这两个函数中的任何一个(我不关心采用哪种通用方法...帮助我找出哪个更合适的加分):

myF :: Generic a => a -> [String]
myF :: Data a => a -> [String]

-- roughly equivalent to
myF :: Location -> [String]
myF x = case x of
Deck -> ["deck"]
Hand pid -> ["hand", show pid]

(对于任何“无效”类型,例如参数不可显示,返回[]错误。)

上下文:我有许多类似的类似枚举的类型,我想为其定义 Data.Aeson.ToJSON 实例,尽管上面给出了 myF 我知道如何做剩下的事。虽然我这样做主要是为了了解更多关于泛型编程的知识。

尝试:

使用通用

λ> unM1 $ from Deck
(L1 (M1 {unM1 = U1}))

λ> :t (undefined :: Rep Location p)
(undefined :: Rep Location p)
:: D1
('MetaData "Location" "Test" "main" 'False)
(
C1 ('MetaCons "Deck" 'PrefixI 'False) U1
:+:
C1
('MetaCons "Hand" 'PrefixI 'False)
(S1
('MetaSel
'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy)
(Rec0 String)))

因为 :+: 被定义为 L1 | R1,我可能会“合并”以上两个结果。我不确定我会这样做的巧妙方法..也许前者的模式匹配并使用它“下降”到后者 - 但我不确定如何在类型定义和真正的代码。

使用数据

AFAICT Data 是泛型的另一种方法。您会使用或者 Generic 或者 Data,对吧?

我想我需要使用其中一个 gmap* 函数,但我不知道如何将这些类型与我的问题联系起来。尝试了一些探索性的“将随机参数插入各种方法”但没有得到任何有趣的结果。

更新! 我试图简化我的示例,但我可能做得太多了。在我的实际代码中,PlayerId 是围绕字符串的新类型。在这种情况下,以下“有效”(构造函数名称的模数小写):

mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r
(r `mkQ` q) a = case cast a of
Just b -> q b
Nothing -> r

myF :: Data a => a -> [String]
myF input =
[showConstr . toConstr $ input]
++ gmapQ (\x -> ("" `mkQ` f) x) input

f :: PlayerId -> String
f (PlayerId x) = x

这里的见解是构造函数和参数需要区别对待。剩下的一个问题是上面的代码需要知道 PlayerId。以下不起作用:

f :: Show a => a -> String
f = show

... 因为它与 gmapQ 的类型签名不匹配。我想我理解为什么会这样:gmapQ 的工作方式是使用 cast 并且 f 的定义不够具体,无法为其提供实际类型至。我不确定是否有办法解决这个问题,或者它是否是使用 Data 的限制。 (这仍然可能是可行的,如果不是理想的话:我可以想象这样一种情况,我有 myF 由一些 fs 参数化,这些 fs 特定于类型中的特定参数。 )

它也感觉不对,因为我从 original SYB paper 复制了 mkQ 函数。 ...我原以为我应该能够使用 Data.Data 提供的函数来做到这一点。

最佳答案

使用泛型时,您不需要合并两种类型的信息。您只需通过实例处理每种可能的类型。

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
import GHC.Generics

type PlayerId = String
data Location = Deck | Hand PlayerId deriving Generic

instance MyClass Location

class MyClass a where
myF :: a -> [String]
default myF :: (Generic a, MyClass1 (Rep a)) => a -> [String]
myF = defaultMyF

defaultMyF :: (Generic a, MyClass1 (Rep a)) => a -> [String]
defaultMyF a = myF1 $ from a

Rep a 有种类 * -> * 所以我们不能直接为 U1 实现 MyClassV1M1 等。相反,我们需要另一个类,其中 myF 的类型为 ::a b -> [String]

class MyClass1 a where
myF1 :: a b -> [String]

instance MyClass1 V1 where
myF1 _ = []

instance MyClass1 U1 where
myF1 _ = []

instance MyClass1 (K1 i String) where
myF1 (K1 a) = [a]

instance (MyClass1 f, Constructor t) => MyClass1 (C1 t f) where
myF1 c@(M1 a) = (conName c) : myF1 a

instance (MyClass1 f) => MyClass1 (D1 t f) where
myF1 (M1 a) = myF1 a


instance (MyClass1 f) => MyClass1 (S1 t f) where
myF1 (M1 a) = myF1 a

instance (MyClass1 a, MyClass1 b) => MyClass1 (a :+: b) where
myF1 (L1 x) = myF1 x
myF1 (R1 x) = myF1 x

instance (MyClass1 a, MyClass1 b) => MyClass1 (a :*: b) where
myF1 (a :*: b) = myF1 a ++ myF1 b

现在你可以测试它了:

main :: IO ()
main = do
putStrLn $ show $ myF Deck
putStrLn $ show $ myF $ Hand "1234"

关于haskell - 简单 ADT 到数组的通用转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54738623/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com