镜头的用途/用途是什么? [英] What are lenses used/useful for?

查看:70
本文介绍了镜头的用途/用途是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我似乎找不到任何关于在实际示例中使用什么镜头的解释.来自 Hackage 页面的这个短段落是我找到的最接近的:

I can't seem to find any explanation of what lenses are used for in practical examples. This short paragraph from the Hackage page is the closest I've found:

该模块提供了一种访问和更新结构元素的便捷方式.它与 Data.Accessors 非常相似,但更通用一点,依赖项更少.我特别喜欢它在状态 monad 中处理嵌套结构的干净程度.

This modules provides a convienient way to access and update the elements of a structure. It is very similar to Data.Accessors, but a bit more generic and has fewer dependencies. I particularly like how cleanly it handles nested structures in state monads.

那么,它们有什么用?与其他方法相比,它们有什么优点和缺点?为什么需要它们?

So, what are they used for? What benefits and disadvantages do they have over other methods? Why are they needed?

推荐答案

它们提供了对数据更新的清晰抽象,并且从来没有真正需要".它们只是让您以不同的方式推理问题.

They offer a clean abstraction over data updates, and are never really "needed." They just let you reason about a problem in a different way.

在某些命令式/面向对象"编程语言(如 C)中,您熟悉一些值集合的概念(我们称它们为结构")以及标记集合中每个值的方法(标签通常称为领域").这导致了这样的定义:

In some imperative/"object-oriented" programming languages like C, you have the familiar concept of some collection of values (let's call them "structs") and ways to label each value in the collection (the labels are typically called "fields"). This leads to a definition like this:

typedef struct { /* defining a new struct type */
  float x; /* field */
  float y; /* field */
} Vec2;

typedef struct {
  Vec2 col1; /* nested structs */
  Vec2 col2;
} Mat2;

然后您可以像这样创建这个新定义的类型的值:

You can then create values of this newly defined type like so:

Vec2 vec = { 2.0f, 3.0f };
/* Reading the components of vec */
float foo = vec.x;
/* Writing to the components of vec */
vec.y = foo;

Mat2 mat = { vec, vec };
/* Changing a nested field in the matrix */
mat.col2.x = 4.0f;

类似在 Haskell 中,我们有数据类型:

Similarly in Haskell, we have data types:

data Vec2 =
  Vec2
  { vecX :: Float
  , vecY :: Float
  }

data Mat2 =
  Mat2
  { matCol1 :: Vec2
  , matCol2 :: Vec2
  }

然后像这样使用这种数据类型:

This data type is then used like this:

let vec  = Vec2 2 3
    -- Reading the components of vec
    foo  = vecX vec
    -- Creating a new vector with some component changed.
    vec2 = vec { vecY = foo }

    mat = Mat2 vec2 vec2

然而,在 Haskell 中,没有简单的方法可以更改数据结构中的嵌套字段.这是因为您需要围绕要更改的值重新创建所有包装对象,因为 Haskell 值是不可变的.如果你在 Haskell 中有一个像上面这样的矩阵,并且想改变矩阵中右上角的单元格,你必须这样写:

However, in Haskell, there's no easy way of changing nested fields in a data structure. This is because you need to re-create all of the wrapping objects around the value that you are changing, because Haskell values are immutable. If you have a matrix like the above in Haskell, and want to change the upper right cell in the matrix, you have to write this:

    mat2 = mat { matCol2 = (matCol2 mat) { vecX = 4 } }

它可以工作,但看起来很笨拙.所以,有人想出的,基本上是这样的:如果你将两件事组合在一起:一个值的getter"(如上面的 vecXmatCol2)与相应的函数,给定 getter 所属的数据结构,可以创建一个更改该值的新数据结构,您可以做很多巧妙的事情.例如:

It works, but it looks clumsy. So, what someone came up with, is basically this: If you group two things together: the "getter" of a value (like vecX and matCol2 above) with a corresponding function that, given the data structure that the getter belongs to, can create a new data structure with that value changed, you are able to do a lot of neat stuff. For example:

data Data = Data { member :: Int }

-- The "getter" of the member variable
getMember :: Data -> Int
getMember d = member d

-- The "setter" or more accurately "updater" of the member variable
setMember :: Data -> Int -> Data
setMember d m = d { member = m }

memberLens :: (Data -> Int, Data -> Int -> Data)
memberLens = (getMember, setMember)

实现镜头的方式有很多种;对于本文,我们假设一个镜头如上所示:

There are many ways of implementing lenses; for this text, let's say that a lens is like the above:

type Lens a b = (a -> b, a -> b -> a)

即它是某种类型 a 的 getter 和 setter 的组合,它有一个 b 类型的字段,所以上面的 memberLens 将是一个 <代码>镜头数据输入.这让我们做什么?

I.e. it is the combination of a getter and a setter for some type a which has a field of type b, so memberLens above would be a Lens Data Int. What does this let us do?

好吧,让我们首先创建两个简单的函数来从镜头中提取 getter 和 setter:

Well, let's first make two simple functions that extract the getters and setters from a lens:

getL :: Lens a b -> a -> b
getL (getter, setter) = getter

setL :: Lens a b -> a -> b -> a
setL (getter, setter) = setter

现在,我们可以开始对东西进行抽象了.让我们再次考虑上面的情况,我们要修改两层楼深"的值.我们用另一个镜头添加数据结构:

Now, we can start abstracting over stuff. Let's take the situation above again, that we want to modify a value "two stories deep." We add a data structure with another lens:

data Foo = Foo { subData :: Data }

subDataLens :: Lens Foo Data
subDataLens = (subData, \ f s -> f { subData = s }) -- short lens definition

现在,让我们添加一个由两个镜头组成的函数:

Now, let's add a function that composes two lenses:

(#) :: Lens a b -> Lens b c -> Lens a c
(#) (getter1, setter1) (getter2, setter2) =
    (getter2 . getter1, combinedSetter)
    where
      combinedSetter a x =
        let oldInner = getter1 a
            newInner = setter2 oldInner x
        in setter1 a newInner

代码写得有点快,但我认为它的作用很清楚:getter 很简单;您获得内部数据值,然后读取其字段.当 setter 应该用 x 的新内部字段值更改某个值 a 时,首先检索旧的内部数据结构,设置其内部字段,然后用新的内部数据结构更新外部数据结构.

The code is kind of quickly written, but I think it's clear what it does: the getters are simply composed; you get the inner data value, and then you read its field. The setter, when it is supposed to alter some value a with the new inner field value of x, first retrieves the old inner data structure, sets its inner field, and then updates the outer data structure with the new inner data structure.

现在,让我们创建一个简单地增加镜头值的函数:

Now, let's make a function that simply increments the value of a lens:

increment :: Lens a Int -> a -> a
increment l a = setL l a (getL l a + 1)

如果我们有这个代码,它的作用就很清楚了:

If we have this code, it becomes clear what it does:

d = Data 3
print $ increment memberLens d -- Prints "Data 4", the inner field is updated.

现在,因为我们可以组合镜头,所以我们也可以这样做:

Now, because we can compose lenses, we can also do this:

f = Foo (Data 5)
print $ increment (subDataLens#memberLens) f
-- Prints "Foo (Data 6)", the innermost field is updated.

所有镜头包装所做的本质上都是将镜头的这一概念——setter"和getter"的分组,整合到一个简洁的包装中,使它们易于使用.在特定的镜头实现中,可以这样写:

What all of the lens packages do is essentially to wrap this concept of lenses - the grouping of a "setter" and a "getter," into a neat package that makes them easy to use. In a particular lens implementation, one would be able to write:

with (Foo (Data 5)) $ do
  subDataLens . memberLens $= 7

因此,您非常接近代码的 C 版本;修改数据结构树中的嵌套值变得非常容易.

So, you get very close to the C version of the code; it becomes very easy to modify nested values in a tree of data structures.

镜头无非是:一种修改部分数据的简单方法.因为有了它们,对某些概念进行推理变得非常容易,因此它们在拥有大量数据结构且必须以各种方式相互交互的情况下得到广泛应用.

Lenses are nothing more than this: an easy way of modifying parts of some data. Because it becomes so much easier to reason about certain concepts because of them, they see a wide use in situations where you have huge sets of data structures that have to interact with one another in various ways.

有关镜头的优缺点,请参阅最近问题在这里.

For the pros and cons of lenses, see a recent question here on SO.

这篇关于镜头的用途/用途是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆