【问题标题】:Getting every Friday until a certain date, but in a functional style?每个星期五直到某个日期,但以实用的风格?
【发布时间】:2015-03-01 09:53:11
【问题描述】:

例如,我想获取从现在到 30 天后每个星期五的日期。

目前,我可以利用下划线库和 moment.js 来做到这一点。但结果是超级冗长和烦人的程序/命令。观察:

var initDate = moment().day("Friday");
var endDate = moment().add(30, 'days');

var result = [];
result.push(initDate);

while (_.last(result).isBefore(endDate)) {
    var x = _.last(result);
    result.push(nextWeek(x));
}

alert(result);    // answer here

// create a new moment from given moment and add 7 days
function nextWeek(initMoment) {
    var x = moment(initMoment);
    return x.add(7,'days');    
}

这是对应的小提琴:http://jsfiddle.net/aafsh5xa/

我想知道是否有一种方法可以使用类似 Haskell 的功能,例如列表推导或无限列表(惰性求值),以使其更加简洁。可能是这样的:

var initDate = moment();
var endDate = moment().add(30,days);

var everyFriday = genLazyList(initDate, nextFridayFrom);
var result = _.filter(everyFriday, function(input){ return input.isBefore(endDate); });

请注意,everyFriday 是由 genLazyList 生成的无限列表,并且在调用 _.filter() 函数之前不会对其进行评估。而 nextFridayFrom() 是 genLazyList 用来制作惰性列表的函数。

【问题讨论】:

  • 也许只有我,但这不应该是相当简单的-> jsfiddle.net/aafsh5xa/1
  • 我专门询问使用函数式编程技术来完成它。你的小提琴和我的小提琴一样不起作用。
  • 简而言之,在 JS 中没有办法做到这一点,genLazyList 将在调用时执行和评估,并从现在到永恒的每个星期五返回,没有办法延迟评估它直到它通过过滤器运行。
  • 我找到了一种使用这个库的方法:streamjs.org 给我一些时间,我会写一个答案,按照我的建议去做。

标签: javascript haskell functional-programming underscore.js lazy-evaluation


【解决方案1】:

JS 有儒略日期库吗?这会让事情变得简单得多。

这里是一些伪 Haskell 代码,假设我们有以下函数:

toJulianDate :: Date -> Int
toDate :: Int -> Date
dayOfWeek :: Date -> Int     -- 0 = Sun, 1 = Mon, ... 5 = Fri, etc.

那么我们可以这样写:

everyFridayBetween :: Date -> Date -> [Date]
everyDridayBetween d1 d2 = map toDate [jfriday, jfriday+7..j2]
  where
    j1 = toJulianDate d1
    j2 = toJulianDate d2
    dow = dayOfWeek d1
    jfriday = if dow <= 5 then j1+(5-dow) else j1 + 7 + (5-dow)

【讨论】:

  • 这是一种非常有趣的方法!
【解决方案2】:

我想知道是否有一种方法可以使用类似 Haskell 的功能,例如列表推导或无限列表(惰性求值),以使其更加简洁。

在 ES6 中,您将能够对无限列表使用生成器函数(specMDN)。

与此同时,函数式编程中的循环通常是通过递归来完成的,对吧?所以:

function getFridays(f, dt, days) {
  if (days > 0) {
    if (dt.getDay() === 5) {
      f.push(new Date(dt));
    }
    dt.setDate(dt.getDate() + 1);
    f = getFridays(f, dt, days - 1);
  }
  return f;
}

var fridays = getFridays([], new Date(), 30);
document.body.innerHTML = '<pre>' + JSON.stringify(fridays, null, 4) + '</pre>';

不过,那个版本有副作用(它的两个参数是在函数中状态发生变化的对象)。我不精通函数式编程,但我知道要避免副作用,所以也许:

function getFridays(date, days) {
  var f, newDate;
  
  f = [];
  if (days > 0) {
    if (date.getDay() === 5) {
      f.push(new Date(date));
    }
    newDate = new Date(date);
    newDate.setDate(newDate.getDate() + 1);
    return f.concat(getFridays(newDate, days - 1));
  }
  return f;
}

var fridays = getFridays(new Date(), 30);
document.body.innerHTML = '<pre>' + JSON.stringify(fridays, null, 4) + '</pre>';

显然内存效率要低得多(就 GC 流失而言),但如果我理解正确的话,在 FP 中没有副作用比内存/GC 效率更重要。

【讨论】:

  • 请注意...即使在 Haskell 之类的语言中,您也可以改变状态。重要的考虑因素是被变异的状态是否在函数范围之外不可见,从而保持引用透明性。请参阅这个关于 ST Monad 的答案,它允许我们在静态保证此属性的同时改变状态。 stackoverflow.com/a/8197275/536017
  • @danem:对。这就是我发布第二个示例的原因:由第一个示例引起的突变都将在函数外部可见,例如副作用。
  • 当然,但我的意思是,考虑到这一点,更有效的实现是可能的。例如:jsfiddle.net/6wsa0usq/1。这只是您作为闭包实现的第一个示例。更进一步,我们有一个有效的例子,它几乎与发布在线程上的命令式实现相同。 lpaste.net/121435 我想说的是,您不必求助于递归或任何其他方式来获得 FP 的好处。 :)
  • @danem:我的第一个示例不是“实现为闭包”(除了 所有 JavaScript 函数都是闭包之外的任何方式)。这只是一个递归函数。递归:我认为这就是您在 FP 中执行循环的方式。那么,您是否只在 FP 中执行正常循环?
  • 不,我从未说过您的第一个示例是作为闭包实现的。我说我发布的示例 (jsfiddle.net/6wsa0usq/1) 本质上是您的第一个示例,但是工作是在闭包中完成的,以“无副作用”的方式改变状态。抱歉,我应该说“显式递归”。我的观点是,通过使用像“forM”这样的函数(实际上是递归定义的),我们在某些情况下可以编写与使用命令式语言相同的代码。我只是想指出,递归本身并不一定是可取的。
【解决方案3】:

请注意,我在回答中使用了 stream.jsmoment.js。所以你必须在你的 HTML 中包含以下内容(注意我的语法是玉):

script(src="path/to/stream.js")
script(src="path/to/moment.js")

这是我的代码:

var initDate = moment().day("Friday");
var endDate = moment().add(30,'days');

function allFridays() {
    return new Stream(initDate, function(){
            return allFridays().map(function(date){return moment(date).add(7,'days')});
        }
    );    
}

allFridays().takeWhile(function(h){return h.isBefore(endDate)}).print();

或者我最喜欢的口味,coffeescript:

initDate = moment().day('Friday')
endDate = moment().add(30, 'days')

allFridays = ->
  new Stream(initDate, ->
    allFridays().map (date) ->
      moment(date).add 7, 'days'
)

allFridays().takeWhile((h) -> h.isBefore endDate).print()

这不是很漂亮吗?

快速解释:

  1. 我制作了一个无限的时刻(日期)对象流,代表从现在到永远的所有星期五
  2. 我提取(即检索)此流的元素直到第一个时刻,它不代表我的 endDate 之前的时间。

就是这样!

请注意takeWhile() 函数尚未在stream.js 官方版本中实现,但我只是从它的lib 文件夹中复制并粘贴了它。查看它的 git repo here

【讨论】:

  • 整洁。这是我的看法(使用stream.js),没有moment.js 并且适用于所有工作日:jsfiddle.net/KooiInc/0L02La7v
  • 不错!那里有一些有用的想法,@KooiInc。
【解决方案4】:

ES6 生成器功能的一些概念验证

function *fridays13th() {
  var nextFriday = getNextFriday(new Date())

  while (true) {
    if (isFriday13th(nextFriday)) {
      yield new Date(nextFriday)
    }

    nextFriday = getNextFriday(nextFriday)
  }

  function isFriday13th(day) {
    return ((day.getDay() === 5) && (day.getDate() === 13))
  }

  function getNextFriday(d) {
    return new Date(d.setDate(d.getDate() + (5 - d.getDay() > 0 ? 5 - d.getDay() : 12 - d.getDay())))
  }
}


function generator(fn) {
  var res = []
  var g = fn()

  return {
    take: function(max) {
      for (var i = 0; i < max; i += 1) {
        var r = g.next()
        res.push(r.value)
      }
      return res
    }
  }
}


var fridays = generator(fridays13th).take(5)


document.body.innerHTML = '<pre>' + JSON.stringify(fridays, null, 4) + '</pre>';

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-02
    • 1970-01-01
    • 2011-06-25
    • 2020-09-03
    相关资源
    最近更新 更多