考虑以下记录及其镜头:
data Bar = Bar {barField1 :: Int, barField2 :: String}
makeLensesWith abbreviatedFields ''Bar
data BarError = BarError {barerrField1 :: [String], barerrField2 :: [String]}
makeLensesWith abbreviatedFields ''BarError现在,通过实现field1和HasField2类型类,它们都可以访问镜片field2和HasField1。但是,我无法编译以下代码:
-- 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错误消息:
/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注释:,而不是^.和%~,我使用^#和#%~,因为我想要)同时作为一个采摘者和策划者。
编辑:演示问题的一个更简单的片段是:
-- 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)发布于 2017-08-30 12:59:11
所以从本质上说,你的问题与镜头无关,而是与(访问器-)函数有关,它可以在不同类型上操作,因为每个函数都给出了不同类型的结果。
这立即意味着麻烦:如果访问的字段类型应该依赖于包含-struct类型,则这是一个相依型。Haskell不是一种依赖类型的语言。在Python中,这是一种很容易完成的任务,方法是按名称调用字段(以字符串的形式),然后通过鸭型对字段进行操作,但是Haskell出于非常好的理由在运行时擦除诸如记录标签字符串之类的昂贵信息,当然编译器需要知道所有类型,这样就不能在运行时进行鸭推断。从这个意义上说,你所要求的根本不可能。
还是真的是这样?实际上,GHC在依赖类型方面已经变得相当出色。很长一段时间以来,将非类型特定的标签处理为类型级别的字符串值(称为Symbols )已经成为可能,而且最近出现了允许访问任何记录的字段的工作,与Python非常类似,但都是在编译时使用字段中包含的任何类型。
最重要的是,您需要表示类型级别的函数,将记录标签和记录类型映射到包含元素的类型。这是由班级表示的。
{-# 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进行测试,可能无法使用更早的版本。
发布于 2017-08-30 16:25:00
如果您希望一个作为参数传递的值在两种不同类型下操作,则需要Rank2Types (或等效的RankNTypes)扩展。
然后,由于第2级或更高级别的类型永远不会在GHC中推断,所以您需要显式地写入类型签名。
我们的第一遍可能类似于:IsString a => (Int, Int) -> (forall s a. Lens' s a) -> Bar -> BarError -> BarError,但是,对于第二个论点来说,这太笼统了,所以我倾向于怀疑这种类型的非底部值是否存在。我们当然不能在那里通过field1或field2。
因为我们想传递field1或field2,所以我们需要统一它们类型的东西:HasField1 s a => Lens' s a和HasField2 s a => Lens' s a。不幸的是,由于HasField1和HasField2没有共享(或有)任何超类,唯一将这些类与最后一段中给出的类型相统一的类型。
请注意,即使HasField1和HasField2共享一个超类,我们仍然无法完成。您的实现还要求Bar中的字段是Foldable,BarError中的字段是IsString的列表。表达这些约束是可能的,但并不完全是对用户友好的。
https://stackoverflow.com/questions/45958455
复制相似问题