具有功能的可组合数据类型

注意:这是“ Composition Software”系列(现在是书!)的一部分,该系列从零开始学习JavaScript ES6 +中的功能编程和组合软件技术。 敬请关注。 还有更多的东西要来!
<上一页| <>

在JavaScript中,最简单的组合方法是函数组合,而函数只是可以向其添加方法的对象。 换句话说,您可以执行以下操作:

  const t = value => {
   const fn =()=>值;  fn.toString =()=>`t($ {value})`; 返回fn;
 };
 const someValue = t(2); console.log(
   someValue.toString()//“ t(2)”
 ); 

这是一个工厂,它返回数字数据类型t实例。 但是请注意,这些实例不是简单的对象。 相反,它们是函数,并且可以像其他函数一样组成它们。 让我们假设它的主要用例是求和其成员。 当它们组成时,将它们相加也许是有意义的。

首先,让我们建立一些规则(四个=表示“等于”):

  • t(x)(t(0)) ==== t(x)
  • t(x)(t(1)) ==== t(x + 1)

您可以使用我们已经创建的便捷的.toString()方法在JavaScript中表达这一点:

  • t(x)(t(0)).toString() === t(x).toString()
  • t(x)(t(1)).toString() === t(x + 1).toString()

我们可以将其转换为一种简单的单元测试:

  const assert = {
  相同:((实际的,预期的,味精)=> {
    如果(actual.toString()!==预期.toString()){
      抛出新的错误(`OK:$ {msg}
        预期:$ {预期}
        实际:$ {实际}
       `);
     } console.log(`OK:$ {msg}`);
   }
 };
 {
   const msg ='由t(0)==== t(x)组成的值t(x)';
   const x = 20;
   const a = t(x)(t(0));
   const b = t(x);
   assert.same(a,b,msg);
 } {
   const msg ='由t(1)==== t(x + 1)组成的值t(x)';
   const x = 20;
   const a = t(x)(t(1));
   const b = t(x + 1);
   assert.same(a,b,msg);
 } 

这些测试最初将失败:

 NOT OK: a value t(x) composed with t(0) ==== t(x) Expected: t(20) Actual: 20 

但是我们可以通过3个简单的步骤使它们通过:

  1. fn函数更改为返回t(value + n)add函数,其中n是传递的参数。
  2. t类型添加一个.valueOf()方法,以便新的add()函数可以将t()实例作为参数。 +运算符将使用n.valueOf()的结果作为第二个操作数。
  3. 使用Object.assign()将方法分配给add()函数。

当您将它们放在一起时,它看起来像这样:

  const t = value => {
   const add = n => t(value + n);  return Object.assign(add,{
     toString:()=>`t($ {value})`,
     valueOf:()=>值
   });
 }; 

然后测试通过:

  “确定:由t(0)==== t(x)组成的值t(x)”
 “确定:由t(1)==== t(x + 1)组成的值t(x)” 

现在,您可以使用函数组成来构成t()值:

  //从上到下编写函数:
 const pipe =(... fns)=> x => fns.reduce((y,f)=> f(y),x); //糖以初始值启动管道:
 const sumT =(... fns)=>管道(... fns)(t(0)); sumT(
   t(2),
   t(4),
   t(-1)
 )。的价值();  // 5 

只要有一些有意义的合成操作,数据的形状就没有关系。 对于列表或字符串,它可以是串联的。 对于DSP,可能是信号求和。 当然,对于相同的数据,许多不同的操作可能有意义。 问题是,哪种操作最能代表构图的概念? 换句话说,哪种操作最受惠是这样的:

  const结果= compose(
  值1,
  值2,
  值3
 ); 

Moneysafe是一个开源库,实现了这种类型的可组合功能数据类型。 JavaScript的Number类型不能准确表示美元的某些分数。

  .1 + .2 === .3 //否 

Moneysafe通过将美元升为美分来解决问题:

 npm install --save moneysafe 

然后:

 从'moneysafe'导入{$}; $(。1)+ $(。2)=== $(。3).cents;  //正确 

分类帐语法利用了Moneysafe将值提升为可组合函数的事实。 它公开了一个简单的功能组合实用程序,称为分类帐:

 从“ moneysafe”导入{$};
从'moneysafe / ledger'导入{$$,subtractPercent,addPercent}; $$(
   $(40),
   $(60),
   //减去折扣
  减去Percent(20),
   //加税
   addPercent(10)
 )。$;  // 88 

返回的值是提款金额类型的值。 它公开了方便的.$ getter,它将内部浮点美分值转换为美元,四舍五入到最接近的美分。

结果是一个直观的界面,用于执行分类账式的货币计算。

克隆资金安全:

  git clone git@github.com:ericelliott / moneysafe.git 

运行安装程序:

  npm安装 

使用监视控制台运行单元测试。 他们都应该通过:

  npm run watch 

在新的终端窗口中,删除实现:

  rm source / moneysafe.js &&触摸source / moneysafe.js 

再次查看监视控制台测试。 您应该会看到一个错误。

您的任务是使用单元测试和文档作为指南从头开始重新实现moneysafe.js

我已经为成员录制了一个由7部分组成的视频演练系列。 这是第一集:


埃里克·埃利奥特 Eric Elliott) 《编程JavaScript应用程序》 (O’Reilly)和 《通过埃里克·埃利奥特学习JavaScript》的作者 他为 Adobe Systems Zumba Fitness 《华尔街日报》 ESPN BBC 和顶级录音艺术家(包括 Usher Frank Ocean Metallica等)的 软件经验做出了贡献

他将大部分时间都花在与世界上最美丽的女人在一起的任何地方工作。