为给定的数据类型编写在镜头上具有多态性的函数?

如何解决为给定的数据类型编写在镜头上具有多态性的函数?

不确定我是否在标题中正确地表达了问题,但我正在尝试执行以下操作:

{-# 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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-