我应该如何在函数式编程中根据不同的条件使用不同的逻辑对对象内部的列表进行排序

问题描述 投票:0回答:1

考虑到我们有一个'api_fetchData'从服务器获取数据,并且基于路由,它可以是表或树,并且我们有两种状态,我们应该根据接收到的数据来更新它们。而且我应该注意,如果数据是表,我们应该按优先级对记录进行排序。我想知道我应该如何以函数式编程方式做到这一点(例如使用ramda或任何一个...)

 const tree=null;

const table = null:

//表

 {
        type: "table",
        records: [
          {
            id: 1,
            priority: 15
          },
          {
            id: 2,
            priority: 3
          }
        ]
}

//树

{
        type: "tree",
        children: [
          {
            type: "table",
            records: [
              {
                id: 1,
                priority: 15
              },
              {
                id: 2,
                priority: 3
              }
            ]
          }
        ]
  }

这是我所做的:

//定义工具

  const Right = x => ({
    map: f => Right(f(x)),
    fold: (f, g) => g(x),
    inspect: x => `Right(${x})`
  });
  const Left = x => ({
    map: f => Left(x),
    fold: (f, g) => f(x),
    inspect: x => `Left(${x})`
  });
  const fromNullable = x => (x != null ? Right(x) : Left(null));
  const state = reactive({
    path: context.root.$route.path,
    tree: null,
    table: null
  });

//最终代码:

  const fetchData = () => {
    return fromNullable(mocked_firewall[state.path])
      .map(data =>
        data.type === "tree"
          ? (state.tree = data)
          : (state.table = R.mergeRight(data, {
              records: prioritySort(data.records)
            }))
      )
      .fold(
        () => console.log("there is no data based on selected route"),
        x => console.log(x)
      );
  };
javascript vue.js functional-programming ramda.js
1个回答
0
投票

您在这里有几个问题-

  1. 安全地访问不确定对象上的深层嵌套数据
  2. 使用功能技术对复杂数据数组进行排序
  3. 安全地并且不可变地>>更新不确定对象上的深层嵌套数据] >>

    1。

  4. 使用.map.chain是低级的,当语法变得痛苦时,应添加其他抽象-
const state =
  { tree:
      { type: "sequoia"
      , children: [ "a", "b", "c" ]
      }
  }

recSafeProp(state, [ "tree", "type" ])
  .getOrElse("?") // "sequoia"

recSafeProp(state, [ "tree", "foobar" ])
  .getOrElse("?") // "?"

recSafeProp(state, [ "tree", "children", 0 ])
  .getOrElse("?") // "a"

recSafeProp(state, [ "tree", "children", 1 ])
  .getOrElse("?") // "b"

recSafeProp(state, [ "tree", "children", 99 ])
  .getOrElse("?") // "?"

我们可以轻松实现recSafeProp,并在此过程中发明了自己的便利功能safeProp

const { Nothing, fromNullable } =
  require("data.maybe")

const recSafeProp = (o = {}, props = []) =>
  props.reduce // for each props as p
    ( (r, p) => // safely lookup p on child
        r.chain(child => safeProp(child, p))
    , fromNullable(o) // init with Maybe o
    )

const safeProp = (o = {}, p = "") =>
  Object(o) === o // if o is an object
    ? fromNullable(o[p]) // wrap o[p] Maybe
    : Nothing()  // o is not an object, return Nothing

2。

Comparison模块。您似乎对功能模块很熟悉,所以让我们直接进入->
const { empty, map } =
  Comparison

const prioritySort =
  map(empty, record => record.priority || 0)

// or...
const prioritySort =
  map(empty, ({ priority = 0}) => priority)

myarr.sort(prioritySort)

如果您想要不可变的排序–

const isort = (f, [ ...copy ]) =>
  copy.sort(f)

const newArr =
  isort(origArr, proritySort)

这里是Comparison模块。在this Q&A中引入。如果您有兴趣查看如何以实用的方式制作复杂的分拣机,请在此处进行检查。

const Comparison =
  { empty: (a, b) =>
      a < b ? -1
        : a > b ? 1
          : 0
  , map: (m, f) =>
      (a, b) => m(f(a), f(b))
  , concat: (m, n) =>
      (a, b) => Ordered.concat(m(a, b), n(a, b))
  , reverse: (m) =>
      (a, b) => m(b, a)
  , nsort: (...m) =>
      m.reduce(Comparison.concat, Comparison.empty)
  }

const Ordered =
  { empty: 0
  , concat: (a, b) =>
      a === 0 ? b : a
  }

合并分类器–

const { empty, map, concat } =
  Comparison

const sortByProp = (prop = "") =>
  map(empty, (o = {}) => o[prop])

const sortByFullName =
  concat
    ( sortByProp("lastName")  // primary: sort by obj.lastName
    , sortByProp("firstName") // secondary: sort by obj.firstName
    )

data.sort(sortByFullName) // ...

可分类分类器–

const { ..., reverse } =
  Comparison

// sort by `name` then reverse sort by `age`&nbsp;&ndash;
data.sort(concat(sortByName, reverse(sortByAge)))

功能原理–

// this...
concat(reverse(sortByName), reverse(sortByAge))

// is the same as...
reverse(concat(sortByName, sortByAge))

以及–

const { ..., nsort } =
  Comparison

// this...
concat(sortByYear, concat(sortByMonth, sortByDay))

// is the same as...
concat(concat(sortByYear, sortByMonth), sortByDay)

// is the same as...
nsort(sortByYear, sortByMonth, sortByDay)

3。

此处到达的最低悬挂果实为Immutable.js。如果以后有更多时间,我将展示针对特定问题的lo-fi方法。

您的解决方案对我来说似乎太过分了。

函数式编程涉及很多事情,并且没有简洁的定义,但是如果您的主要工作单元是纯函数,并且您不对数据进行突变,那么您就可以成为函数式程序员。有时使用Either monad有一些强大的好处。但是这段代码看起来更像是试图将Either放到毫无意义的地方。

下面是编写此代码的一种建议方法。但是在此过程中,我必须做出一些假设。首先,当您讨论从服务器获取数据时,我假设它必须使用Promisesasync-await或更好的替代方法(例如TasksFutures)以异步模式运行。我进一步假设您提到mocked_firewall的地方就是您进行实际的异步调用的地方。 (您的示例代码将其视为可以在path中查找结果的对象;但是,我无法真正模仿模拟真实服务。)还有一个假设:fold(() => log(...), x => log(x))位什么都不是必不可少的,仅是您的代码已完成应做的演示。

考虑到所有这些,我编写了一个版本,其中有一个对象type映射到datastate到新状态的函数,以及一个中央函数fetchData,它需要类似[ C0](或真的像我对它的更改以返回Promise)并返回一个接受mocked_firewall并返回新的state的函数。

看起来像这样:

state
// State-handling functions
const handlers = {
  tree: (data, state) => ({... state, tree: data}),
  table: (data, state) => ({... state, table: {...data, records: prioritySort (data .records)}})
}

// Main function
const fetchData = (firewall) => (state) => 
  firewall (state)
    .then ((data) => (handlers [data .type] || ((data, state) => state)) (data, state))


// Demonstrations
fetchData (mocked_firewall) ({path: 'foo', table: null, tree: null})
  .then (console .log) // updates the tree

fetchData (mocked_firewall) ({path: 'bar', table: null, tree: null})
  .then (console .log) // updates the table
  
fetchData (mocked_firewall) ({path: 'baz', table: null, tree: null})
  .then (console .log) // server returns type we can't handle; no update
  
fetchData (mocked_firewall) ({path: 'qux', table: null, tree: null})
  .then (console .log) // server returns no `type`; no update
.as-console-wrapper {min-height: 100% !important; top: 0}

您会注意到这不会更改<script> // Dummy functions -- only for demonstration purposes const prioritySort = (records) => records .slice (0) .sort (({priority: p1}, {priority: p2}) => p1 - p2) const mocked_firewall = ({path}) => Promise.resolve({ foo: {type: "tree", children: [{ type: "table", records: [{id: 1, priority: 15}, {id: 2, priority: 3}] }]}, bar: { type: 'table', records: [{id: 1, priority: 7}, {id: 2, priority: 1}, {id: 3, priority: 4}] }, baz: {type: 'unknown', z: 45}, }[path] || {}) </script>;而是返回该状态的新对象。我看到它被标记为state,据我了解,这不是Vue的工作方式。 (实际上,这是我实际上并未真正使用Vue的原因之一。)您可以轻松地更改处理程序以使用vue之类的内容来更新state,甚至跳过返回值。但是,不要让任何FP狂热者抓住您这样做;请记住,函数式程序员非常熟悉“关键算法技术,例如递归和前提条件。” 1

您还标记了此tree: (data, state) => {state.tree = data; return state}。我是Ramda的创始人之一,也是忠实的粉丝,但我看到Ramda仅在边缘提供帮助。例如,我包括了您提到但未提供的ramda.js的原始版本。 Ramda版本可能会更好,例如

prioritySort

类似地,如果您不想改变状态,我们可以使用Ramda版本重写处理程序函数,可能会简化一点。但这将是次要的。对于主要功能,我看不到使用Ramda函数可以改善的任何内容。

有一个很好的可测试性参数,我们不仅应该将const prioritySort = sortBy (prop ('priority')) 传递给主函数,还应该传递firewall对象。这完全取决于您,但这确实使更容易独立地模拟和测试零件。如果您不想这样做,则完全可以像这样在主函数中内联它们:

handlers

但是最后,我发现原始文档更容易阅读,无论是原样还是作为主要函数的另一个参数提供的处理程序。


1

原始报价来自const fetchData = (firewall) => (state) => firewall (state) .then ((data) => (({ tree: (data, state) => ({... state, tree: data}), table: (data, state) => ({ ... state, table: {...data, records: prioritySort(data .records)} }) }) [data .type] || ((data, state) => state)) (data, state)) ,但我最擅长James Iry出色的Verity Stob

0
投票

您的解决方案对我来说似乎太过分了。

函数式编程涉及很多事情,并且没有简洁的定义,但是如果您的主要工作单元是纯函数,并且您不对数据进行突变,那么您就可以成为函数式程序员。有时使用Either monad有一些强大的好处。但是这段代码看起来更像是试图将Either放到毫无意义的地方。

下面是编写此代码的一种建议方法。但是在此过程中,我必须做出一些假设。首先,当您讨论从服务器获取数据时,我假设它必须使用Promisesasync-await或更好的替代方法(例如TasksFutures)以异步模式运行。我进一步假设您提到mocked_firewall的地方就是您进行实际的异步调用的地方。 (您的示例代码将其视为可以在path中查找结果的对象;但是,我无法真正模仿模拟真实服务。)还有一个假设:fold(() => log(...), x => log(x))位什么都不是必不可少的,仅是您的代码已完成应做的演示。

© www.soinside.com 2019 - 2024. All rights reserved.