如何解决为给定的数据类型编写在镜头上具有多态性的函数?
不确定我是否在标题中正确地表达了问题,但我正在尝试执行以下操作:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
module Lib where
import Control.Lens
data Foo = Foo {_bar1 :: Int,_bar2 :: String,_bar3 :: [Rational]} deriving (Show,Eq)
makeFieldsNoPrefix ''Foo
aFoo :: Foo
aFoo = Foo 33 "Hm?" [1/6,1/7,1/8]
stringToLens :: (HasBar1 s a,Functor f,HasBar2 s a,HasBar3 s a) => String -> Maybe ((a -> f a) -> s -> f s)
stringToLens str = case str of
"bar1" -> Just bar1
"bar2" -> Just bar2
"bar3" -> Just bar3
_ -> Nothing
updateFoo :: (HasBar1 a1 a2,HasBar2 a1 a2,HasBar3 a1 a2,Read a2) => String -> String -> a1 -> Maybe a1
updateFoo lensStr valStr myFoo = case stringToLens lensStr of
Just aLens -> Just $ set aLens (read valStr) myFoo
Nothing -> Nothing
newFoo :: Maybe Foo
newFoo = updateFoo "bar1" 22 aFoo
{--
Couldn't match type ‘[Char]’ with ‘Int’
arising from a functional dependency between:
constraint ‘HasBar2 Foo Int’ arising from a use of ‘updateFoo’
instance ‘HasBar2 Foo String’
at /home/gnumonic/Haskell/Test/test/src/Lib.hs:14:1-24
• In the expression: updateFoo "bar1" 22 aFoo
In an equation for ‘newFoo’: newFoo = updateFoo "bar1" 22 aFoo
--}
(忽略这里的read用法,我在我正在研究的实际模块中以“正确的方式”进行操作。)
那显然是行不通的。我认为按照这种方式创建类型类可能行得通:
class OfFoo s a where
ofFoo :: s -> a
instance OfFoo Foo Int where
ofFoo foo = foo ^. bar1
instance OfFoo Foo String where
ofFoo foo = foo ^. bar2
instance OfFoo Foo [Rational] where
ofFoo foo = foo ^. bar3
但是,似乎没有一种方法可以将stringToLens函数实际上可用,即使在我尝试使用它之前,它都可以正常使用,从而无法将该类添加到约束中。 (尽管我什至没有类型检查是否使用makeLenses代替makeFields,而且我也不十分确定为什么。)
例如(为简单起见,可能已删除):
stringToLens :: (HasBar1 s a,HasBar3 s a,OfFoo s a) => String -> (a -> f a) -> s -> f s
stringToLens str = case str of
"bar1" -> bar1
"bar2" -> bar2
"bar3" -> bar3
那种类型检查,但几乎没有用,因为任何应用该功能的尝试都会引发功能依赖错误。
我还尝试使用Control.Lens.Reify中的Reified新类型,但这不能解决功能依赖性问题。
我不知道的是,如果我像这样修改updateFoo
:
updateFoo2 :: Read a => ASetter Foo Foo a a -> String -> Foo -> Foo
updateFoo2 aLens val myFoo = set aLens (read val) myFoo
然后这有效:
testFunc :: Foo
testFunc = updateFoo2 bar1 "22" aFoo
但是无论何时使用,都会在myLens1
上引发功能依赖错误(尽管定义类型检查):
testFunc' :: Foo
testFunc' = updateFoo2 (stringToLens "bar1") 22 aFoo -- Error on (stringToLens "bar1")
myLens1 :: (HasBar1 s a,OfFoo s a) => (a -> f a) -> s -> f s
myLens1 = stringToLens "bar1" -- typechecks
testFunc2 :: Foo
testFunc2 = updateFoo2 myLens1 "22" aFoo -- Error on myLens1
所以我可以定义一个stringToLens函数,但这几乎没用...
不幸的是,我假设这样的东西可以正常工作,所以写了很多代码。我正在编写一个数据包生成器,如果可以使它正常工作,那么我就有一种非常便捷的方法来快速添加对新协议的支持。 (我的其余代码在各种目的中广泛使用镜头。)我可以想到一些解决方法,但它们都非常冗长,并且需要大量模板Haskell(为每个新协议生成每个函数的副本)数据类型)或大量样板代码(例如,在read
函数中创建虚拟类型来为updateFoo
发出正确的类型信号)。
有没有办法用镜头来做我要在这里做的事情,或者如果没有类似强制性的类型,这是不可能的吗?如果没有,那么有没有比我看到的更好的解决方法?
在这一点上,我最好的猜测是编译器没有足够的信息来推断值字符串的类型,而没有充分评估透镜。
但是,似乎在这方面似乎应该是可行的,因为当stringToLens的输出传递给updateFoo时,它将具有确定的(正确的)类型。所以我很困惑。
解决方法
实现stringToLens
需要一些类似的依赖类型,因为结果Lens
的 type 取决于参数的 value :字段名。 Haskell没有完全依赖类型,尽管它们可能{或{3}}或多或少都有困难。
在updateFoo
中,将字段名称(lensStr
)和字段值的“序列化”形式(valStr
)都作为参数,并返回一些更新函数数据类型。我们可以在没有依赖的情况下拥有它吗?
想象一下,对于某个特定类型Foo
,您有一个类似Map FieldName (String -> Maybe (Foo -> Foo))
的东西。对于每个字段名称,您将具有一个用于解析字段值的函数,如果成功,该函数将返回Foo
的更新函数。不需要依赖类型,因为每个字段值的解析都将隐藏在具有统一签名的函数后面。
如何为给定类型构建此类解析器返回更新器?您可以手动构建它,也可以在某些emulated generics的帮助下派生它。
这是一个基于wizardry库的可能实现(尽管最好将它建立在更成熟的red-black-record上)。一些初步进口:
{-# LANGUAGE DeriveGeneric,FlexibleContexts,FlexibleInstances,#-}
{-# LANGUAGE TypeApplications,TypeFamilies,TypeOperators,ScopedTypeVariables #-}
import qualified Data.Map.Strict as Map
import Data.Map.Strict
import Data.Monoid (Endo (..))
import Data.Proxy
import Data.RBR
( (:.:) (Comp),And,Case (..),FromRecord (fromRecord),I (..),IsRecordType,K (..),KeyValueConstraints,KeysValuesAll,Maplike,Record,ToRecord (toRecord),collapse'_Record,cpure'_Record,injections_Record,liftA2_Record,unI,)
import GHC.Generics (Generic)
import GHC.TypeLits
实现本身:
type FieldName = String
type TextInput = String
makeUpdaters ::
forall r c.
( IsRecordType r c,-- Is r convertible to the rep used by red-black-record?
Maplike c,-- Required for certain applicative-like operations over the rep.
KeysValuesAll (KeyValueConstraints KnownSymbol Read) c -- Are all fields readable?
) =>
Proxy r ->
Map FieldName (TextInput -> Maybe (r -> r))
makeUpdaters _ =
let parserForField :: forall v. Read v
=> FieldName -> ((,) FieldName :.: (->) TextInput :.: Maybe) v
parserForField fieldName = Comp (fieldName,Comp read)
parserRecord = cpure'_Record (Proxy @Read) parserForField
injectParseResult ::
forall c a.
Case I (Endo (Record I c)) a -> -- injection into the record
((,) FieldName :.: (->) TextInput :.: Maybe) a -> -- parsing function
(FieldName,Case I (Maybe (Endo (Record I c))) TextInput)
injectParseResult (Case makeUpdater) (Comp (fieldName,Comp readFunc)) =
( fieldName,( Case $ \textInput ->
let parsedFieldValue = readFunc . unI $ textInput
in case parsedFieldValue of
Just x -> Just $ makeUpdater . pure $ x
Nothing -> Nothing ) )
collapsed :: [(FieldName,Case I (Maybe (Endo (Record I c))) TextInput)]
collapsed = collapse'_Record $
liftA2_Record
(\injection parser -> K [injectParseResult injection parser])
injections_Record
parserRecord
toFunction :: Case I (Maybe (Endo (Record I c))) TextInput
-> TextInput -> Maybe (r -> r)
toFunction (Case f) textInput = case f $ I textInput of
Just (Endo endo) -> Just $ fromRecord . endo . toRecord
Nothing -> Nothing
in toFunction <$> Map.fromList collapsed
用于测试的类型:
data Person = Person {name :: String,age :: Int} deriving (Generic,Show)
-- let updaters = makeUpdaters (Proxy @Person)
--
instance ToRecord Person
instance FromRecord Person
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。