【译】征服 JavaScript 面试: 什么是函数式编程?| Eric Elliott

发表于 5年以前  | 总阅读数:2555 次
  • 共享状态
  • 不可变性
  • 副作用(Side Effects)
  • 通过高阶函数提升可重用性
    • 容器、函子(Functor)、列表和流
  • 对比声明式与命令式
  • 结论
  • 作业
  • 更进一步

原文:http://www.zcfy.cc/article/2221

函数式编程在 JavaScript 界已经成为了一个非常热门的话题。而仅在几年之前,还几乎没有 JavaScript 程序员了解函数式编程是什么,但在最近三年里,我看到非常多的应用程序代码库里大量使用着函数式编程思想。

函数式编程 (通常简称为 FP)是指通过复合纯函数来构建软件的过程,它避免了共享的状态(share state)易变的数据(mutable data)、以及副作用(side-effects)。函数式编程是声明式而不是命令式,并且应用程序状态通过纯函数流转。对比面向对象编程,后者的应用程序状态通常是共享并共用于对象方法。

函数式编程是一种编程范式意味着它是一种软件构建的思维方式,有着自己的理论基础和界定法则。其他编程范式的例子包括面向对象编程和过程式编程。

与命令式或面向对象代码相比,函数式代码倾向于更简洁、更可预测以及更易于测试 ---- 但是如果你对它以及与它相关的常见模式不熟悉,读函数式代码会让你觉得信息量太大,而且相关文献对于初学者来说往往难以理解。

如果你开始 google 函数式编程的术语,你很可能一下子碰壁,那些学术术语对新人来说着实有点吓人。它有一个非常陡峭的学习曲线。但是如果你已经用 JavaScript 写了一段时间的代码,你很可能不知不觉中在你的软件里已经使用了很多函数式编程原理和功能。

不要让那些新名词把你吓跑。实际上它比你所听说的要简单很多。

最难的部分是记住那些以前不熟悉的词汇。在这些名词定义中蕴含了许多思想,你只有理解了它们,才能够开始掌握函数式编程真正的意义:

  • 纯函数(Pure functions)
  • 函数复合(Function composition)
  • 避免共享状态(Avoid shared state)
  • 避免改变状态(Avoid mutating state)
  • 避免副作用(Avoid side effects)

换句话说,如果你想要了解函数式编程在实际中的意义,你需要从理解那些核心概念开始。

一个纯函数是这样的一个函数:

  • 给它同样的输入,总是返回同样的结果,并且
  • 没有副作用

纯函数有着许多对函数式编程而言非常重要的属性,包括引用透明(你可以将一个函数调用替换成它的结果值,而不会对程序的运行造成影响)。获取更多细节,可以阅读"什么是纯函数"。

函数复合是结合两个或多个函数,从而产生一个新函数或进行某些计算的过程。例如,复合操作 f*g(点号意思是对两者执行复合运算)在 JavaScript 中相当于执行 f(g(x))。理解函数复合是理解软件如何用函数式编程模型来构建的很重要的一步。获取更多细节,可以阅读"什么是函数组合"。

共享状态

共享状态 的意思是任意变量、对象或者内存空间存在于共享作用域下,或者作为对象的属性在各个作用域之间被传递。共享作用域包括全局作用域和闭包作用域。通常,在面向对象编程中,对象以添加属性到其他对象上的方式在作用域之间共享。

举个例子,一个电脑游戏可能会控制一个游戏对象(game object),它上面有角色(characters)和游戏道具(items),这些数据作为属性存储在游戏对象之上。而函数式编程避免共享状态 ---- 与前者不同地,它依赖于不可变数据结构和纯粹的计算过程来从已存在的数据中派生出新的数据。要获取更多关于软件开发如何使用函数式编程处理应用程序状态的详细内容,可以阅读"10 Tips for Better Redux Architecture"。

共享状态的问题是为了理解函数的作用,你需要了解那个函数所用到的全部共享变量的变化历史。

想象你有一个 user 对象需要保存。你的 saveUser() 函数向服务器 API 发起一个请求。此时,用户改变了他们的头像,通过 updateAvatar() 并触发了另一次 saveUser() 请求。在保存动作执行后,服务器返回一个更新的 user 对象,客户端要将这个对象替换内存中的对象,以保持与服务器同步。

不幸地是,第二次请求有可能比第一次请求更早返回,所以当第一次请求(现在已经过时了)返回时,新的头像又从内存中丢失了,被替换回旧的头像。这是一个同步竞争的例子,是一个非常常见的共享状态 bug。

共享状态的另一个常见问题是改变函数调用次序可能导致一连串的错误,因为函数操作共享数据是依时序的:

//使用共享数据,函数调用的次序会改变函数调用的结果
    const x = {
      val: 2
    };

    const x1 = () => x.val += 1;

    const x2 = () => x.val *= 2;

    x1();
    x2();

    console.log(x.val); // 6

    //下面的例子与上面的相同,除了……
    const y = {
      val: 2
    };

    const y1 = () => y.val += 1;

    const y2 = () => y.val *= 2;

    // ...函数的调用次序颠倒了一下...
    y2();
    y1();

    // ... 这改变了结果值:
    console.log(y.val); // 5

如果你避免共享状态,函数的调用时序不同就不会改变函数的调用结果。使用纯函数,给定同样的输入,你将总是能得到同样的输出。这使得函数调用完全独立于其他函数调用,可以从根本上简化变更和重构。改变函数内容,或者改变函数调用的时序不会波及和破坏程序的其他部分。

const x = {
      val: 2
    };

    const x1 = x => Object.assign({}, x, { val: x.val + 1});

    const x2 = x => Object.assign({}, x, { val: x.val * 2});

    console.log(x1(x2(x)).val); // 5


    const y = {
      val: 2
    };

    //由于它对于外部变量没有依赖,
    //我们不需要不同的函数来操作不同的变量

    //这里故意留白


    //由于函数没有操作可变数据,你可以调用这些函数任意次,用各种次序
    //都不会改变之后调用函数的结果值。
    x2(y);
    x1(y);

    console.log(x1(x2(y)).val); // 5

在上面的例子里,我们使用了 Object.assign() 并传入一个空的 object 作为第一个参数来拷贝 x 的属性,以防止 x 在函数内部被改变。在这个例子里,它等价由于重新创建一个对象,而这是一种 JavaScript 里的通用模式, 用来拷贝已存在状态而不是使用引用,从而避免像我们第一个例子里产生的问题。

如果你仔细看例子里的 console.log() 语句,你会发现我前面已经提到过的概念:函数复合。回顾一下,函数复合看起来像是这样:f(g(x))。在这个例子里,我们的 f()g()x1()x2(),所以复合是 x1*x2

当然,如果你改变复合的顺序,输出将改变。操作的顺序仍然很重要。f(g(x)) 并不总是等价于 g(f(x)),但是,有一件事情发生了改变,那就是函数外部的变量不会被修改 ---- 原本函数修改外部变量是一个大问题。要是函数不纯,我们如果不了解函数使用或操作的每个变量的完整历史,就不可能完全理解它做了什么。

移除函数时序依赖,你就完全消除了一大类潜在的 bug。

不可变性

一个不可变的(immutable)对象是指一个对象不会在它创建之后被改变。对应地,一个可变的(mutable)对象是指任何在创建之后可以被改变的对象。

不可变性是函数式编程的一个核心概念,因为没有它,你的程序中的数据流是有损的。状态历史被抛弃而奇怪的 bug 可能会在你的软件中产生。关于更多不变性的意义,阅读 "The Dao of Immutability."。

在 JavaScript 中,很重要的一点是不要混淆了 const 和不变性。const 创建一个变量绑定,让该变量不能再次被赋值。const 并不创建不可变对象。你虽然不能改变绑定到这个变量名上的对象,但你仍然可以改变它的属性,这意味着 const 的变量仍然是可变的,而不是不可变的。

不可变对象完全不能被改变。你可以通过深度冻结对象来创造一个真正的不可变的值。JavaScript 提供了一个方法,能够浅冻结一个对象:

const a = Object.freeze({
      foo: "Hello",
      bar: "world",
      baz: "!"
    });

    a.foo = "Goodbye";
    // Error: Cannot assign to read only property "foo" of object Object

然而冻结的对象只是表面一层不可变,例如,深层的属性还是可以被改变:

const a = Object.freeze({
      foo: { greeting: "Hello" },
      bar: "world",
      baz: "!"
    });

    a.foo.greeting = "Goodbye";

    console.log(`${ a.foo.greeting }, ${ a.bar }${a.baz}`);

如你所见,被冻结的 object 的顶层基本属性不能被改变,但是如果有一个属性本身也是 object(包括数组等),它依然可以被改变 ---- 因此甚至被冻结的对象也不是不可变的,除非你遍历整个对象树并冻结每一个对象属性。

在许多函数式编程语言中,有特殊的不可变数据结构,被称为 trie 数据结构(trie 的发音为 tree),这一结构有效地深冻结 ---- 意味任何属性无论它的对象层级如何都不能被改变。

当一个对象被拷贝给一个操作符时,tries 使用结构共享来共用不可变对象的引用内存地址,这减少内存占用,而且能够显著地改善一些类型的操作的性能。

例如,你可以使用 ID 来比较对象,如果两个对象的根 ID 相同,你不需要继续遍历比较整个对象树来寻找差异。

有一些 JavaScript 的库使用了 tries,包括 Immutable.js 和 Mori。

我体验了这两个库,最终决定在需要大量不可变状态大的型项目中使用 Immutable.js。想要了解这一部分的更多内容,请移步 "10 Tips for Better Redux Architecture"。

副作用(Side Effects)

副作用是指除了函数返回值以外,任何在函数调用之外观察到的应用程序状态改变。副作用包括:

  • 改变了任何外部变量或对象属性(例如,全局变量,或者一个在父级函数作用域链上的变量)
  • 写日志
  • 在屏幕输出
  • 写文件
  • 发网络请求
  • 触发任何外部进程
  • 调用另一个有副作用的函数

在函数式编程中,副作用被尽可能避免,这使得程序的作用更容易理解,也使得程序更容易被测试。

Haskell 以及其他函数式编程语言经常从纯函数中隔离和封装副作用,使用 monads 技巧。Mondas 这个话题要深入下去可以写一本书,所以我们先放一放。

你现在需要做的是要从你的软件中隔离副作用行为。如果你让副作用与你的程序逻辑分离,你的软件将会变得更易于扩展、重构、调试、测试和维护。

这也是为什么大部分前端框架鼓励我们分开管理状态和组件渲染,采用松耦合的模型。

通过高阶函数提升可重用性

函数式编程倾向于复用一组通用的函数功能来处理数据。面向对象编程倾向于把方法和数据集中到对象上。那些被集中的方法只能用来操作设计好的数据类型,通常是那些包含在特定对象实例上的数据。

在函数式编程里,对任何类型的数据一视同仁。同样的 map() 操作可以 map 对象、字符串、数字或任何别的类型,因为它接受一个函数参数,来适当地操作给定类型。函数式编程通过使用高阶函数来实现这一技巧。

在 JavaScript 里,函数是一等公民,JavaScript 允许使用者将函数作为数据 ---- 可以将它们赋值给变量、作为参数传递给其他函数、将它们作为返回值返回,等等……

高阶函数指的是一个函数以函数为参数,或以函数为返回值,或者既以函数为参数又以函数为返回值。高阶函数经常用于:

  • 抽象或隔离行为、作用,异步控制流程作为回调函数,promises,monads,等等……
  • 创建可以泛用于各种数据类型的功能
  • 部分应用于函数参数(偏函数应用)或创建一个柯里化的函数,用于复用或函数复合。
  • 接受一个函数列表并返回一些由这个列表中的函数组成的复合函数。

容器、函子(Functor)、列表和流

Functor 是可以被用来执行具体 map 操作的数据。换句话说,它是一个有接口的容器,能够遍历其中的值。当你看到"functor"这个词,你在脑海里应该想到"mappable"。

之前我们说同样的 map() 函数能够操作各种数据类型。它是通过将 map 操作抽象出来,提供给 functor API 使用。map() 利用该接口执行重要的流程控制操作。在 Array.prototype.map() 的场景里,容器是一个数组,但是其他数据接口也可以作为 functor,同样它也提供了 mapping 操作的 API。

让我们看一下 Array.prototype.map() 是如何让你从 mapping 功能里抽象数据类型,让 map() 可以适用于任何数据类型的。我们创建一个简单的 double() mapping,它简单地将传给它的值乘以 2:

const double = n => n * 2;
    const doubleMap = numbers => numbers.map(double);
    console.log(doubleMap([2, 3, 4])); // [ 4, 6, 8 ]

假设我们相对游戏中的目标执行奖励翻倍操作,我们所需要做的只是小小改变一下我们传给 map()double() 函数,这样便一切正常:

const double = n => n.points * 2;

    const doubleMap = numbers => numbers.map(double);

    console.log(doubleMap([
      { name: "ball", points: 2 },
      { name: "coin", points: 3 },
      { name: "candy", points: 4}
    ])); // [ 4, 6, 8 ]

使用 functors 以及使用高阶函数抽象来创建通用功能函数,以处理任意数值或不同类型的数据,这是函数式编程中很重要的概念。你还能看到类似的概念以各种不同的方式被应用。

"流即是随着时间推移而变化的列表。"

现在你所需要知道的是容器和容器的值所能应用的形式不仅仅只有数组和 functor。一个数组只是一些内容的列表。如果这个列表随着时间推移而变化则成为一个流 ---- 所以你可以应用同样的功能来处理时间流 ---- 如果你用函数式编程实际开始构建一个真正的软件时,你就会看到很多这种用法。

对比声明式与命令式

函数式编程是一个声明式范式,意思是说程序逻辑不需要通过明确描述控制流程来表达。

命令式 程序花费大量代码来描述用来达成期望结果的特定步骤 ---- 控制流:即如何做。

声明式 程序抽象了控制流过程,花费大量代码描述的是数据流:即做什么。

举个例子,下面是一个用 命令式 方式实现的 mapping 过程,接收一个数值数组,并返回一个新的数组,新数组将原数组的每个值乘以 2:

const doubleMap = numbers => {
      const doubled = [];
      for (let i = 0; i < numbers.length; i++) {
        doubled.push(numbers[i] * 2);
      }
      return doubled;
    };

    console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

而实现同样功能的 声明式 mapping 用函数 Array.prototype.map() 将控制流抽象了,从而我们可以表达更清晰的数据流:

const doubleMap = numbers => numbers.map(n => n * 2);

    console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

命令式 代码中频繁使用语句。语句是指一小段代码,它用来完成某个行为。通用的语句例子包括 forifswitchthrow,等等……

声明式 代码更多依赖表达式。表达式是指一小段代码,它用来计算某个值。表达式通常是某些函数调用的复合、一些值和操作符,用来计算出结果值。

以下都是表达式:

2 * 2
    doubleMap([2, 3, 4])
    Math.max(4, 3, 2)

通常在代码里,你会看到一个表达式被赋给某个变量,或者作为函数返回值,或者作为参数传给一个函数。在被赋值、返回或传递之前,表达式首先被计算,之后它的结果值被使用。

结论

函数式编程偏好:

  • 使用纯函数而不是使用共享状态和副作用
  • 让可变数据成为不可变的
  • 用函数复合替代命令控制流
  • 使用高阶函数来操作许多数据类型,创建通用、可复用功能取代只是操作集中的数据的方法。
  • 使用声明式而不是命令式代码(关注做什么,而不是如何做)
  • 使用表达式替代语句
  • 使用容器与高阶函数替代多态

作业

学习和练习这一组核心的数据扩展

  • .map()
  • .filter()
  • .reduce()

使用 map 来转换如下数组的值为 item 名字:

// vvv Don"t change vvv
    const items = [
      { name: "ball", points: 2 },
      { name: "coin", points: 3 },
      { name: "candy", points: 4}
    ];
    // ^^^ Don"t change ^^^

    const result = items.map(
      /* ==vvv Replace this code vvv== */
      () => {}
      /* ==^^^ Replace this code ^^^== */
    );


    // vvv Don"t change vvv
    test("Map", assert => {
      const msg = "Should extract names from objects";
      const expected = [
        "ball", "coin", "candy"
      ];

      assert.same(result, expected, msg);
      assert.end();
    });
    // ^^^ Don"t change ^^^

使用 filter 来选择出 points 大于等于 3 的元素:

// vvv Don"t change vvv
    const items = [
      { name: "ball", points: 2 },
      { name: "coin", points: 3 },
      { name: "candy", points: 4 }
    ];
    // ^^^ Don"t change ^^^

    const result = items.filter(
      /* ==vvv Replace this code vvv== */
      () => {}
      /* ==^^^ Replace this code ^^^== */
    );


    // vvv Don"t change vvv
    test("Filter", assert => {
      const msg = "Should select items where points >= 3";
      const expected = [
        { name: "coin", points: 3 },
        { name: "candy", points: 4 }
      ];

      assert.same(result, expected, msg);
      assert.end();
    });
    // ^^^ Don"t change ^^^

使用 reduce 来求出 points 的和:

// vvv Don"t change vvv
    const items = [
      { name: "ball", points: 2 },
      { name: "coin", points: 3 },
      { name: "candy", points: 4 }
    ];
    // ^^^ Don"t change ^^^

    const result = items.reduce(
      /* ==vvv Replace this code vvv== */
      () => {}
      /* ==^^^ Replace this code ^^^== */
    );


    // vvv Don"t change vvv
    test("Learn reduce", assert => {
      const msg = "should sum all the points";
      const expected = 9;

      assert.same(result, expected, msg);
      assert.end();
    });
    // ^^^ Don"t change ^^^

更进一步

准备好更深入学习了吗?阅读 Jafar Husain 的 Learn Rx exercises 来学习一些最重要的函数式编程工具。

想要了解更详细的关于函数式编程的细节以及如何使用函数式编程结合实际每天使用的 JavaScript 来构建真正的应用程序?

与 Eric Elliott 一同学习 JavaScript 吧。这么好的机会,别错过。


Eric Elliott is the author of "Programming JavaScript Applications" (O'Reilly), and "Learn JavaScript with Eric Elliott". He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.

感谢 JS_Cheerleader.

英文原文:https://medium.com/javascript-scene/master-the-javascript-interview-what-is-functional-programming-7f218c68b3a0#.2pp6q2j8v

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
为Electron程序添加运行时日志 5年以前  |  20374次阅读
Node.js下通过配置host访问URL 5年以前  |  5895次阅读
用 esbuild 让你的构建压缩性能翻倍 4年以前  |  5794次阅读
 目录