diff --git a/karma.conf.js b/karma.conf.js index e131253..e119fa4 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -14,7 +14,7 @@ module.exports = function (config) { colors: true, logLevel: config.LOG_INFO, autoWatch: false, - browsers: ['PhantomJS'], + browsers: ['ChromeHeadless'], singleRun: true, concurrency: 6e6 }); diff --git a/src/monet.js b/src/monet.js index d391f5b..c9c359a 100644 --- a/src/monet.js +++ b/src/monet.js @@ -65,6 +65,16 @@ Free: 'Free' } + function doNotation(monad, co) { + function iterate (el) { + return el.done ? monad.unit(el.value) : el.value.bind(function (val) { + return iterate(it.next(val)) + }) + } + var it = co() + return iterate(it.next()) + } + function setType(target, typeName) { target[TYPE_KEY] = 'Monet/' + typeName } @@ -633,6 +643,8 @@ return maybe.toList() } + Maybe.do = doNotation.bind(null, Maybe) + Maybe.fn = Maybe.prototype = { init: function (isValue, val) { this.isValue = isValue @@ -757,6 +769,8 @@ return Success(v) } + Validation.do = doNotation.bind(null, Validation) + Validation.fn = Validation.prototype = { init: function (val, success) { this.val = val @@ -918,6 +932,8 @@ }) } + IO.do = doNotation.bind(null, IO) + IO.fn = IO.prototype = { init: function (effectFn) { if (!isFunction(effectFn)) { @@ -973,6 +989,8 @@ return new Either.fn.init(val, false) } + Either.do = doNotation.bind(null, Either) + Either.fn = Either.prototype = { init: function (val, isRightValue) { this.isRightValue = isRightValue diff --git a/test/either-spec.js b/test/either-spec.js index 632514b..c1b19ff 100644 --- a/test/either-spec.js +++ b/test/either-spec.js @@ -293,4 +293,51 @@ describe('An Either', function () { }) + describe('do notation', function() { + it('will yield the Right value inside a generator', function () { + Either.do(function*() { + var a = yield Either.Right(5) + expect(a).toBe(5) + return a + }) + }) + + it('will return the value wrapped in Right', function() { + var result = Either.do(function* () { + var a = yield Either.Right(5) + var b = yield Either.Right(1) + return a + b + }) + + expect(result).toBeRightWith(6) + }) + + it('will return Left when Left is yielded inside the generator', function() { + var result = Either.do(function* () { + var a = yield Either.Right(5) + var b = yield Either.Left(10) + return a + b + }) + + expect(result).toBeLeftWith(10) + }) + + it('will short-circuit the generator when Left is yielded', function() { + var spyBeforeLeft = jasmine.createSpy() + var spyAfterLeft = jasmine.createSpy() + + var result = Either.do(function* () { + spyBeforeLeft() + var a = yield Either.Left(5) + spyAfterLeft() + var b = yield Either.Right(3) + return a + b + }) + + expect(spyBeforeLeft).toHaveBeenCalled() + expect(spyAfterLeft).not.toHaveBeenCalled() + expect(result).toBeLeftWith(5) + }) + }) + }) diff --git a/test/io-spec.js b/test/io-spec.js index 4ef116d..93247dd 100644 --- a/test/io-spec.js +++ b/test/io-spec.js @@ -51,4 +51,28 @@ describe('An IO monad', function () { expect(effect.ap).toBe(effect['fantasy-land/ap']) }) }) + + describe('do notation', function() { + it('will yield the Success value inside a generator', function () { + var effect = IO.do(function* () { + var a = yield IO.of(5) + expect(a).toBe(5) + return a + }) + + effect.run() + }) + + it('will return the value wrapped IO', function() { + var effect = IO.do(function* () { + var a = yield IO.of(5) + var b = yield IO.of(1) + return a + b + }) + + effect.map(function (val) { + expect(val).toBe(6) + }).run() + }) + }) }) diff --git a/test/maybe-spec.js b/test/maybe-spec.js index dde4fba..beea23e 100644 --- a/test/maybe-spec.js +++ b/test/maybe-spec.js @@ -449,5 +449,50 @@ describe('A Maybe', function () { }) }) + describe('do notation', function() { + it('will yield the Just value inside a generator', function () { + Maybe.do(function*() { + var a = yield Maybe.Just(5) + expect(a).toBe(5) + return a + }) + }) + it('will return the value wrapped in Just', function() { + var result = Maybe.do(function* () { + var a = yield Maybe.Just(5) + var b = yield Maybe.Just(1) + return a + b + }) + + expect(result).toBeSomeMaybeWith(6) + }) + + it('will return Nothing when Nothing is yielded inside the generator', function() { + var result = Maybe.do(function* () { + var a = yield Maybe.Just(5) + var b = yield Maybe.Nothing() + return a + b + }) + + expect(result).toBeNoneMaybe() + }) + + it('will short-circuit the generator when Nothing is yielded', function() { + var spyBeforeNothing = jasmine.createSpy() + var spyAfterNothing = jasmine.createSpy() + + var result = Maybe.do(function* () { + spyBeforeNothing() + var a = yield Maybe.Nothing() + spyAfterNothing() + var b = yield Maybe.Just(3) + return a + b + }) + + expect(spyBeforeNothing).toHaveBeenCalled() + expect(spyAfterNothing).not.toHaveBeenCalled() + expect(result).toBeNoneMaybe() + }) + }) }) diff --git a/test/validation-spec.js b/test/validation-spec.js index f1429ba..095a455 100644 --- a/test/validation-spec.js +++ b/test/validation-spec.js @@ -281,4 +281,51 @@ describe('A Validation', function () { }) + describe('do notation', function() { + it('will yield the Success value inside a generator', function () { + Validation.do(function*() { + var a = yield Validation.Success(5) + expect(a).toBe(5) + return a + }) + }) + + it('will return the value wrapped in Success', function() { + var result = Validation.do(function* () { + var a = yield Validation.Success(5) + var b = yield Validation.Success(1) + return a + b + }) + + expect(result).toBeSuccessWith(6) + }) + + it('will return Fail when Left is yielded inside the generator', function() { + var result = Validation.do(function* () { + var a = yield Validation.Success(5) + var b = yield Validation.Fail(10) + return a + b + }) + + expect(result).toBeFailureWith(10) + }) + + it('will short-circuit the generator when Fail is yielded', function() { + var spyBeforeFail = jasmine.createSpy() + var spyAfterFail = jasmine.createSpy() + + var result = Validation.do(function* () { + spyBeforeFail() + var a = yield Validation.Fail(5) + spyAfterFail() + var b = yield Validation.Success(3) + return a + b + }) + + expect(spyBeforeFail).toHaveBeenCalled() + expect(spyAfterFail).not.toHaveBeenCalled() + expect(result).toBeFailureWith(5) + }) + }) + })