给定功能记录和功能所作用类型的数据记录,如何通用地应用功能记录? [英] Given a record of functions, and a record of data of the types acted on by the functions, how to generically apply the function record?

查看:78
本文介绍了给定功能记录和功能所作用类型的数据记录,如何通用地应用功能记录?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

标题有点不准确,因为我的特殊情况要复杂得多:它们不是作用于第一条记录中的函数直接作用于第二条记录中的值,而是作用于值的列表(或其他可遍历的).可以假定,如果为某个特定领域购买了商品,那么返回的结果与该领域的类型相同.

The title is slightly inaccurate as my particular case is a bit more involved: rather than the functions in the first record acting directly on values in the second record, they act on a list (or other traversable) of the values. The result of an application for a particular field returns a value of the same type as was in the field, we can assume, if that buys us anything.

例如:

data Foo = Foo {
  v1 :: Int
, v2 :: Double
}

data FooFuns = FooFuns {
  v1 :: [Int] -> Int
, v2 :: [Double] -> Double
}

所以现在的目标是自动构建例如

So the goal now is to automatically construct e.g.

result = Foo {
  v1 = (v1 FooFuns) (v1 <$> listOfFoos)
, v2 = (v2 FooFuns) (v2 <$> listOfFoos)
}

当前,我将函数包装为newtype值列表(因此,

Currently I'm wrapping up the function on a list of values as a newtype (so it can be used by Higgledy's HKD) and a GADT for the Traversable constraint, but this latter part may be unnecessary, or perhaps better modeled as a typeclass:

data TraversableFun a t where
  TraversableFun :: Traversable t => (t a -> a) -> TraversableFun t a
newtype ListFun a = ListFun {unTravFun :: TraversableFun [] a}

type RecSummaryFuns a = HKD a ListFun

现在RecSummaryFuns a应该具有与a相同的字段名称"(构造函数参数).理想情况下,应该有一种方法可以轻松地将sFuns应用于下面的recs以获得单个记录.

Now RecSummaryFuns a should have the same "field names" (constructor arguments) as a. Ideally there would be a way to easily apply sFuns to recs below to get a single record out.

applyStatFuns :: Traversable t => RecSummaryFuns r -> t r -> r
applyStatFuns sFuns recs = ???

我也很好奇这是否是对情况进行建模的最佳方法:基本上,我正在将摘要统计信息应用于记录中保存的值,但是我需要一种方法来封装每种记录类型的摘要统计信息.

I'm also curious if this is the best way to model the situation: basically I'm applying summary statistics to values held in records, but I need a way to encapsulate those summary statistics for each type of record.

推荐答案

现在RecSummaryFuns a应该具有相同的字段名称"(构造函数 参数)作为

Now RecSummaryFuns a should have the same "field names" (constructor arguments) as a

此答案使用 red-black-record 构造通用记录" ,其字段名称与原始Foo记录相同.首先,我们必须自动派生一些

This answer uses red-black-record to construct "generalized records" that have the same field names as the original Foo record. First we must auto-derive some supporting typeclasses:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-} -- hide some scary types

import Data.RBR (FromRecord (..), Record, ToRecord (..), fromNP, insert, toNP, unit)
import Data.SOP (I (I), NP) -- from sop-core
import Data.SOP.NP (liftA2_NP, liftA_NP) --   useful functions for n-ary products
import GHC.Generics

data Foo
  = Foo
      { v1 :: Int,
        v2 :: Double
      }
  deriving (Show, Generic, FromRecord, ToRecord)

现在,我们可以定义通用记录的值,该记录的字段将包含函数.可悲的是,我们不能采用通常的记录语法:

Now we can define a value of our generalized record, whose fields will hold functions. Sadly, we can't employ the usual record syntax:

newtype Func a = Func ([a] -> a) -- helper newtype encapsulating the function

type FooFunc = Record Func (RecordCode Foo) -- wrap every field in Func

exampleFunc :: FooFunc
exampleFunc =
      insert @"v1" (Func head) -- field names give with TypeApplications
    . insert @"v2" (Func last) -- same order as in the original record
    $ unit -- unit is the empty record

下一步是在流行核心:

The next step is defining this generic apply function with the help of the n-ary product datatype provided by sop-core:

applyFunc ::  _ => Record Func _ -> [r] -> r
applyFunc func foos =
  let foos_NP :: [NP I _] -- a list of n-ary products. I is an identity functor
      foos_NP = toNP . toRecord <$> foos
      listfoos_NP :: [NP [] _] -- turn every component into a singleton list
      listfoos_NP = liftA_NP (\(I x) -> [x]) <$> foos_NP
      listfoo_NP :: NP [] _ -- a single n-ary product where each component is a list
      listfoo_NP = mconcat listfoos_NP
      func_NP :: NP Func _ -- turn the function record into a n-ary prod
      func_NP = toNP func
      resultFoo_NP_I :: NP I _ -- apply the functions to each list component
      resultFoo_NP_I = liftA2_NP (\(Func f) vs -> I (f vs)) func_NP listfoo_NP
   in fromRecord . fromNP $ resultFoo_NP_I -- go back to the nominal record Foo

将它们放在一起:

main :: IO ()
main =
  print $
    applyFunc exampleFunc [Foo 0 0.0, Foo 1 1.0]
-- result: Foo {v1 = 0, v2 = 1.0}

此解决方案的可能缺点是编译时间较长,而且将Foo的列表转换为applyFunc内部的Foo -with-list-fields的事实对于长列表而言可能效率不高.

Possible disadvantages of this solution are longer compilation times, and also the fact that turning the list-of-Foos into a Foo-with-list-fields inside applyFunc might be inefficient for long lists.

我们可以放弃 red-black-record -我们仅在使用它可以将字段名称保留在通用记录中,并依靠 sop-core /<直接直接访问href ="https://hackage.haskell.org/package/generics-sop" rel ="nofollow noreferrer"> generics-sop ;在这种情况下,字段名称的处理方式将有所不同,或者我们可以仅依靠位置匹配.

We could ditch red-black-record—we are only using it to preserve field names in the generalized records—and rely on sop-core / generics-sop directly; in that case field names would be handled differently—or we could simply rely on positional matching.

这篇关于给定功能记录和功能所作用类型的数据记录,如何通用地应用功能记录?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆