这是什么功能模式?如果有的话 [英] What functional-pattern is this? If any?

查看:45
本文介绍了这是什么功能模式?如果有的话的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现自己一遍又一遍,正在写这样的代码,并且在思考.必须有一个已知的模式,但是要仔细研究Ramda等不同功能库的文档.我找不到比赛.我该怎么用?

I find myself over and over again, writing code like this, and is thinking. There must be a known pattern for this, but plowing through documentation of different functional libs like Ramda. I can't quite find a match. What should I use?

var arrayOfPersons = [{ firstName: 'Jesper', lastName: 'Jensen', income: 120000, member: true }/* .... a hole lot of persons */];

function createPredicateBuilder(config) {

  return {

    build() {
      var fnPredicate = (p) => true;

      if (typeof config.minIncome == 'number') {
        fnPredicate = (p) => fnPredicate(p) && config.minIncome <= p.income;
      }
      if (typeof config.member == 'boolean') {
        fnPredicate = (p) => fnPredicate(p) && config.member === p.member;
      }
      // .. continue to support more predicateparts.
    },

    map(newConfig) {
      return createPredicateBuilder({ ...config, ...newConfig });
    }
  };
}

var predicateBuilder = createPredicateBuilder({});

// We collect predicates
predicateBuilder = predicateBuilder.map({ minIncome: 200000 });
// ...
predicateBuilder = predicateBuilder.map({ member: false });

// Now we want to query...
console.log(arrayOfPersons.filter(predicateBuilder.build()));

我创建一个构建器实例,并调用map,并使用build/map方法创建一个返回到对象的新实例.状态在功能范围中捕获.将来的某个时候,我想获取我收集的函数(或结果).

I create a builder instance, and calls to map, creates a new instance returning in an object with build/map methods. The state is captured in the functions scope. Sometime in the future, I want to get my collected function (or result).

我认为这是FP,但是这种模式是什么?是否有任何库使它更容易?

I think this is FP, but what is this pattern, and is there any libs that makes it easyer?

我的灵感源自事物的名称(构建者/构建者)使我蒙昧吗?

Is my oop-inspired naming of things (builder/build) blinding me?

推荐答案

功能编程与模式无关,而与法律有关.法律使程序员可以像数学家可以对方程式那样对程序进行推理.

Functional programming is less about patterns and more about laws. Laws allow the programmer to reason about their programs like a mathematician can reason about an equation.

让我们看一下加数.加法是一个二进制运算(它需要两个个数字),并且总是产生另一个数字.

Let's look at adding numbers. Adding is a binary operation (it takes two numbers) and always produces another number.

1 + 2 = 3
2 +1 = 3

1 +(2 + 3)= 6
(1 + 2)+ 3 = 6

(((1 + 2)+ 3)+ 4 = 10
(1 + 2)+(3 + 4)= 10
1 +(2 + 3)+ 4 = 10
1 +(2 +(3 + 4))= 10

我们可以按任意顺序添加数字,但仍然得到相同的结果.此属性是关联性,它构成了关联律的基础.

We can add numbers in any order and still get the same result. This property is associativity and it forms the basis of the associative law.

添加零有些有趣,或者是理所当然的.

Adding zero is somewhat interesting, or taken for granted.

1 + 0 = 1
0 +1 = 1

3 + 0 = 3
0 + 3 = 3

在任何数字上添加零不会更改该数字.这就是 identity元素.

Adding zero to any number will not change the number. This is known as the identity element.

这两个东西( 1 )是一个关联的二进制运算,而( 2 )是一个标识元素,组成了 monoid .

These two things, (1) an associative binary operation and (2) an identity element, make up a monoid.

如果可以的话...

  1. 将谓词编码为域的元素
  2. 为元素创建二进制操作
  3. 确定身份元素

...然后我们将获得属于monoid类别的好处,从而使我们能够以 equalal 方式对程序进行推理.没有学习的模式,只有遵守的法律.

... then we receive the benefits of belonging to the monoid category, allowing us to reason about our program in an equational way. There's no pattern to learn, only laws to uphold.

1.建立域

正确获取数据非常棘手,在像JavaScript这样的多范式语言中更是如此.这个问题与函数式编程有关,因此 functions 将是一个不错的选择.

Getting your data right is tricky, even more so in a multi-paradigm language like JavaScript. This question is about functional programming though so functions would be a good go-to.

在您的程序中...

build() {
  var fnPredicate = (p) => true;

  if (typeof config.minIncome == 'number') {
    fnPredicate = (p) => fnPredicate(p) && config.minIncome <= p.income;
  }
  if (typeof config.member == 'boolean') {
    fnPredicate = (p) => fnPredicate(p) && config.member === p.member;
  }
  // .. continue to support more predicateparts.
},

...我们看到了程序级别和数据级别的混合.该程序经过硬编码,只能理解可能具有以下特定键( minIncome member )及其各自类型( number boolean ),以及用于确定谓词的比较操作.

... we see a mixture of the program level and the data level. This program is hard-coded to understand only an input that may have these specific keys (minIncome, member) and their respective types (number and boolean), as well the comparison operation used to determine the predicate.

让我们保持非常简单.让我们来一个静态谓词

Let's keep it really simple. Let's take a static predicate

item.name === "Sally"

如果我希望使用相同的谓词,但使用不同的项目进行比较,则可以将此表达式包装在函数中,然后将 item 用作函数的参数.

If I wanted this same predicate but compared using a different item, I would wrap this expression in a function and make item a parameter of the function.

const nameIsSally = item =>
  item.name === "Sally"
  
console .log
  ( nameIsSally ({ name: "Alice" })    // false
  , nameIsSally ({ name: "Sally" })    // true
  , nameIsSally ({ name: "NotSally" }) // false
  , nameIsSally ({})                   // false
  )

此谓词易于使用,但仅可用于检查名称​​ Sally .我们通过将表达式包装在函数中并将 name 用作函数的参数来重复此过程.这种通用技术称为抽象",在函数式编程中一直被使用.

This predicate is easy to use, but it only works to check for the name Sally. We repeat the process by wrapping the expression in a function and make name a parameter of the function. This general technique is called abstraction and it's used all the time in functional programming.

const nameIs = name => item =>
  item.name === name

const nameIsSally =
  nameIs ("Sally")

const nameIsAlice =
  nameIs ("Alice")
  
console .log
  ( nameIsSally ({ name: "Alice" })    // false
  , nameIsSally ({ name: "Sally" })    // true
  , nameIsAlice ({ name: "Alice" })    // true
  , nameIsAlice ({ name: "Sally" })    // false
  )

如您所见,我们包装的表达式已经是一个函数并不重要.JavaScript具有对函数的 first-class 支持,这意味着可以将它们视为值.返回函数或将函数作为输入的程序称为高阶函数.

As you can see, it doesn't matter that the expression we wrapped was already a function. JavaScript has first-class support for functions, which means they can be treated as values. Programs that return a function or receive a function as input are called higher-order functions.

以上,我们的谓词表示为具有任意类型( a )值并产生 boolean 的函数.我们将其表示为 a->布尔值.因此,每个谓词都是我们域的一个元素,并且该域是所有函数 a->布尔值.

Above, our predicates are represented as functions which take a value of any type (a) and produce a boolean. We will denote this as a -> Boolean. So each predicate is an element of our domain, and that domain is all functions a -> Boolean.

2.二进制运算

我们将再进行一次抽象练习.让我们采用静态组合谓词表达式.

We'll do the exercise of abstraction one more time. Let's take a static combined predicate expression.

p1 (item) && p2 (item)

我可以通过将该表达式包装在函数中并将 item 作为函数的参数,来将其用于其他项目.

I can re-use this expression for other items by wrapping it in a function and making item a parameter of the function.

const bothPredicates = item =>
  p1 (item) && p2 (item)

但是我们希望能够组合任何谓词.同样,我们将要重用的表达式包装在函数中,然后为变量分配参数,这一次是 p1 p2 .

But we want to be able to combine any predicates. Again, we wrap the expression we want to re-use in an function then assign parameter(s) for the variable(s), this time for p1 and p2.

const and = (p1, p2) => item =>
  p1 (item) && p2 (item)

在继续之前,让我们检查域并确保我们的二进制操作是正确的.二进制操作必须:

Before we move on, let's check our domain and ensure our binary operation and is correct. The binary operation must:

  1. 以我们域中的两(2)个元素( a->布尔)为输入
  2. 将域中的元素作为输出返回
  3. 该操作必须是关联的: f(a,b) == f(b,a)
  1. take as input two (2) elements from our domain (a -> Boolean)
  2. return as output an element of our domain
  3. the operation must be associative: f(a,b) == f(b,a)

实际上,接受域 p1 p2 的两个元素.返回值是 item =>.... 是接收 item 并返回 p1(item)&&p2(项目).每个谓词均接受单个值并返回布尔值.这简化为 Boolean&&我们知道的布尔值是另一个布尔值.总而言之,接受两个谓词并返回一个新的谓词,这正是二进制运算必须执行的操作.

Indeed, and accepts two elements of our domain p1 and p2. The return value is item => ... which is a function receiving an item and returns p1 (item) && p2 (item). Each is a predicate that accepts a single value and returns a Boolean. This simplifies to Boolean && Boolean which we know is another Boolean. To summarize, and takes two predicates and returns a new predicate, which is precisely what the binary operation must do.

const and = (p1, p2) => item =>
  p1 (item) && p2 (item)

const nameIs = x => item =>
  item.name === x
  
const minIncome = x => item =>
  x <= item.income

const query =
  and
    ( nameIs ("Alice")
    , minIncome (5)
    )
  
console .log
  ( query ({ name: "Sally", income: 3})    // false
  , query ({ name: "Alice", income: 3 })   // false
  , query ({ name: "Alice", income: 7 })   // true
  )

3.身份元素

identity元素添加到任何其他元素后,不得更改该元素.因此,对于任何谓词 p 和谓词身份元素 empty ,必须满足以下条件

The identity element, when added to any other element, must not change the element. So for any predicate p and the predicate identity element empty, the following must hold

和(p,空)== p
和(empty,p)== p

我们可以将空谓词表示为接受任何元素的函数,并且总是返回 true .

We can represent the empty predicate as a function that takes any element and always returns true.

const and = (p1, p2) => item =>
  p1 (item) && p2 (item)
  
const empty = item =>
  true
  
const p = x =>
  x > 5
  
console .log
  ( and (p, empty) (3) === p (3)  // true
  , and (empty, p) (3) === p (3)  // true
  )

法律权力

现在我们有了一个二进制运算和一个identity元素,我们可以组合任意数量的谓词.我们定义 sum ,它将我们的monoid直接插入 reduce .

Now that we have a binary operation and an identity element, we can combine an arbitrary amount of predicates. We define sum which plugs our monoid directly into reduce.

// --- predicate monoid ---
const and = (p1, p2) => item =>
  p1 (item) && p2 (item)

const empty = item =>
  true
  
const sum = (...predicates) =>
  predicates .reduce (and, empty)  // [1,2,3,4] .reduce (add, 0) 

// --- individual predicates ---
const nameIs = x => item =>
  item.name === x

const minIncome = x => item =>
  x <= item.income

const isTeenager = item =>
  item.age > 12 && item.age < 20
  
// --- demo ---
const query =
  sum
    ( nameIs ("Alice")
    , minIncome (5)
    , isTeenager
    )

console .log
  ( query ({ name: "Sally", income: 8, age: 14 })   // false
  , query ({ name: "Alice", income: 3, age: 21 })   // false
  , query ({ name: "Alice", income: 7, age: 29 })   // false
  , query ({ name: "Alice", income: 9, age: 17 })   // true
  )

空总和谓词仍返回有效结果.这就像匹配所有结果的空查询.

The empty sum predicate still returns a valid result. This is like the empty query that matches all results.

const query =
  sum ()

console .log
  ( query ({ foo: "bar" })                          // true
  )


免费便利

使用函数对谓词进行编码也使它们在其他方面也很有用.如果您有一组项目,则可以直接在 .find .filter 中使用谓词 p .当然,对于使用 sum 创建的谓词也是如此.

Using functions to encode our predicates makes them useful in other ways too. If you have an array of items, you could use a predicate p directly in .find or .filter. Of course this is true for predicates created using and and sum too.

const p =
  sum (pred1, pred2, pred3, ...)

const items =
  [ { name: "Alice" ... }
  , { name: "Sally" ... }
  ]

const firstMatch =
  items .find (p)

const allMatches =
  items .filter (p)


将其设置为模块

您不想定义诸如 add sum empty 之类的全局变量.打包此代码时,请使用某种模块.

You don't want to define globals like add and sum and empty. When you package this code, use a module of some sort.

// Predicate.js
const add = ...

const empty = ...

const sum = ...

const Predicate =
  { add, empty, sum }

export default Predicate

使用时

import { sum } from './Predicate'

const query =
  sum (...)

const result =
  arrayOfPersons .filter (query)


测验

请注意我们的谓词身份元素与&&

Notice the similarity between our predicate identity element and the identity element for &&

T&&?== T
?&&T == T
F&&?== F
?&&F == F

我们可以将上面的所有?替换为 T ,等式将成立.下面,您认为 ||| 的标识元素是什么?

We can replace all ? above with T and the equations will hold. Below, what do you think the identity element is for ||?

T ||?== T
?||T == T
F ||?== F
?||F == F

* (二进制乘法)的标识元素是什么?

What's the identity element for *, binary multiplication?

n *吗?= n
?* n = n

数组或列表的标识元素如何?

How about the identity element for arrays or lists?

concat(l,?)== l
concat(?,l)== l

玩得开心吗?

我想您会喜欢相反函子.在同一个竞技场中,换能器.有一个演示演示了如何也围绕这些低级模块构建高级API.

I think you'll enjoy contravariant functors. In the same arena, transducers. There's a demo showing how to build a higher-level API around these low-level modules too.

这篇关于这是什么功能模式?如果有的话的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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