在haskell中是否有一种比较/更改一个/两个记录的规范方法?

时间:2019-08-15 23:20:49

标签: haskell

我想比较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}

2 个答案:

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