首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在两个不同的记录上使用相同的镜头?(或者,将字段名记录为头等值?)

如何在两个不同的记录上使用相同的镜头?(或者,将字段名记录为头等值?)
EN

Stack Overflow用户
提问于 2017-08-30 10:48:39
回答 2查看 300关注 0票数 0

考虑以下记录及其镜头:

代码语言:javascript
复制
data Bar = Bar {barField1 :: Int, barField2 :: String}
makeLensesWith abbreviatedFields ''Bar

data BarError = BarError {barerrField1 :: [String], barerrField2 :: [String]}
makeLensesWith abbreviatedFields ''BarError

现在,通过实现field1HasField2类型类,它们都可以访问镜片field2HasField1。但是,我无法编译以下代码:

代码语言:javascript
复制
-- Most-general type-signature inferred by the compiler, if I remove the
-- KindSignatures from `record` & `errRecord` below:
--
-- validateLength :: (IsString a) => (Int, Int) -> ALens t t [a] [a] -> t -> t -> t
-- 
validateLength (mn, mx) l (record :: Bar)  (errRecord :: BarErr) =
  let len = length (record ^# l)
  in if ((len<mn) || (len>mx))
  then errRecord & l #%~ (\x -> ("incorrect length"):x)
  else errRecord

-- Usage scenario:
--
-- let x = Bar 10 "hello there"
--     xErr = BarError [] []
-- in validateLength (3, 10) field2 x xErr

错误消息:

代码语言:javascript
复制
/Users/saurabhnanda/projects/vl-haskell/src/TryLens.hs:18:20: error:
    • Couldn't match type ‘BarError’ with ‘Bar’
      Expected type: BarError -> BarError
        Actual type: Bar -> BarError
    • In the second argument of ‘(&)’, namely
        ‘l #%~ (\ x -> ("incorrect length") : x)’
      In the expression:
        errRecord & l #%~ (\ x -> ("incorrect length") : x)
      In the expression:
        if ((len < mn) || (len > mx)) then
            errRecord & l #%~ (\ x -> ("incorrect length") : x)
        else
            errRecord

注释:,而不是^.%~,我使用^##%~,因为我想要)同时作为一个采摘者和策划者。

编辑:演示问题的一个更简单的片段是:

代码语言:javascript
复制
-- intended type signature:
-- funkyLensAccess :: l -> r1 -> r2 -> (t1, t2)
--
-- type signature inferred by the compiler
-- funkyLensAccess :: Getting t s t -> s -> s -> (t, t)
--
funkyLensAccess l rec1 rec2 = (rec1 ^. l, rec2 ^. l)
EN

回答 2

Stack Overflow用户

发布于 2017-08-30 12:59:11

所以从本质上说,你的问题与镜头无关,而是与(访问器-)函数有关,它可以在不同类型上操作,因为每个函数都给出了不同类型的结果。

这立即意味着麻烦:如果访问的字段类型应该依赖于包含-struct类型,则这是一个相依型。Haskell不是一种依赖类型的语言。在Python中,这是一种很容易完成的任务,方法是按名称调用字段(以字符串的形式),然后通过鸭型对字段进行操作,但是Haskell出于非常好的理由在运行时擦除诸如记录标签字符串之类的昂贵信息,当然编译器需要知道所有类型,这样就不能在运行时进行鸭推断。从这个意义上说,你所要求的根本不可能。

还是真的是这样?实际上,GHC在依赖类型方面已经变得相当出色。很长一段时间以来,将非类型特定的标签处理为类型级别的字符串值(称为Symbols )已经成为可能,而且最近出现了允许访问任何记录的字段的工作,与Python非常类似,但都是在编译时使用字段中包含的任何类型。

最重要的是,您需要表示类型级别的函数,将记录标签和记录类型映射到包含元素的类型。这是由班级表示的。

代码语言:javascript
复制
{-# LANGUAGE DataKinds, KindSignatures, FlexibleInstances, FlexibleContexts, FunctionalDependencies, ScopedTypeVariables, UnicodeSyntax, TypeApplications, AllowAmbiguousTypes #-}

import GHC.Records
import GHC.TypeLits (Symbol)

data Bar = Bar {barField1 :: Int, barField2 :: String}

data BarError = BarError {barerrField1 :: [String], barerrField2 :: [String]}
 deriving (Show)

type LensOn s a = (a, a -> s)  -- poor man's lens focus

instance HasField "Field2" Bar (LensOn Bar String) where
  getField (Bar i s) = (s, \s' -> Bar i s')

instance HasField "Field2" BarError (LensOn BarError [String]) where
  getField (BarError f₁ f₂) = (f₂, \f₂' -> BarError f₁ f₂')

validateLength :: ∀ (f :: Symbol)
                      . ( HasField f Bar (LensOn Bar String)
                        , HasField f BarError (LensOn BarError [String]) )
    => (Int,Int) -> Bar -> BarError -> BarError
validateLength (mn,mx) record errRecord
    = let len = length . fst $ getField @f record
      in if len < mn || len > mx
          then case getField @f errRecord of
                 (oldRec, setRec) -> setRec $ "incorrect length" : oldRec
          else errRecord

main :: IO ()
main = let x = Bar 10 "hello there"
           xErr = BarError [] []
       in print $ validateLength @"Field2" (3,10) x xErr

使用GHC-8.3.20170711进行测试,可能无法使用更早的版本。

票数 3
EN

Stack Overflow用户

发布于 2017-08-30 16:25:00

如果您希望一个作为参数传递的值在两种不同类型下操作,则需要Rank2Types (或等效的RankNTypes)扩展。

然后,由于第2级或更高级别的类型永远不会在GHC中推断,所以您需要显式地写入类型签名。

我们的第一遍可能类似于:IsString a => (Int, Int) -> (forall s a. Lens' s a) -> Bar -> BarError -> BarError,但是,对于第二个论点来说,这太笼统了,所以我倾向于怀疑这种类型的非底部值是否存在。我们当然不能在那里通过field1field2

因为我们想传递field1field2,所以我们需要统一它们类型的东西:HasField1 s a => Lens' s aHasField2 s a => Lens' s a。不幸的是,由于HasField1HasField2没有共享(或有)任何超类,唯一将这些类与最后一段中给出的类型相统一的类型。

请注意,即使HasField1HasField2共享一个超类,我们仍然无法完成。您的实现还要求Bar中的字段是FoldableBarError中的字段是IsString的列表。表达这些约束是可能的,但并不完全是对用户友好的。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45958455

复制
相关文章

相似问题

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