我想比较haskell中的两个记录,而不必一遍又一遍地定义记录的所有元素的记录数据类型的每个变化以及2个数据的每个函数。
我阅读了有关镜头的信息,但是我找不到关于它的例子, 并且不知道在文档中从哪里开始阅读。
示例,不起作用:
data TheState = TheState { number :: Int,
truth :: Bool
}
initState = TheState 77 True
-- not working, example:
stateMaybe = fmap Just initState
-- result should be:
-- ANewStateType{ number = Just 77, truth = Just True}
以同样的方式,我想比较两种状态:
state2 = TheState 78 True
-- not working, example
stateMaybe2 = someNewCompare initState state2
-- result should be:
-- ANewStateType{ number = Just 78, truth = Nothing}
答案 0 :(得分:2)
就像其他人在评论中提到的那样,创建不同的记录来保存字段的Maybe
版本并进行手动转换最容易。但是,有一种方法可以使函子像以一种更加自动化的方式在您的字段上进行映射一样。
它可能比您想要的要复杂得多,但是可以使用称为更高种类的数据( HKD )的模式和名为barbies
的库来实现
以下是有关该主题的精彩博客文章:https://chrispenner.ca/posts/hkd-options
这是我在您的特定示例中尝试使用 HKD 的尝试:
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
-- base
import Data.Functor.Identity
import GHC.Generics (Generic)
-- barbie
import Data.Barbie
type TheState = TheState_ Identity
data TheState_ f = TheState
{ number :: f Int
, truth :: f Bool
} deriving (Generic, FunctorB)
initState :: TheState
initState = TheState (pure 77) (pure True)
stateMaybe :: TheState_ Maybe
stateMaybe = bmap (Just . runIdentity) initState
这里发生的是,我们将记录的每个字段都包装在自定义f
中。现在,我们要选择参数TheState
的内容,以包装每个字段。现在, normal 记录的所有字段都包裹在Identity
中。但是您也可以轻松使用其他版本的记录。使用bmap
函数,您可以将从TheState_
的一种类型映射到另一种类型。
老实说,博客文章在解释这一点上将比我做得更好。我觉得这个主题很有趣,但是我自己还是一个新手。
希望这对您有所帮助! :-)
答案 1 :(得分:0)
如何根据记录制作函子。为此,我有一个答案:将该功能应用于>记录的所有项目。
我想将记录用作异构容器/哈希图,其中 名称决定值的类型
虽然没有“简单”的直接方法,但是可以使用几个现有的库来完成。
此答案使用red-black-record库,该库本身是基于sop-core的匿名产品构建的。 “ sop-core”允许将产品中的每个字段包装在像Maybe
这样的函子中,并提供统一操作字段的功能。 “ red-black-record”继承了这一点,并添加了命名字段和普通记录中的转换。
要使TheState
与“ red-black-record”兼容,我们需要执行以下操作:
{-# LANGUAGE DataKinds, FlexibleContexts, ScopedTypeVariables,
DeriveGeneric, DeriveAnyClass,
TypeApplications #-}
import GHC.Generics
import Data.SOP
import Data.SOP.NP (NP,cliftA2_NP) -- anonymous n-ary products
import Data.RBR (Record, -- generalized record type with fields wrapped in functors
I(..), -- an identity functor for "simple" cases
Productlike, -- relates a map of types to its flattened list of types
ToRecord, toRecord, -- convert a normal record to its generalized form
RecordCode, -- returns the map of types correspoding to a normal record
toNP, fromNP, -- convert generalized record to and from n-ary product
getField) -- access field from generalized record using TypeApplication
data TheState = TheState { number :: Int,
truth :: Bool
} deriving (Generic,ToRecord)
我们自动推导Generic
实例,该实例允许其他代码内省数据类型的结构。 ToRecord
需要这样做,它可以将普通记录转换为其"generalized forms"。
现在考虑以下功能:
compareRecords :: forall r flat. (ToRecord r,
Productlike '[] (RecordCode r) flat,
All Eq flat)
=> r
-> r
-> Record Maybe (RecordCode r)
compareRecords state1 state2 =
let mapIIM :: forall a. Eq a => I a -> I a -> Maybe a
mapIIM (I val1) (I val2) = if val1 /= val2 then Just val2
else Nothing
resultNP :: NP Maybe flat
resultNP = cliftA2_NP (Proxy @Eq)
mapIIM
(toNP (toRecord state1))
(toNP (toRecord state2))
in fromNP resultNP
它比较具有ToRecord r
实例的两条记录,以及具有Eq
实例(Productlike '[] (RecordCode r) flat
和All Eq flat
约束)的对应的扁平类型列表。
首先,它使用toRecord
将初始记录参数转换为它们的广义形式。这些通用形式使用身份函子I
进行参数化,因为它们来自“纯”值,并且还没有任何作用。
使用toNP
将广义记录形式转换为n元乘积。
然后,我们可以使用“ sop-core”中的cliftA2_NP
函数使用它们各自的Eq
实例来比较所有字段。该函数需要使用Proxy
指定Eq
约束。
剩下的唯一要做的就是使用fromNP
重建通用记录(由Maybe
参数化的记录)。
使用示例:
main :: IO ()
main = do
let comparison = compareRecords (TheState 0 False) (TheState 0 True)
print (getField @"number" comparison)
print (getField @"truth" comparison)
getField
用于从通用记录中提取值。字段名称通过Symbol
的形式指定为-XTypeApplications
。