【问题标题】:Pure functions when working with DOM manipulation使用 DOM 操作时的纯函数
【发布时间】:2017-05-22 09:27:41
【问题描述】:

我正试图围绕纯函数展开思考,但我不确定我是否真的理解它。我知道纯函数不应该改变外部状态,只要输入相同,它每次都应该返回相同的输出。

我知道例如这个函数是不纯的,因为它改变了程序其他部分可能使用的购物车变量:

const addToCart = (cart, item) => { 
  cart.push(item); 
  return cart; 
};

纯状态下的同一个函数:

const addToCart = (cart, item) => { 
  const newCart = lodash.cloneDeep(cart); 
  newCart.push(item); 
  return newCart; 
};

这对我来说很有意义。我了解到纯函数应该总是返回一些东西。

但是,我正在处理一些需要我使用 HTML5 画布元素的东西,并且我有这个清除画布的功能:

function clearCanvas(canvas) { 
  canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); 
}

我怎样才能使上述函数成为纯函数?我意识到它是不纯的,因为它不返回任何东西,而且它也会改变 canvas 变量的状态。 DOM 操作本质上是不纯的吗?

任何帮助将不胜感激:)

【问题讨论】:

    标签: javascript dom canvas functional-programming


    【解决方案1】:

    IO 单子

    你可能对 IO monad 感兴趣——本质上 IO 包含一个 thunk 或惰性函数,它仅在我们调用 runIO 时运行。更重要的是,我们可以将内容限制在 IO 和 map 中,这是一个允许我们对包含的值进行操作的普通函数。

    如需良好阅读和另一个 IO 实现,请参阅 Brian Lonsdorf 书中的 Chapter 9: Monadic Onions


    小演示

    // IO :: (void -> a) -> IO a
    const IO = f => ({
      // runIO :: void -> a
      runIO: f,
      // map :: IO a => (a -> b) -> IO b
      map: g =>
        IO(() => g(f())),
      // chain :: IO a => (a -> IO b) -> IO b
      chain: g =>
        IO(g(f()).runIO)
    })
    
    // IO.of :: a -> IO a
    IO.of = x => IO(() => x)
    
    // log :: String -> a -> IO a
    const log = label => x =>
      IO(() => (console.log(label, x), x))
    
    // $ :: String -> IO HTMLElement
    const $ = x =>
      IO(() => document.querySelector(x))
    
    // getText :: HTMLElement -> String
    const getText = e =>
      e.textContent
    
    // main :: String -> IO String
    const main = selector =>
      $(selector)
        .map(getText)
        .chain(log('A'))
        .map(s => s.toUpperCase())
        .chain(log('B'))
        .runIO()
    
    main('title')
    // A hello world
    // B HELLO WORLD
    <title>hello world</title>

    【讨论】:

    • @avalanche1 这很难回答您不理解的问题 - 如果您有具体问题,我很乐意回答。
    • 是的,对不起)这东西不适合业余爱好者
    【解决方案2】:

    DOM 操作是不纯的。您无法以“纯粹”的方式清除画布。

    任何改变系统状态或与外界交互的东西都被称为side effects,并且在纯函数式编程环境中应避免副作用。

    然而,DOM 操作显然做到了这两点;清除画布既是状态的变化(未清除到清除),用户也可以看到这种变化。


    您可能希望更深入地了解functional programming,这似乎是您试图通过纯/不纯函数方法实现的目标。

    函数式编程的目的是避免改变状态,例如,一个程序不会改变一个对象,而另一个程序正在使用它,这可能会产生意想不到的和不想要的结果。

    【讨论】:

    • 感谢您的回答。这是有道理的。
    • 投反对票,为什么?我自己还在学习,如果我说了什么不正确的,请告诉我,我们都在这里学习。无法解释的反对票毫无价值。
    【解决方案3】:

    通过函数编程范式来做到这一点

    function clearCanvas(canvas) { 
        const imgData = canvas.getContext('2d').getImageData(0,0,canvas.width,canvas.height);
        new Uint32Array(imgData.data.buffer).set(0);
        return data;
    } 
    
    canvas.getContext('2d').setImageData(clearCanvas(canvas),0,0);
    

    没有副作用,并且为相同的参数返回相同的数据。

    但是函数式编程是一种防御风格,它的工作假设是代码是危险的,并且必须隔离危险。因此,它非常耗资源且速度慢,根本不适合高性能代码。

    【讨论】:

    • 只有您提供的功能是无副作用的,OP 询问的实际过程(“DOM 操作是否天生不纯?”)不是(正如您自己在您的代码中所显示的那样 - p.)
    • @theonlygusti 函数式编程的荒谬性可以在其范式约束内与 DOM 交互,您可以通过复制间接地这样做(疯狂的是)。但是没有副作用只是一个抽象的理想,使用的任何 RAM 都来自全局存储和/或堆栈(每个函数都必须这样做)。因为这个事实,你不能在 JS 中编写纯函数。
    • @theonlygusti 只是说你不能用使用全局存储的语言编写纯函数。
    • medium.com/javascript-scene/… 这篇文章让我对 JS 中的函数式编程产生了兴趣。这篇文章的要点是 JS 在某些方面非常适合函数式编程,而在其他方面则根本不适合。但是我们仍然应该尝试使用有效的部分,因为 JavaScript 目前是唯一在浏览器中本地运行的语言。
    猜你喜欢
    • 2022-12-07
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    • 2011-12-05
    • 2016-08-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多