每周五到某个日期,但功能性强?

Getting every Friday until a certain date, but in a functional style?

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

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

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');    
}

这里是对应的fiddle: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 用来制作惰性列表的函数。

I was wondering if there's a way to use Haskell-like features such as list comprehensions or infinite lists (lazy evaluation) to make this a lot more concise.

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

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

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 中没有副作用比 memory/GC 效率更重要。

JS 有 Julian 日期库吗?这样事情就简单多了。

这里是一些伪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)

请注意,我在回答中使用了 stream.js and moment.js。因此,您必须在 HTML 中执行以下操作(请注意,我的语法是用 jade 编写的):

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. 我获取(即检索)此流的元素直到第一时刻,这并不代表我的结束日期之前的时间。

就是这样!

请注意,takeWhile() 功能尚未在官方 stream.js 版本中实现,但我只是从其 lib 文件夹中复制并粘贴它。请参阅其 git 回购 here

具有 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>';