Skip to content

Instantly share code, notes, and snippets.

@ryanguill
Last active March 31, 2018 00:41
Show Gist options
  • Save ryanguill/ac2201f9a13feb9ec07e4ed1aaade82e to your computer and use it in GitHub Desktop.
Save ryanguill/ac2201f9a13feb9ec07e4ed1aaade82e to your computer and use it in GitHub Desktop.
An implementation of Option and Result in cfml as a closure. Works on ACF 10, 11 and 2016, Lucee 4.5 and 5.
<cfscript>
function Option () {
var NIL = "__NIL__";
var _isNil = function (input) {
return (isNull(input) || (isSimpleValue(input) && input == Nil));
};
var _isSome = false;
var _val = NIL;
var m = {
some: function (val) {
if (_isNil(val)) {
throw("value must be non-null");
}
_val = arguments.val;
_isSome = true;
return m;
},
none: function () {
_isSome = false;
_val = NIL;
return m;
},
of: function (val) {
if (_isNil(val)) {
return m.none();
}
return m.some(arguments.val);
},
isSome: function () {
return _isSome;
},
isNone: function () {
return !_isSome;
},
toString: function () {
if (_isSome) {
return 'Some( ' & _val & ' )';
}
return 'None()';
},
unwrap: function () {
if (_isSome) {
return _val;
}
throw("Cannot unwrap a none.")
},
unwrapOr: function (required other) {
if (_isSome) {
return _val;
}
return other;
},
unwrapOrElse: function (required fn) {
if(_isSome) {
return _val;
}
return fn();
},
map: function (required fn) hint="returns an option" {
if (_isSome) {
return Option().of(fn(_val));
}
return Option().none();
},
filter: function (required conditionFn) hint="returns an option" {
if (_isSome) {
if (conditionFn(_val)) {
return m;
}
}
return Option().none();
},
forEach: function (required fn) hint="for side effects" {
if (_isSome) {
fn(_val);
}
},
match: function (struct options = {}) hint="pass a struct with two keys with functions for values, `some` and `none`" {
if (_isSome) {
if (!structKeyExists(options, "some")) {
return m.toString();
}
return options.some(_val);
}
if (!structKeyExists(options, "none")) {
return m.toString();
}
return options.none();
}
};
m.get = m.unwrap;
m.getOr = m.unwrapOr;
m.getOrElse = m.unwrapOrElse;
return m;
}
function Result () {
var _isOk = false;
var _okVal = "";
var _errVal = "";
var r = {
ok: function (okVal) {
_isOk = true;
_okVal = okVal;
return r;
},
err: function (errVal) {
_isOk = false;
_errVal = errVal;
return r;
},
isOk: function () {
return _isOk;
},
isErr: function () {
return !_isOk;
},
toString: function () {
if (_isOk) {
return "Ok( " & _okVal & " )";
}
return "Err( " & _errVal & " )";
},
getOk: function () {
if (_isOk) {
return Option().some(_okVal);
}
return Option().none();
},
getErr: function () {
if (!_isOk) {
return Option().some(_errVal);
}
return Option().none();
},
unwrap: function () {
if (_isOk) {
return _okVal;
}
throw("Called unwrap on a Result.Err");
},
unwrapErr: function () {
if (_isOk) {
throw("Called unwrapErr on a Result.Ok");
}
return _errVal;
},
unwrapOr: function (required other) {
if (_isOk) {
return _okVal;
}
return other;
},
unwrapOrElse: function (required fn) {
if (_isOk) {
return _okVal;
}
return fn(_errVal);
},
map: function (required fn) hint="returns a Result" {
/*
if isOk, transform the value, otherwise no-op
*/
if (_isOk) {
return Result().ok(fn(_okVal));
}
return r;
},
mapErr: function (required fn) hint="returns a Result" {
/*
if isErr, transform the value, otherwise no-op
*/
if (_isOk) {
return r;
}
return Result().err(fn(_errVal));
},
match: function (struct options = {}) hint="pass a struct with two keys with functions for values, `ok` and `err`" {
if (_isOk) {
if (!structKeyExists(options, "ok")) {
return r.toString();
}
return options.ok(_okVal);
}
if (!structKeyExists(options, "err")) {
return r.toString();
}
return options.err(_errVal);
}
};
return r;
}
t = testSuite("Maybe()");
expect = t.expect;
some1 = Option().some(1);
expectASome(some1, 1);
of1 = Option().of(1);
expectASome(of1, 1);
none1 = Option().none();
expectANone(none1);
writeOutput(t.printResults());
/* ========== */
writeOutput("<hr />");
t = testSuite("Result()");
expect = t.expect;
ok1 = Result().ok(1);
expectAnOk(ok1, 1);
err2 = Result().err(2);
expectAnErr(err2, 2);
writeOutput(t.printResults());
/* ========== */
writeOutput("<hr />");
/* function to test some and none */
private function expectASome(option, expectedValue) {
expect(option.isSome(), true, "isSome() on a some is true");
expect(option.isNone(), false, "isNone() on a none is false");
expect(option.unwrap(), expectedValue, "unwrap() on a some gives the value");
expect(option.unwrapOr(2), expectedValue, "unwrapOr() on a some gives the value");
expect(option.unwrapOrElse(function() {
return 3;
}), expectedValue, "unwrapOrElse() on a some gives the value");
expect(option.get(), expectedValue, "get() on a some gives the value");
expect(option.getOr(2), expectedValue, "getOr() on a some gives the value");
expect(option.getOrElse(function() {
return 3;
}), expectedValue, "getOrElse() on a some gives the value");
expect(option.toString(), 'Some( ' & expectedValue & ' )', "toString on a some gives the value wrapped in a Some() label");
expect(option.match({
some: function (value) {
return 'was a some';
}, none: function () {
return 'was a none';
}
}), 'was a some', "match on a some returns the value of the some: function");
expect(option.match(), option.toString(), "match on a some without a some function returns the same as toString()");
if (isNull(arguments.secondPass)) {
var mapped = option.map(function (value) {
return 100;
});
expectASome(option=mapped, expectedValue=100, secondPass=true);
var filtered = option.filter(function (value) {
return true;
});
expectASome(option=filtered, expectedValue=expectedValue, secondPass=true);
var filtered = option.filter(function (value) {
return false;
});
expectANone(option=filtered, secondPass=true);
var sideEffect = "";
option.forEach(function (value) {
sideEffect = value;
});
expect(sideEffect, expectedValue, "forEach on a some should execute the function.");
}
}
private function expectANone(option) {
expect(option.isSome(), false, "isSome() on a none is true");
expect(option.isNone(), true, "isNone() on a none is false");
var didThrow = false;
try {
option.unwrap();
} catch (any e) {
didThrow = true;
}
expect(didThrow, true, "unwrap() on a none throws an error");
expect(option.unwrapOr(2), 2, "unwrapOr() on a none gives the passed value");
expect(option.unwrapOrElse(function() {
return 3;
}), 3, "unwrapOrElse() on a none gives the value of the passed function");
var didThrow = false;
try {
option.get();
} catch (any e) {
didThrow = true;
}
expect(didThrow, true, "get() on a none throws an error");
expect(option.getOr(2), 2, "getOr() on a none gives the passed value");
expect(option.getOrElse(function() {
return 3;
}), 3, "getOrElse() on a none gives the value of the passed function");
expect(option.toString(), 'None()', "toString on a none returns `None()`");
expect(option.match({
some: function (value) {
return 'was a some';
}, none: function () {
return 'was a none';
}
}), 'was a none', "match on a none returns the value of the none: function");
expect(option.match(), option.toString(), "match on a none without a none function returns the same as toString()");
if (isNull(arguments.secondPass)) {
var mapped = option.map(function (value) {
return 100;
});
expectaNone(option=mapped, secondPass=true);
var filtered = option.filter(function (value) {
return true;
});
expectANone(option=filtered, secondPass=true);
var filtered = option.filter(function (value) {
return false;
});
expectANone(option=filtered, secondPass=true);
var sideEffect = "";
option.forEach(function (value) {
sideEffect = value;
});
expect(sideEffect, "", "forEach on a none should _not_ execute the function.");
}
}
private function expectAnOk (result, expectedOkValue) {
expect(result.isOk(), true, "isOk on an ok should be true");
expect(result.isErr(), false, "isErr on an ok should be false");
expect(result.toString(), "Ok( " & expectedOkValue & " )", "toString() on an ok should return the value wrapped in an Ok() label");
expect(result.getOk().isSome(), true, "getOk() on an ok should return a Maybe.some");
expect(result.getOk().unwrap(), expectedOkValue, "getOk() should return a Maybe that when upwrapped returns the expected value");
expect(result.getErr().isSome(), false, "getErr() on an ok should return a Maybe.none");
expect(result.unwrap(), expectedOkValue, "unwrap on an Ok should return the wrapped value");
var didThrow = false;
try {
result.unwrapErr();
} catch (any e) {
didThrow = true;
}
expect(didThrow, true, "unwrapErr() on an Ok should throw an error");
expect(result.unwrapOrElse(function (err) {
return err;
}), expectedOkValue, "unwrapOrElse() on an Ok should return the ok value");
if (isNull(arguments.secondPass)) {
var mapped = result.map(function (x) {
return "mappedOkValue";
});
expect(mapped.unwrap(), "mappedOkValue", "mapping an Ok should apply the function");
expectAnOk(result=mapped, expectedOkValue="mappedOkValue", secondPass=true);
var mappedErr = result.mapErr(function(x) {
return "mappedErrValue";
});
expect(mappedErr.unwrap(), expectedOkValue, "mapErr an Ok should not apply the function");
expectAnOk(result=mappedErr, expectedOkValue=expectedOkValue, secondPass=true);
}
expect(result.match({
ok: function (okVal) {
return 'was an ok';
},
err: function (errVal) {
return 'was an err';
}
}), 'was an ok', "match on a ok returns the value of the ok: function");
expect(result.match(), result.toString(), "match on a ok without a ok function returns the same as toString()");
}
private function expectAnErr (result, expectedErrValue) {
expect(result.isOk(), false, "isOk on an err should be false");
expect(result.isErr(), true, "isErr on an err should be true");
expect(result.toString(), "Err( " & expectedErrValue & " )", "toString() on an err should return the value wrapped in an Err() label");
expect(result.getOk().isSome(), false, "getOk() on an err should return a Maybe.none");
expect(result.getErr().isSome(), true, "getErr() on an err should return a Maybe.some");
expect(result.getErr().unwrap(), expectedErrValue, "getErr() should return a Maybe that when upwrapped returns the expected value");
expect(result.unwrapErr(), expectedErrValue, "unwrap on an Err should return the wrapped value");
var didThrow = false;
try {
result.unwrap();
} catch (any e) {
didThrow = true;
}
expect(didThrow, true, "unwrap() on an Err should throw an error");
expect(result.unwrapOrElse(function (err) {
return "foo";
}), "foo", "unwrapOrElse() on an Err should return the mapped Err value");
if (isNull(arguments.secondPass)) {
var mapped = result.map(function (x) {
return "mappedOkValue";
});
expect(mapped.unwrapErr(), expectedErrValue, "mapping an Err should not apply the function");
expectAnErr(result=mapped, expectedErrValue=expectedErrValue, secondPass=true);
var mappedErr = result.mapErr(function(x) {
return "mappedErrValue";
});
expect(mappedErr.unwrapErr(), "mappedErrValue", "mapErr on an Err should apply the function");
expectAnErr(result=mappedErr, expectedErrValue="mappedErrValue", secondPass=true);
}
expect(result.match({
ok: function (okVal) {
return 'was an ok';
},
err: function (errVal) {
return 'was an err';
}
}), 'was an err', "match on a err returns the value of the err: function");
expect(result.match(), result.toString(), "match on a err without a err function returns the same as toString()");
}
/* test suite */
private function testSuite (label, supressPassMessages = true) {
var _results = {
pass: 0,
fail: 0,
failures: []
};
var t = {
expect: function (required any testValue, required any targetValue, string message = "", any dumpVar) {
if (arguments.testValue != arguments.targetValue) {
arguments.message &= "<br /> expected [" & encodeForHtml(toString(arguments.targetValue)) & "] <br /> but received [" & encodeForHtml(toString(testValue)) & "] <br />";
var cs = callStackGet();
var lineRef = "";
var template = "";
var lineNumber = "";
for (var line in cs) {
if (structKeyExists(line, "Function") && line["Function"] == getFunctionCalledName()) {
continue;
}
if (structKeyExists(line, "Function") && findNoCase("closure", line["Function"])) {
continue;
}
if (findNoCase("trycf", cgi.SERVER_NAME)) {
lineRef = lineRef & "<br />" & "line";
} else {
if (structKeyExists(line, "Template")) {
lineRef = lineRef & "<br />" & line["Template"];
template = line["template"];
}
}
if (structKeyExists(line, "LineNumber")) {
lineRef = lineRef & ":" & line["LineNumber"];
lineNumber = line["lineNumber"];
}
break;
}
_results.fail += 1;
arrayAppend(_results.failures, arguments.message & trim(lineRef));
writeOutput('<pre style="font-weight:bold; color:red;">Fail! ' & arguments.message & trim(lineRef) & '</pre><br />');
} else {
_results.pass += 1;
if (!supressPassMessages) {
writeoutput('<pre style="color:green;">Pass: #message#</pre>');
}
}
},
getResults: function () {
return _results;
},
printResults: function () {
return label & ' results: <span style="color:green;">' & _results.pass & ' passed</span>, <span style="font-weight:bold; color:red;">' & _results.fail & ' failed.</span>';
}
};
return t;
}
</cfscript>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment