什么是多态 lambda?

2024-01-04

lambda(匿名函数)的概念对我来说非常清楚。我知道类方面的多态性,运行时/动态分派用于根据实例的最派生类型调用适当的方法。但 lambda 到底如何才能是多态的呢?我是另一位试图了解更多有关函数式编程的 Java 程序员。


您会发现我在下面的答案中并没有过多谈论 lambda。请记住,在函数式语言中,任何函数都只是绑定到名称的 lambda,因此我所说的函数可以翻译为 lambda。

多态性

请注意,多态性实际上并不需要 OO 语言通过重写虚拟方法的派生类实现的那种“调度”。那只是一种特殊的多态性,子类型化 http://en.wikipedia.org/wiki/Polymorphism_(computer_science)#Subtyping.

多态性本身只是意味着函数不仅允许一种特定类型的参数,而且能够针对任何允许的类型。最简单的例子:你根本不关心类型,而只是传递传入的任何内容。或者,让它不quite如此琐碎,将其包装在单个元素容器中。例如,您可以用 C++ 实现这样的函数:

template<typename T> std::vector<T> wrap1elem( T val ) {
  return std::vector(val);
}

但你无法实现它作为 lambda,因为 C++ (撰写时间:C++11) 不支持多态 lambda。

无类型值

……至少不是这样。 C++ 模板以一种相当不寻常的方式实现多态性:编译器实际上为任何人传递给函数的每个类型(在它遇到的所有代码中)生成一个单态函数。这是必要的,因为 C++'值语义:当传入一个值时,编译器需要知道确切的类型(它在内存中的大小、可能的子节点等),以便复制它。

在大多数较新的语言中,几乎所有内容都只是参考到某个值,并且当您调用函数时,它不会获得参数对象的副本,而只是获得对已存在对象的引用。较旧的语言要求您将参数显式标记为引用/指针类型。

A big advantage of reference semantics is that polymorphism becomes much easier: pointers always have the same size, so the same machine code can deal with references to any type at all. That makes, very uglily1, a polymorphic container-wrapper possible even in C:

typedef struct{
  void** contents;
  int size;
} vector;

vector wrap1elem_by_voidptr(void* ptr) {
  vector v;
  v.contents = malloc(sizeof(&ptr));
  v.contents[0] = ptr;
  v.size = 1;
  return v;
}
#define wrap1elem(val) wrap1elem_by_voidptr(&(val))

Here, void*只是一个指向任何未知类型。由此产生的明显问题是:vector不知道它“包含”什么类型的元素!所以你不能真正用这些对象做任何有用的事情。除非你知道它是什么类型!

int sum_contents_int(vector v) {
  int acc = 0, i;
  for(i=0; i<v.size; ++i) {
    acc += * (int*) (v.contents[i]);
  }
  return acc;
}

显然,这是极其费力的。如果类型是 double 呢?如果我们想要怎么办product,不是总和?当然,我们可以手写每个案例。这不是一个好的解决方案。

如果我们有一个通用函数,我们会更好指示做什么作为额外的参数! C 有函数指针:

int accum_contents_int(vector v, void* (*combine)(int*, int)) {
  int acc = 0, i;
  for(i=0; i<v.size; ++i) {
    combine(&acc, * (int*) (v.contents[i]));
  }
  return acc;
}

然后可以像这样使用

void multon(int* acc, int x) {
  acc *= x;
}
int main() {
  int a = 3, b = 5;
  vector v = wrap2elems(a, b);
  printf("%i\n", accum_contents_int(v, multon));
}

Apart from still being cumbersome, all the above C code has one huge problem: it's completely unchecked if the container elements actually have the right type! The casts from *void will happily fire on any type, but in doubt the result will be complete garbage2.

类和继承

这个问题是 OO 语言解决的主要问题之一,它试图将您可能执行的所有操作与对象中的数据捆绑在一起,如下所示:methods。在编译类时,类型是单态的,因此编译器可以检查操作是否有意义。当您尝试使用这些值时,如果编译器知道如何找到这些值就足够了method。特别是,如果您创建派生类,编译器就会知道“啊哈,即使在派生对象上也可以从基类调用该方法”。

不幸的是,这意味着通过多态性实现的所有功能都相当于组合数据并简单地在单个字段上调用(单态)方法。实际上得到不同的行为(但是有控制地!)对于不同的类型,OO语言需要虚方法。这基本上意味着该类有额外的字段指向方法实现的指针,很像指向combine我在 C 示例中使用的函数 - 不同之处在于您只能通过添加派生类来实现重写方法,为此编译器再次知道所有数据字段等的类型,并且您是安全的。

复杂的类型系统,检查参数多态性

While inheritance-based polymorphism obviously works, I can't help saying it's just crazy stupid3 sure a bit limiting. If you want to use just one particular operation that happens to be not implemented as a class method, you need to make an entire derived class. Even if you just want to vary an operation in some way, you need to derive and override a slightly different version of the method.

Let's revisit our C code. On the face of it, we notice it should be perfectly possible to make it type-safe, without any method-bundling nonsense. We just need to make sure no type information is lost – not during compile-time, at least. Imagine (Read ∀T as "for all types T")

∀T: {
  typedef struct{
    T* contents;
    int size;
  } vector<T>;
}

∀T: {
  vector<T> wrap1elem(T* elem) {
    vector v;
    v.contents = malloc(sizeof(T*));
    v.contents[0] = &elem;
    v.size = 1;
    return v;
  }
}

∀T: {
  void accum_contents(vector<T> v, void* (*combine)(T*, const T*), T* acc) {
    int i;
    for(i=0; i<v.size; ++i) {
      combine(&acc, (*T) (v[i]));
    }
  }
}

观察如何,即使签名看起来很像这篇文章顶部的 C++ 模板(正如我所说,它实际上只是自动生成的单态代码),执行实际上几乎只是普通的 C。没有T那里的值,只是指向它们的指针。无需编译多个版本的代码:atruntime,不需要类型信息,我们只处理通用指针。编译时,我们确实知道类型并且可以使用函数头来确保它们匹配。即,如果你写了

void evil_sumon (int* acc, double* x) { acc += *x; }

并尝试去做

vector<float> v; char acc;
accum_contents(v, evil_sumon, acc);

编译器会抱怨,因为类型不匹配:在声明中accum_contents它说类型可能会有所不同,但所有出现的T确实需要解决the same type.

这正是参数多态性在 ML 系列语言以及 Haskell 中的工作原理:函数实际上对它们正在处理的多态数据一无所知。但他们被赋予了具有这些知识的专业操作员,作为参数.

在像 Java(lambda 之前)这样的语言中,参数多态性并没有给你带来太多好处:因为编译器故意让定义“只是一个简单的辅助函数”变得困难,而只支持类方法,所以你可以简单地使用派生函数- 立即下课。但在函数式语言中,定义小型辅助函数是可以想象到的最简单的事情:lambda!

所以你可以在 Haskell 中编写令人难以置信的简洁代码:

前奏>foldr (+) 0 [1,4,6]
11
前奏>foldr(\x y -> x+y+1) 0 [1,4,6]
14
前奏> let f start =foldr(\_ (xl,xr) -> (xr, xl)) start
前奏> :t f
f :: (t, t) -> [a] -> (t, t)
前奏> f(“左”,“右”)[1]
(“右左”)
前奏> f(“左”,“右”)[1, 2]
(“左右”)

请注意我如何在 lambda 中将其定义为助手f,我对类型没有任何线索xl and xr,我只是想交换这些元素的元组,这需要类型the same。所以这将是一个多态 lambda,其类型为

\_ (xl, xr) -> (xr, xl)   ::  ∀ a t.  a -> (t,t) -> (t,t)

1Apart from the weird explicit malloc stuff, type safety etc.: code like that is extremely hard to work with in languages without garbage collector, because somebody always needs to clean up memory once it's not needed anymore, but if you didn't watch out properly whether somebody still holds a reference to the data and might in fact need it still. That's nothing you have to worry about in Java, Lisp, Haskell...

2There is a completely different approach to this: the one dynamic languages choose. In those languages, every operation needs to make sure it works with any type (or, if that's not possible, raise a well-defined error). Then you can arbitrarily compose polymorphic operations, which is on one hand "nicely trouble-free" (not as trouble-free as with a really clever type system like Haskell's, though) but OTOH incurs quite a heavy overhead, since even primitive operations need type-decisions and safeguards around them.

3I'm of course being unfair here. The OO paradigm has more to it than just type-safe polymorphism, it enables many things e.g. old ML with it's Hindler-Milner type system couldn't do (ad-hoc polymorphism: Haskell has type classes for that, SML has modules), and even some things that are pretty hard in Haskell (mainly, storing values of different types in a variable-size container). But the more you get accustomed to functional programming, the less need you will feel for such stuff.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

什么是多态 lambda? 的相关文章

  • 了解函数类型

    我在尝试理解 Haskell 如何确定函数类型时感到有点困惑 这是一个例子 boolFcn x y x 3 y 4 当我检查上述函数的类型时 它给出了结果 Num a1 Num a Eq a1 Eq a gt a gt a1 gt Bool
  • 根据R中的行索引计算滚动总和

    我正在尝试根据窗口大小 k 计算分组滚动总和 但是 如果组内行索引 n 小于 k 我想使用条件 k min n k 我的问题与这个问题类似R dplyr 滚动总和 https stackoverflow com questions 3015
  • 为什么 Vector[Option[Int]] 上的 flatMap 其映射器函数结果不是 Vector[Option[Int]] 有效?

    例如 Vector Some 1 Some 2 Some 3 None flatMap n gt n 产生一个Vector 1 2 3 而不是给出错误 正如我在其他语言中看到的那样 flatMap当你有一个产生嵌套的映射器函数时使用 所以我
  • enumFromTo 如何工作?

    我无法将号码添加到Char 以下内容将无法编译 a 1 但是 a z 成功创建一个字符串 其中每个字符值都会递增 有没有一个特殊的函数可以增加Char 我知道我能做到chr ord c 1 如何 a z 或底层enumFromTo函数增加结
  • Scala 插入列表中的特定位置

    这是我确实解决的问题 但是作为一个完全命令式的 Scala 菜鸟 我觉得我发现了一些完全不优雅的东西 任何改进的想法表示赞赏 val l1 4 1 2 3 4 Nil original list val insert List 88 99
  • 构建表达式树

    我正在努力思考如何为更多 lambda 构建表达式树 如下所示 更不用说可能有多个语句的东西了 例如 Func
  • 数组名或函数名何时“转换”为指针? (在C中)

    1 误解 每当用 C 语言声明一个数组时 都会有一个指向该数组第一个元素的指针created 数组的名称 隐式 是吗 我不这么认为 前两行this http ee hawaii edu tep EE160 Book chap7 subsec
  • 在 Haskell 中使用 Maybe 类型

    我正在尝试利用 Haskell 中的 Maybe 类型 我有一个查找返回 Maybe 的键 值元组 如何访问 Maybe 包装的数据 例如 我想将 Maybe 包含的整数与另一个整数相加 或者 您可以进行模式匹配 case maybeVal
  • 为什么在 haskell 中不带括号不可能进行负数相乘

    乘法5 3在 haskell gchi 中给了我一个错误 但乘以5 3 工作正常 为什么需要括号 ghci GHCi version 7 4 1 http www haskell org ghc for help Loading packa
  • 当 Haskell 持久库中需要“Key”时,如何通过“Int”获取实体?

    我将持久性 orm 与 scotty Web 框架一起使用 我想通过 id 从 db 获取值 这些是来自 GET 请求的 有 get 函数接受 Key Entity 变量并返回 Maybe Entity 我使用以下代码从数据库获取值 k l
  • Haskell 中的随机枢轴快速排序

    是否有可能在 Haskell 中实现快速排序 使用 RANDOM PIVOT 但仍然有一个简单的Ord a gt a gt a 签名 我开始了解 Monad 目前 我将 monad 解释为某种 命令模式 这对于 IO 非常有用 所以 我知道
  • 数据记录的类约束

    我有一个data type data BuildException a KillBuild JobID a Stage FailBuild JobID a Stage CancelBuild JobID a Stage StopBuild
  • 为什么阴谋集团重新安装“总是危险的”?

    使用 Cabal 重新安装软件包时 通常会看到以下警告 警告 请注意 重新安装总是很危险的 无论如何继续 此消息背后的一些原因是什么 目前 重新安装软件包意味着破坏性地覆盖已安装的软件包 如果旧包对系统有任何反向依赖性 它们将不再工作 为了
  • 如何编写将布尔值返回到一个函数的函数

    我在这里发现了一个类似的问题 它问了几乎相同的问题 但又不完全一样 我的问题是如何将 a gt Bool 类型的函数列表组合成一个也是 a gt Bool 的函数 Ex compose a gt Bool gt a gt Bool comp
  • 追踪 Haskell 中的错误

    我如何获得有关 Haskell 错误发生位置的更多信息 例如 昨天我正在开发一个 Haskell 程序 该程序解析输入文件 转换数据 然后打印出报告信息 有一次 我跑了 main 然后回来了 Prelude read parse error
  • 带边界的 haskell 列表数据类型

    我有以下类型定义来表示卡片 data Suit Hearts Spades Diamonds Clubs data Rank Numeric Integer Jack Queen King Ace data Card Card Rank S
  • 如何在 Haskell 中处理这个简单的 IO 异常

    全部处理 在下面的代码中 getDirectoryContents dir可能会失败 例如dir可能不存在 如何捕获这个并向用户抛出有意义的消息 我知道 IO 异常处理已经被问过很多次了 但我仍然找不到一个简单的方法来做到这一点 walk
  • 如何在Python中求和

    我想知道如何在 python 中表示总和而不需要像这样的循环here http docs scipy org doc scipy reference tutorial optimize html 我们有 def rosen x The Ro
  • 我怎么知道PowerShell函数参数被省略了

    考虑这样的函数 function Test foo bar 我们可以称之为 Test foo null Test 我如何知道何时省略了 foo 以及何时为 null 如果您使用的是 Powershell V2 或更高版本 则可以使用 PSB
  • 变量值的 swap() 函数[重复]

    这个问题在这里已经有答案了 我无法达到下面这个交换函数的预期结果 我希望将值打印为 3 2 function swap x y var t x x y y t console log swap 2 3 任何线索将不胜感激 您的函数正在内部交

随机推荐

  • facebook sdk android,如何获取用户电话号码?

    我想知道用户的电话号码 但我在其中找不到任何提及文档 https developers facebook com docs reference api user 有没有办法获取用户的电话号码 https developers faceboo
  • python中的循环缓冲区比使用双端队列的缓冲区快?

    我正在使用 collections deque 在 python 中实现循环缓冲区 以将其用于一些计算 这是我原来的代码 clip moviepy editor VideoFileClip file mp4 clip size clip s
  • Android:onCreateOptionsMenu() 项目操作

    我有一个通过以下方式创建的菜单 Override public boolean onCreateOptionsMenu Menu menu menu add Email return super onCreateOptionsMenu me
  • 迁移到 Thymeleaf 3 后,Thymeleaf 停止解析布局模板

    我有一个简单的 Spring Boot 应用程序 版本为 1 5 7 我正在尝试将其迁移到版本 2 0 0 我快完成了 但还缺少最后一块 那就是百里香叶 旧版本中一切正常 但迁移后 Spring Boot 停止解析任何模板 页面和电子邮件
  • 如何使 Django 注释在“用户”字段上使用 select_lated() ?

    我在用着Django 评论框架 https docs djangoproject com en dev ref contrib comments 所有评论均由经过身份验证的用户发布 在评论附近 我使用显示一些用户个人资料信息 comment
  • 警报管理器的替代品 - Android

    说真的 执行应用程序未运行时发送通知等简单任务的解决方案是什么 使用具有自定义操作系统的设备 例如小米 设置闹钟后 它会在短时间内 例如几分钟 起作用 但不会在 例如几小时 长时间内起作用 尽管它在某些设备上以及应用程序运行时工作得很好 我
  • 使用 KnockoutJS 和简单类继承时丢失对 self 的引用

    我在用John Resig 的 简单 JavaScript 继承 http ejohn org blog simple javascript inheritance 创建一个可以继承的类 我还使用 KnockoutJS 来计算可观察量 问题
  • Java使用反射调用类的main()方法

    我需要使用反射从另一个主方法调用 Java 类的主方法 必须使用反射 以消除被调用的主类的编译时依赖 直接的方法不会产生影响 因为它只识别 公共 和 非静态 方法 建议 不应该比调用任何其他函数更复杂 public static void
  • 如何在mvc 3中使用jquery互相填充级联下拉列表?

    asp net mvc3如何互相加载级联dropdownlist 我能怎么做 我一直在利用http geekswithblogs net ranganh archive 2011 06 14 cascading dropdownlist i
  • Docker 中的小写镜像名称

    为什么 Docker 只允许镜像名称使用小写字母 为什么不用大写字母 在容器提交期间 docker commit OPTIONS CONTAINER REPOSITORY TAG 这是一个设计选择 技术上 允许混合大小写名称可能不会有任何实
  • React、Sass,从 .scss 文件导入 img 时出现错误

    我想设置一个简单的背景图像 并覆盖 sass 文件中的反应组件 但我收到此错误 Module not found You attempted to import assets hero jpg which falls outside of
  • symfony2.1 捆绑版本混乱,针对 symfony 和 sonata admin / knp 菜单捆绑

    我想在新项目中使用 symfony2 1 我尝试用composer安装symfony2 但是存在多个错误 问题 因此我对每个问题也有疑问粗体字体样式 有时我不知道我是否以正确的方式做事 我想使用稳定版本 所以我选择了 2 1 0 版本而不是
  • 如何在 ReactJs 中加载 WebGL 顶点和片段着色器?

    让我先提出我的问题 然后分享相关细节 有没有一种方法可以在 ReactJS 中使用 加载 修复解析错误 WebGL 着色器 有可用的装载机吗 经过几个小时的努力 在谷歌上搜索了一种能够使用着色器文件 即顶点和片段着色器 的方法 我还没有找到
  • 稍后可以在领域中更改主键吗?

    我在本地设置了一个临时主键并将对象保存到领域 稍后服务器为该对象分配一个唯一键 然后我想更新该对象的主键 那么是否可以为对象重新分配主键呢 当我们为对象重新分配主键时会发生什么 它会用旧的主键保存对象吗 可以将主键的值更改为最高 Realm
  • 将文本限制为 angular.js 中的特定语言字符

    我正在使用 angular js 构建一个表单 我的表格看起来像
  • 在本机反应中打开地图/谷歌地图

    我正在尝试打开谷歌地图或我的地图react native应用 当我在 iPhone 模拟器上运行该应用程序时 我收到此错误 Don t know how to open URI 我做错了什么 My code openGps var url
  • MongoDB 中的changeStream 和tailable 游标有什么区别

    我试图确定变更流之间的区别 https docs mongodb com manual changeStreams https docs mongodb com manual changeStreams https docs mongodb
  • 如何修复 RN 突然无法找到本地模块(无法解析模块)?

    我遇到了一个奇怪的情况 突然我的 RN 应用程序无法加载任何场景 该应用程序已经运行良好数周了 我没有移动任何文件 失败 error bundling UnableToResolveError Unable to resolve modul
  • 嵌入式系统的时间戳

    我想为嵌入式系统 运行 ArchLinux 的 Raspberry Pi A 上的传感器测量添加时间戳 我发现了time from time h但它给了我 第二个 分辨率 我至少需要 毫秒 系统将运行几个小时 我不担心长时间的漂移 我怎样才
  • 什么是多态 lambda?

    lambda 匿名函数 的概念对我来说非常清楚 我知道类方面的多态性 运行时 动态分派用于根据实例的最派生类型调用适当的方法 但 lambda 到底如何才能是多态的呢 我是另一位试图了解更多有关函数式编程的 Java 程序员 您会发现我在下