我最近在Hackage上发现了镜头包,并且现在正试图在一个小的测试项目中使用它,如果我继续工作,它可能会在一个非常遥远的日子变成MUD/MUSH服务器.
这是我的代码的最小化版本,说明我现在遇到的问题,用于访问键/值容器的at镜头(在我的情况下是Data.Map.Strict)
{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, TemplateHaskell #-}
module World where
import Control.Applicative ((<$>),(<*>), pure)
import Control.Lens
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as DM
import Data.Maybe
import Data.UUID
import Data.Text (Text)
import qualified Data.Text as T
import System.Random (Random, randomIO)
newtype RoomId = RoomId UUID deriving (Eq, Ord, Show, Read, Random)
newtype PlayerId = PlayerId UUID deriving (Eq, Ord, Show, Read, Random)
data Room =
Room { _roomId :: RoomId
, _roomName :: Text
, _roomDescription :: Text
, …
Run Code Online (Sandbox Code Playgroud) 使用镜头库我可以将修改功能应用于各个目标,如下所示:
Prelude Control.Lens> (1, 'a', 2) & _1 %~ (*3)
(3,'a',2)
Prelude Control.Lens> (1, 'a', 2) & _3 %~ (*3)
(1,'a',6)
Run Code Online (Sandbox Code Playgroud)
如何组合这些单独的镜头(_1
和_3
),以便能够同时对两个目标执行此更新?我期待以下精神:
Prelude Control.Lens> (1, 'a', 2) & ??? %~ (*3)
(3,'a',6)
Run Code Online (Sandbox Code Playgroud) Many types of optics have a van Laarhoven representation.
For example, a Lens
of type Lens s t a b
can be represented as:
Functor f => (a -> f b) -> s -> f t
Run Code Online (Sandbox Code Playgroud)
Similarly a Traversal
, can be represented in a similar way, swapping the Functor
constraint for Applicative
:
Applicative f => (a -> f b) -> s -> f t
Run Code Online (Sandbox Code Playgroud)
几种光学框架(例如Monocle和Arrow)定义了一种类型Optional
。
在《单片眼镜光学》中,层次结构 Optional
介于Lens
和之间Traversal
根据我的理解:如果 …
我有一个不可变的数据结构,我在地图中有嵌套值,如下所示:
case class TradingDay(syms: Map[String, SymDay] = Map.empty)
case class SymDay(sym: String, traders: Map[String, TraderSymDay] = Map.empty)
case class TraderSymDay(trader: String, sym: String, trades: List[Trade] = Nil)
Run Code Online (Sandbox Code Playgroud)
另外,我有一天中所有交易的清单,我想生成TradingDay
结构,在哪里
case class Trade(sym: String, trader: String, qty: Int)
Run Code Online (Sandbox Code Playgroud)
我想通过折叠我的交易来弄清楚如何用镜头更新这个结构(见附录):
(TradingDay() /: trades) { (trd, d) =>
def sym = trd.sym
def trader = trd.trader
import TradingDay._
import SymDay._
import TraderSymDay._
val mod =
for {
_ <- (Syms member sym).mods(
_ orElse some(SymDay(sym)))
_ <- (Syms at sym andThen Traders …
Run Code Online (Sandbox Code Playgroud) 我正在编写一个Haskell程序,它涉及模拟一个抽象机器,它具有内部状态,接受输入并提供输出.我知道如何使用状态monad实现这一点,从而产生更清晰,更易于管理的代码.
我的问题是,当我有两个(或更多)有状态对象相互交互时,我不知道如何使用相同的技巧.下面我给出了一个高度简化的问题版本,并勾勒出我到目前为止的内容.
为了这个问题,让我们假设一个机器的内部状态只包含一个整数寄存器,因此它的数据类型是
data Machine = Register Int
deriving (Show)
Run Code Online (Sandbox Code Playgroud)
(实际的机器可能有多个寄存器,程序指针,调用堆栈等等,但现在不要担心.)在上一个问题之后我知道如何使用状态monad实现机器,所以我不必显式传递其内部状态.在这个简化的示例中,导入后实现如下所示Control.Monad.State.Lazy
:
addToState :: Int -> State Machine ()
addToState i = do
(Register x) <- get
put $ Register (x + i)
getValue :: State Machine Int
getValue = do
(Register i) <- get
return i
Run Code Online (Sandbox Code Playgroud)
这让我可以写出类似的东西
program :: State Machine Int
program = do
addToState 6
addToState (-4)
getValue
runProgram = evalState program (Register 0)
Run Code Online (Sandbox Code Playgroud)
这会将6添加到寄存器,然后减去4,然后返回结果.状态monad跟踪机器的内部状态,以便"程序"代码不必明确跟踪它.
在命令式语言的面向对象样式中,这个"程序"代码可能看起来像
def runProgram(machine):
machine.addToState(6)
machine.addToState(-4)
return machine.getValue()
Run Code Online (Sandbox Code Playgroud)
在这种情况下,如果我想模拟两台彼此交互的机器,我可能会写
def …
Run Code Online (Sandbox Code Playgroud) 我用镜头包编码.一切都很顺利,直到我试图访问代数类型的某个字段:
import Control.Lens
data Type = A { _a :: Char } | B
makeLenses ''Type
test1 = _a (A 'a')
test2 = (A 'a') ^. a
No instance for (Data.Monoid.Monoid Char)
arising from a use of `a'
Possible fix:
add an instance declaration for (Data.Monoid.Monoid Char)
In the second argument of `(^.)', namely `a'
In the expression: (A 'a') ^. a
In an equation for `test2': test2 = (A 'a') ^. a
Run Code Online (Sandbox Code Playgroud)
我可以选择_a,但我的真实程序中的数据类型更深,我打算使用镜头来降低我必须做的工作量.我一直在查看镜头库,但那里有很多,我不确定他是否处理过这种情况,或者它只是镜头库不支持的东西.
作为旁注,如果我实际上使用类似String的monoid作为数据类型而不是Char,那么它会编译并给出正确的答案,我不知道为什么.
编辑:在阅读了hammar的评论后,我尝试了这个,这有效:
test2 = (A 'a') …
Run Code Online (Sandbox Code Playgroud) 我有一些数据类型
data Outer = Outer { _list :: [ Inner ] }
data Inner = Inner { _bool :: Bool }
Run Code Online (Sandbox Code Playgroud)
使用Control.Lens,我可以访问第i个内部的_bool(在'State Outer'monad中)
boolValue <- gets (^. list . to (!! i) . inner)
Run Code Online (Sandbox Code Playgroud)
我也想用类似的东西来更新这个值
list ^. (to (!! i)) ^. inner %= True
Run Code Online (Sandbox Code Playgroud)
然而(根据我的理解),'to'功能只会创建一个getter,而不是一个可用作getter或setter的真实镜头.
那么,我怎样才能将(!! i)转换为可以让我更新此字段的镜头?
类似于这个案例类问题,但有一个扭曲:
我有一个case类,它有一些深层嵌套的case类作为属性.举个简单的例子,
case class Foo(fooPropA:Option[String], fooPropB:Option[Int])
case class Bar(barPropA:String, barPropB:Int)
case class FooBar(name:Option[String], foo:Foo, optionFoo: Option[Foo], bar:Option[Bar])
Run Code Online (Sandbox Code Playgroud)
我想将两个FooBar案例类合并在一起,获取输入存在的值并将它们应用于现有实例,从而生成更新版本:
val fb1 = FooBar(Some("one"), Foo(Some("propA"), None), Some(Foo(Some("propA"), Some(3))), Some(Bar("propA", 4)))
val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), Some(Foo(Some("baz"), None)), None)
val merged = fb1.merge(fb2)
//merged = FooBar(Some("one"), Foo(Some("updated"), Some(2)), Some(Foo(Some("baz"), Some(3))), Some(Bar("propA", 4)))
Run Code Online (Sandbox Code Playgroud)
我知道我可以使用镜头来构建深层嵌套的属性更新; 但是,我觉得这需要大量的锅炉板代码:我需要一个镜头用于每个属性,而另一个组合镜头在父类中.这似乎是一个很多维护,即使使用的更简洁的镜头创作方法不成形.
棘手的部分是optionFoo元素:在这种情况下,两个元素都存在Some(value).但是,我想合并内部选项属性,而不仅仅是用fb2的新值覆盖fb1.
我想知道是否有一种很好的方法可以将这两个值合并在一起,只需要最少的代码.我的直觉告诉我尝试使用unapply
case类上的方法返回一个元组,迭代并将元组组合成一个新的元组,然后将元组应用回case类.
有没有更有效的方法来做这件事?
我最近一直在玩镜头,发现它们非常适合它们的预期用途 - 挖掘复杂的数据结构.但我最欣赏他们的一个领域是数据库访问(特别是sqlite,但我认为我的问题推广到大多数数据库),但我看不出任何方法来编写不会牺牲很多钱的镜头性能或粒度.
如果我从DB到表,从表到行,从行到列编写镜头(或者我认为可能是Prism,根据NULLable字段?),那么每个步骤都会导致数据库访问,意味着应该是一次访问至少4.
在另一方面,如果我的目标是地图DB访问1:1与透镜/棱镜的用途,我得到大做,一切镜头不能被分解成更小的碎片时,我不希望只看到是什么列在表格中,等等.
使用具有DB的镜头是否有意义,如果是这样,我错过了一种明显的方法来避免重复工作以避免不必要的DB访问?
"透镜"和"部分透镜"在名称和概念上看起来相似.他们有什么不同?在什么情况下我需要使用其中一个?
标记Scala和Haskell,但我欢迎与任何具有镜头库的功能语言相关的解释.