Java Script: различия между версиями
imported>Supportadmin |
imported>Vix |
||
(не показаны 53 промежуточные версии 2 участников) | |||
Строка 1: | Строка 1: | ||
== | ==Фишечки== | ||
*[[JavaScript на новый лад]] | |||
<hr> | |||
* Техника скрытой идентификации системы и браузера. | |||
... | |||
Определение производится на основе выделения свойственных для разных браузеров шаблонов состояния свойств в JavaScript и характеристик времени выполнения операций, зависящих от особенностей работы JIT, CPU и механизмов выделения памяти. Определение свойств производится через генерацию списка всех объектов, доступных из JavaScript. Как оказалось число объектов напрямую коррелирует с браузерным движком и его версией. | |||
function getProperties(o) { | |||
var result = []; | |||
while (o !== null) { | |||
result = result.concat(Reflect.ownKeys(o)); | |||
o = Object.getPrototypeOf(o); | |||
} | |||
return result; | |||
:= | } | ||
... | |||
Например, для Firefox заявлена поддержка 2247 свойств, в то время как реальное число рекурсивно определённых свойств составляет 15709 (в Tor Browser - 15639), для Chrome заявлено 2698 свойств, а реально предлагается 13570 (в Chrome для Android - 13119). Число и значения свойств меняются от версии к версии браузера и при применении различных операционных систем. | |||
Значения и наличие тех или иных свойств можно использовать для определения типа ОС. Например, в Kubuntu свойство window.innerWidth выставляется в значение 1000, а в Windows 10 - в 1001. В Windows доступно свойство window.navigator.activeVRDisplays, а в Linux его нет. Для Android предоставляется множество специфичных вызовов, но нет window.SharedWorker. Для идентификации операционной системы также предлагается использовать анализ параметров WebGL, состояние которых зависит от драйверов. Кроме того, вызов WEBGL_debug_renderer_infoextension позволяет получить информацию о движке отрисовки OpenGL, который для каждой операционной системы разный. | |||
: | |||
Для определения CPU применяется оценка различий во времени выполнения различных типовых блоков кода, обработка которых зависит от архитектуры набора команд с учётом поведения JIT (определяется сколько регистров CPU будет задействовано и в каких случаях JIT сгенерирует эффективный код с оптимизациями и вовлечением расширенных инструкций, а когда нет). Для определения типа системы распределения памяти и операционной системы также измеряется различие времени выделения памяти для различных структур, по которым можно судить о размере блоков памяти. | |||
Определённые в ходе выполнения скрипта параметры сравниваются с эталонными значениями, свойственными для заранее протестированных окружений. В ходе проверки разработанная техника позволила точно определить 40 различных тестовых окружений, определив версии используемых браузеров, производителя CPU, применяемую операционную систему и факт запуска на реальном оборудовании или в виртуальной машине. | |||
* [http://www.opennet.ru/opennews/art.shtml?num=50872 (c) источник] | |||
<hr> | |||
==Как бы учебник== | |||
*[[Операторы JavaScript]] | |||
*[[Массивы JavaScript]] | |||
*[[Объекты JavaScript]] | |||
*[[Особенности функций в JavaScript]] | |||
**[[Возвратные функции]] | |||
*<span style="color:darkgred; background:yellow">[[Базовые Namespace паттерны JavaScript]]</span> | |||
*<span style="color:darkgred; background:yellow">[[Шаблоны проектирования JavaScript]]</span> | |||
*[[ООП JavaScript - наследование]] | |||
*[[Область видимости переменной в Javascript]] | |||
*[[Клиентский (браузерный) JavaScript]] | |||
**[[Javascript синхронный и асинхронный запрос]] | |||
*[[JavaScript Strict Mode]] | |||
---- | |||
*[[Масштабируемые JavaScript приложения]] | |||
*[[10 несуразностей и секретов JavaScript]] | |||
*[[Используем console на полную]] | |||
---- | |||
*[[Java Script модули]] | |||
*[[Java Script - Base64 Encoding/Decoding]] | |||
==Крайне полезная информация== | |||
Операторы | Это надо обработать | ||
*[http://jsperf.com/array-prototype-push-apply-vs-concat/20 Тесты производительности циклов, работы с массивами и т.д.] | |||
*'''[http://bonsaiden.github.com/JavaScript-Garden/ru/ JavaScript Гарден]''' | |||
*[http://habrahabr.ru/post/117069/ Модульный подход в JavaScript] | |||
*[http://canegor.urc.ac.ru/gui_for_script/articles/random_sort.htm Случайное перемешивание массива] | |||
*[http://habrahabr.ru/post/131714/ Javascript наследование для чайников] | |||
*[http://javascript.ru/basic/operators Операторы, их особенности в JS] | |||
*[http://www.w3schools.com/js/js_comparisons.asp JavaScript Comparison and Logical Operators] | |||
*'''[http://habrahabr.ru/post/138773/ Путь асинхронного самурая]''' | |||
*[http://habrahabr.ru/post/137818/ «Лапша» из callback-ов — будьте проще] | |||
*[http://habrahabr.ru/post/97042/ Как избавиться от пристрастия к синхронности] | |||
*[http://habrahabr.ru/post/137778/ Спагетти в последовательном вызове асинхронных функций. Теория и практика] | |||
*'''[http://hashcode.ru/questions/87809/javascript-xmlhttprequest-%D0%B2-%D1%86%D0%B8%D0%BA%D0%BB%D0%B5 javascript - XMLHttpRequest в цикле]''' | |||
*[[javascript - XMLHttpRequest подробный анализ примера]] | |||
*[http://javascript.ru/blog/tenshi/mnogopotochnyi-yavaskript Многопоточный яваскрипт] | |||
* [http://javascript.ru/tutorial/object/inheritance работаем с объектами в примерах] | |||
* '''[http://javascript.ru/unsorted/top-10-functions 10 лучших функций на JavaScript]''' - в комментах тьма полезностей | |||
* [http://webo.in/articles/habrahabr/28-chain-javascript-calls-optimization/ ОПТИМИЗИРУЕМ JS] | |||
* http://pyramidin.narod.ru/clientref13/ -очень хорошо описаны функции, их стандартные методы, свойства и использование | |||
* [http://wiki.dieg.info/doku.php/pobitovye_ili_porazrjadnye_operacii Побитовые или поразрядные операции] в конце для js | |||
* [http://ruseller.com/lessons.php?rub=28&id=1445 Прототипы в JavaScript] | |||
* [http://habrahabr.ru/post/49245/ Стыкуем компоненты в JavaScript] | |||
* [http://webo.in/articles/habrahabr/77-coupling-async-scripts/ Стыкуем асинхронные скрипты] | |||
* [http://learn.javascript.ru/gcc-closure-library GCC: интеграция с Google Closure Library] | |||
* [http://creativejs.com/resources/requestanimationframe/ 3d -javascipt] | |||
* [http://stepansuvorov.com/blog/2012/07/%D0%BF%D0%B8%D1%88%D0%B5%D0%BC-%D1%81%D0%B2%D0%BE%D0%B9-uploader-%D1%81-%D0%BD%D1%83%D0%BB%D1%8F-%D0%BD%D0%B0-javascript-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D1%8F-fileapi-%D1%87%D0%B0-3/ свой Uploader с нуля] | |||
* [http://alljs.ru/articles/document-write document-write] | |||
* [http://www.jaguar-developers.com/category/articles/javascript_dom/ создаем элементы DOM] | |||
* [http://www.jstoolbox.com/skripty/rabota-s-massivami/zapolnenie-massiva-odinakovymi-znacheniyami/ Заполнение массива одинаковыми значениями] | |||
* [http://webo.in/articles/habrahabr/78-javascript-constructions-performance/ Производительность простых и сложных конструкций в JavaScript] | |||
* [https://tproger.ru/translations/why-js-map-doesnt-work/ Почему функция map не работает с некоторыми массивами в JavaScript и что с этим делать] | |||
==обработка событий в примерах== | |||
: | *[http://on-line-teaching.com/js/js.events.keyboard.htm События клавиатуры и мыши] | ||
: | *[http://javascript.ru/tutorial/events/properties Свойства объекта событие] | ||
: | *[http://www.tigir.com/javascript.htm разные примеры...] | ||
: | *[https://developer.mozilla.org/ru/docs/JavaScript/Guide/Regular_Expressions регулярные выражения - шаблоны] | ||
==Контекст== | |||
: | *[http://habrahabr.ru/post/149516/ Ключевое слово this в javascript — учимся определять контекст на практике] | ||
: | *[http://habrahabr.ru/post/149581/ Привязка контекста (this) к функции в javascript и частичное применение функций] | ||
: | *[http://habrahabr.ru/post/103760/ Правильный захват контекста в Javascript] | ||
*[http://habrahabr.ru/qa/5846/ JavaScript: что делает Function.call.apply(…)?] | |||
*[http://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%B0%D1%82%D0%B5%D0%B3%D0%B8%D1%8F_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F) Стратегия (шаблон проектирования)] | |||
[http://javascript.ru/ecma/part10 Контексты исполнения] | |||
*[http://habrahabr.ru/post/112069/ предсказание элемента формы] | |||
==Библиотеки== | |||
* [http://qooxdoo.org/ JavaScript-фреймворк qooxdoo 1.6] - новое слово в вебинтерфейсах | |||
* [http://www.opennet.ru/opennews/art.shtml?num=32573 Новость про qooxdoo 1.6] | |||
* [http://www.leemon.com/crypto/BigInt.js JS библиотека для работы с большими числами][http://www.leemon.com/crypto/BigInt.html Демо] | |||
* [https://threejs.org/ 3D библиотека java script] | |||
=== | * [https://zzz.dog/ pseudo-3D engine for canvas & SVG] | ||
* [http://learn.javascript.ru/lib Мини-библиотека функций] | |||
===Несколько интересных проектов с помощью которых можно динамически создавать векторные рисунки.=== | |||
* [http://y3x.ru/2010/12/artisanjs/ Статья - Пробуем ремесло рисования с помощью ArtisanJS] | |||
* [http://artisanjs.com/ Страница разработчика ArtisanJS] | |||
* [http://jcscript.com/ Высокопроизводительная библиотека динамического рисования Html5 - полностью самодостаточная] | |||
* [http://raphaeljs.com Кросс-браузерное решение] | |||
* [http://kangax.github.com/fabric.js/kitchensink/ Поддерживается как свободное рисование так и работа с векторными формами] | |||
* [https://habr.com/ru/post/174603/ Pixi.js — 2D движок с прозрачной поддержкой WebGL] | |||
==Полезные трюки== | |||
http://habrahabr.ru/post/155093/ | |||
==Как работает Array.prototype.slice.call== | |||
Для того, чтобы из аргументов JavaScript функции "отрезать" первые Х значений, используется метод: | |||
Array.prototype.slice.call(arguments, X); | |||
Например такая функция вернет "3,4": | |||
<nowiki>(function(){ | |||
var args = Array.prototype.slice.call(arguments, 2); | |||
alert(args); // Returns: 3,4 | |||
})(1, 2, 3, 4);</nowiki> | |||
Таким образом, такой подход используется когда нужно срезать входящие параметры функции JavaScript. | |||
==Array.prototype.concat.apply== | |||
Для объединения более двух массивов используется concat(). | |||
<nowiki>var a = [1, 2], b = ["x", "y"], c = [true, false]; | |||
var d = a.concat(b, c); | |||
console.log(d); // [1, 2, "x", "y", true, false];</nowiki> | |||
For concatenating just two arrays, using push.apply() can be used instead for the case of adding elements from one array to the end of another without producing a new array. With slice() it can also be used instead of concat() but there appears to be no performance advantage from doing this. | |||
<nowiki>var a = [1, 2], b = ["x", "y"]; | |||
a.push.apply(a, b); | |||
console.log(a); // [1, 2, "x", "y"];</nowiki> | |||
==Современный метод bind== | |||
< | В современном JavaScript для привязки функций есть метод bind. Он поддерживается большинством современных браузеров, за исключением IE<9, но легко эмулируется. | ||
Этот метод позволяет привязать функцию к нужному контексту, и даже к аргументам. | |||
Синтаксис bind: | |||
<nowiki>var wrapper = func.bind(context[, arg1, arg2...])</nowiki> | |||
var | *'''func''' Произвольная функция | ||
*'''wrapper''' Функция-обёртка, которую возвращает вызов bind. Она вызывает func, фиксируя контекст и, если указаны, первые аргументы. | |||
*'''context''' Обертка wrapper будет вызывать функцию с контекстом this = context. | |||
*'''arg1, arg2, …''' Если указаны аргументы arg1, arg2... — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове. | |||
Простейший пример, фиксируем только this: | |||
<nowiki>function f() { | |||
alert(this.name); | |||
} | |||
var user = { name: "Вася" }; | |||
var f2 = f.bind(user); | |||
f2(); // выполнит f с this = user | |||
</nowiki> | |||
Использование для привязки метода sayHi к новому объекту User: | |||
<nowiki> | |||
function User() { | |||
this.id = 1; | |||
this.sayHi = function() { | |||
alert(this.id); | |||
}.bind(this); | |||
} | |||
var user = new User(); | |||
setTimeout(user.sayHi, 1000); // выведет "1"</nowiki> | |||
==Object.defineProperty или как сделать код капельку лучше== | |||
[http://habrahabr.ru/post/150571/ оригинал статьи] [http://msdn.microsoft.com/ru-ru/library/ie/dd548687(v=vs.94).aspx тоже ребята старались] | |||
Этот краткий пост-заметку или температурный бред (в Одессе похолодало, да) хочу посвятить такой прекрасной функции, как Object.defineProperty (и Object.defineProperties). Активно использую её уже около двух месяцев, так как поддержка старых браузеров (в том числе и IE8) в проекте, который я сейчас реализую, не требуется (завидуйте). | |||
Как положено статье на хабре, приведу краткое описание того, что она делает. Object.defineProperty добавляет новое свойство, обладающее неким нестандартным для обычного свойства поведением, и принимает три аргумента: | |||
*Объект, который мы модифицируем, добавляя новое свойство | |||
*Свойство (строка), которое, собственно, хотим добавить | |||
*Дескриптор: объект, содержащий «настройки» нового свойства, например аццессоры (геттер, сеттер) | |||
Дескриптор может содержать следующие свойства: | |||
*value (любое значение: строка, функция...) — значение, которое получит определяемое свойство объекта (геттер и сеттер в данном случае определить нельзя) | |||
*writable (true/false) — можно ли перезаписать значение свойства (аццессоры тоже не доступны) | |||
*get (функция) — геттер (value и writable определить нельзя) | |||
*set (функция) — сеттер (value и writable определить нельзя) | |||
} | *configurable (true/false) — можно ли переопределить дескриптор (использовать Object.defineProperty над тем же свойством) | ||
*enumerable (true/false) — будет ли свойство перечисляться через for..in и доступно в Object.keys (плохая формулировка) | |||
<nowiki>var o = {}; | |||
Object.defineProperty(o, "a", {value : 37, | |||
writable : true, | |||
enumerable : true, | |||
configurable : true}); | |||
var bValue; | |||
Object.defineProperty(o, "b", {get : function(){ return bValue; }, | |||
set : function(newValue){ bValue = newValue; }, | |||
enumerable : true, | |||
configurable : true});</nowiki> | |||
Лучше меня объяснит MDN [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty Object/defineProperty]. Благо, даже английский знать не надо, и так всё понятно. | |||
Если нужно определить сразу несколько свойств, можно использовать Object.defineProperties, который принимает два аргумента: объект, требующий изменений и объект с определяемыми ключами. | |||
MDN: [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperties Object/defineProperties]. | |||
<nowiki> | |||
// Код сперт с MDN | |||
Object.defineProperties(obj, { | |||
"property1": { | |||
value: true, | |||
writable: true | |||
}, | |||
"property2": { | |||
value: "Hello", | |||
writable: false | |||
} | |||
// etc. etc. | |||
});</nowiki> | |||
Теперь соль. Чего я вообще решил это запостить? | |||
Так как в упомянутом выше проекте мне приходится использовать defineProperty не просто активно, а очень активно, код стал, мягко говоря, некрасивым. Пришла в голову простейшая идея (как я до этого раньше-то не додумался?), расширить прототип Object, сделав код сильно компактнее. Плохой тон, скажете вы, засерать прототип Object новыми методами. | |||
Откуда вообще взялось это мнение? Потому что все объекты унаследуют это свойство, которое, при обычной модификации прототипа, становится перечисляемым в for..in. На душе становится тепло, когда вспоминаешь о том, что я описал выше, а именно, о свойстве дескриптора enumerable. Действительно, расширив прототип таким образом: | |||
<nowiki>Object.defineProperty( Object.prototype, 'logOk' { | |||
value: function() { console.log('ok') }, | |||
enumerable: false | |||
});</nowiki> | |||
все объекты получат этот метод, но, при этом, он будет неперечисляемым (не нужно каждый раз использовать hasOwnProperty для проверки, есть ли такое свойство): | |||
<nowiki>var o = {a: 1, b: 2} | |||
for( var i in o ) console.log( i, o[ i ] ); | |||
> a 1 | |||
> b 2 | |||
o.logOk(); | |||
> ok</nowiki> | |||
Собственно то, ради чего я тут графоманю. | |||
Во-первых определим метод define, чтоб каждый раз не вызывать перегруженную, на мой взгляд, конструкцию. Во-вторых определим метод extendNotEnum, который расширяет объект неперечисляемыми свойствами. | |||
<nowiki>Object.defineProperties( Object.prototype, { | |||
define: { | |||
value: function( key, descriptor ) { | |||
if( descriptor ) { | |||
Object.defineProperty( this, key, descriptor ); | |||
} else { | |||
Object.defineProperties( this, key ); | |||
} | |||
return this; | |||
}, | |||
enumerable: false | |||
}, | |||
extendNotEnum: { | |||
value: function( key, property ) { | |||
if( property ) { | |||
this.define( key, { | |||
value: property, | |||
enumerable: false, | |||
configurable: true | |||
}); | |||
} else { | |||
for( var prop in key ) if( key.hasOwnProperty( prop ) ){ | |||
this.extendNotEnum( prop, key[ prop ] ); | |||
} | |||
} | |||
}, | |||
enumerable: false | |||
} | |||
}); | |||
</nowiki> | |||
Использование: | |||
<nowiki>var o = { a: 1 }; | |||
o.define( 'randomInt', { | |||
get: function() { | |||
return 42; | |||
} | |||
}); | |||
o.extendNotEnum({ | |||
b: 2; | |||
}); | |||
for( var i in o ) console.log( i, o[ i ] ); | |||
> a 1 | |||
> randomInt 42 | |||
console.log( o.b ); | |||
> 2 | |||
</nowiki> | |||
И пошла… Еще два удобных метода: | |||
var | <nowiki>Object.prototype.extendNotEnum({ | ||
extend: function() { | |||
var args = Array.prototype.slice.call( arguments ); | |||
args.unshift( this ); | |||
return $.extend.apply( null, args ); // если jQuery надоест, можно просто переписать под себя | |||
}, | |||
each: function( callback ) { | |||
return $.each( this, callback ); // аналогично | |||
} | |||
}); | |||
o.extend({c: 3}); // тупо добавляет новые свойства в объект | |||
o.each(function( key, value ) { | |||
// просто повторяет механизм $.each, перебирая все ключи и свойства | |||
});</nowiki> | |||
Добавлять новые свойства можно до бесконечности, никто вам за это руку не откусит. | |||
Вывод | |||
Играйте в Денди, пишите на Javascript, который становится всё лучше и лучше. | |||
комментарии | |||
Еще один финт ушами для тех, кто определяет свойства прототипа только однажды, после определения функции-конструктора: | |||
<nowiki>// запускается в консоли | |||
Object.defineProperty( Object.prototype, 'awesomeprototype', { | |||
set: function( object ) { | |||
for( var prop in object ) { | |||
Object.defineProperty( this.prototype, prop, { | |||
value: object[ prop ], | |||
enumerable: false | |||
}); | |||
} | |||
} | |||
}); | |||
var X = function() {} | |||
X.awesomeprototype = { | |||
method: function() { alert( 'ok' ) } | |||
}; | |||
var x = new X | |||
x.method() | |||
</nowiki> | |||
'''Стоит отметить что в литералах объектов свойства можно задавать через геттер и сеттер:''' | |||
<nowiki>var o = { | |||
__someProperty : 42, | |||
get someProperty() { return this.__someProperty; }, | |||
set someProperty(v) { this.__someProperty = v; } | |||
}; | |||
o.someProperty; // 42 | |||
o.someProperty = 56; | |||
o.someProperty; // 56 | |||
</nowiki> | |||
Ну это так, к слову :) | |||
function | ==deferred== | ||
var | *[http://habrahabr.ru/post/175947/ Асинхронные API и объект Deferred в деталях][https://github.com/StreetStrider/utils/tree/master/doc GitHub] | ||
*[http://javascript.ru/unsorted/async/deferred Объект Deferred.] | |||
*[http://www.smira.ru/2009/02/10/deferred-async-programming/ Асинхронное программирование: концепция Deferred] | |||
} | *[http://www.smira.ru/2009/02/24/more-about-deferred/ Deferred: все подробности] | ||
==Шаблонизация в JavaScript== | |||
*[http://learn.javascript.ru/templates Шаблонизация в JavaScript] | |||
==DOM== | |||
===dom2json=== | |||
http://coderepos.org/share/browser/lang/javascript/dom2json/dom2json.js?rev=30780 | |||
skips empty attributes that IE implicitly adds | |||
<nowiki>/* | |||
* $Id: dom2json.js,v 0.2 2008/06/15 07:04:10 dankogai Exp dankogai $ | |||
*/ | |||
(function(){ | |||
dom2json = function(dom){ | |||
var type = dom.nodeType; | |||
if (type == 3) return dom.nodeValue; | |||
if (type == 1){ | |||
var json = []; | |||
json.push(dom.nodeName); | |||
var attrs = dom.attributes; | |||
if (attrs.length){ | |||
var attr = {}; | |||
for (var i = 0, l = attrs.length; i < l; i++){ | |||
attr[attrs[i].name] = attrs[i].value; | |||
} | |||
if (0 /*@cc_on +1@*/){ | |||
attr['style'] = dom.style; | |||
} | |||
json.push(attr); | |||
} | |||
if (! dom.hasChildNodes()) return json; | |||
var kids = dom.childNodes; | |||
for (var i = 0, l = kids.length; i < l; i++){ | |||
var kjson = arguments.callee(kids[i]); | |||
if (kjson) json.push(kjson); | |||
} | |||
return json; | |||
} | |||
}; | |||
json2dom = function(json){ | |||
var i = 0; | |||
var tag = json[i++] | |||
var dom = document.createElement(tag); | |||
if (json[i].constructor == Object){ | |||
var attr = json[i++]; | |||
for (var name in attr){ | |||
dom.setAttribute(name, attr[name]); | |||
} | |||
} | |||
for (var l = json.length; i < l; i++){ | |||
dom.appendChild( | |||
json[i].constructor == Array | |||
? arguments.callee(json[i]) | |||
: document.createTextNode(json[i]) | |||
); | |||
} | |||
return dom; | |||
} | |||
})();</nowiki> | |||
===обход дерева DOM=== | |||
<nowiki>/** | |||
* Рекурсивное перечисление дочерних элементов | |||
* | |||
* @param DomNode node | |||
* Родительский элемент, чьи дочерние узлы нужно перечислять. | |||
* | |||
* @return void | |||
*/ | |||
function enumChildNodes(node) { | |||
// если нам передали элемент | |||
if (node && 1 == node.nodeType) { | |||
// берем его первый дочерний узел | |||
var child = node.firstChild; | |||
// пока узлы не закончились | |||
while (child) { | |||
// если этот узел является элементом | |||
if (1 == child.nodeType) { | |||
// что-то делаем с найденным элементом | |||
alert('элемент ' + child.tagName); | |||
// рекурсивно перечисляем дочерние узлы | |||
enumChildNodes(child); | |||
}; | |||
// переходим к следующему узлу | |||
child = child.nextSibling; | |||
}; | |||
}; | |||
}; | |||
// перечисляем содержимое body | |||
enumChildNodes(document.body);</nowiki> | |||
===Обход child nodes (потомков) определнного элемента в Javascript=== | |||
JavaScript на заметку Комментировать | |||
Речь о том как корректно обойти элементы потомки DOM для данного элемента, | |||
простите за тафтологию | |||
var object = document.getElementById('el'); | |||
for (var childItem in object.childNodes) { | |||
if (object.childNodes[childItem].nodeType == 1) | |||
object.childNodes[childItem].style.color = '#FF0000'; | |||
В данном примере мы берем некоторый элемент с id = 'el' и проходим по массиву | |||
его потомков (childNodes), при этом проверяем nodeType потомка, чтобы он был | |||
элементом страницы и если так, то окрашиваем его в КРАСНЫЙ цвет. | |||
В принципе на базе этой конструкции можно организовать рекурсивный обход дерева | |||
всех потомков заданного элемента, если конечно возникнет такая необходимость, но | |||
пока я представляю только упрощенный вариант. | |||
Здесь следует обратить внимание на проверку свойства nodeType == 1, дело в том | |||
что без этой проверки в обработку попадут и разрывы строк, т.е. символы "\n", | |||
которые тоже воспинимаются как ноды. Т.е. например конструкция вида: | |||
так быстрее и меньше кода | |||
var object = document.getElementById(‘el’).getElementsByTagName(‘*’); | |||
for(var i=0;object[i];i++){ | |||
object[i].style.color = ‘#FF0000′; | |||
Sun, getElementsByTagName не поддерживает ИЕ, смысл его использовать? | |||
К сожалению заметил, что данная конструкция в Opera выполняется некорректно | |||
(цикл for проходится два раза), никто с таким не встречался? Где-то всё же | |||
закралась моя ошибка или это ошибка браузера (проверял в 9.20, 9.50, 10.20), | |||
замечу, что в остальных браузерах всё выполняется верно. | |||
var object = document.getElementById(‘el’); | |||
for (var childItem in object.childNodes) { | |||
} | } | ||
var object=document.getElementById(‘el’); | |||
for(var i=0;i<object.childNodes.length;i++) { | |||
if (object.childNodes[i].nodeType == 1) { | |||
// тут что-либо делаем | |||
} | |||
} | |||
==Functional JavaScript== | |||
[http://functionaljavascript.blogspot.ru/2013/03/introduction-to-functional-javascript.html Источник] | |||
===Introduction to Functional JavaScript=== | |||
JavaScript was a functional programming language even before it got its name! Back in 1995 Netscape Communications the makers of the Netscape Browser realized that their browser needed a scripting language embedded in HTML. They hired Brendan Eich to embed the Scheme programming language in HTML. Scheme was a full fledged functional programming language. To his dismay “the powers that be” encouraged Brendan to come up with a more traditional programming language. His first cut was a language called “Mocha” in May 1995, and it retained that name till December 1995. It was first renamed “LiveScript” and soon after that Netscape and Sun did a license agreement and it was called “JavaScript”. But by then Brendan Eich had sneaked in some “functional programming” features into JavaScript. | |||
The story gets more complicated, and we will delve into it. Because this story will tell you why JavaScript is what it is today. Brendan Eich claims that hiring him to embed the Scheme language, might actually have been a “bait and switch operation”. But at that point in time Netscape was also negotiating with Sun to embed Java in the browser. Note that JavaScript was for embedding in HTML while Java was for embedding in the Browser. The idea was that Java would be used for component development while JavaScript would be used for lightweight scripting within HTML. | |||
We don't know what actually transpired, but the orders from above to Brendan where clear. The new scripting language must “look like Java” and must be “object based”. Any hopes Brendan might have harboured for Scheme, where now out of the window. We will see why. | |||
Programming Paradigms | |||
We can only speculate on what “look like Java” meant. However we can be certain that it had to be a “curly brace” language. Curly brace languages define statement blocks using curly braces, the “{“ and “}” characters. Indeed Java syntax was fashioned on another curly brace language “C++”, which in turn was fashioned on “C”. Here is how a for-loop looks in Java. | |||
<nowiki>for (int i = 0; i < 10; i++) { | |||
System.out.println("Hello"); | |||
System.out.println("World"); | |||
}</nowiki> | |||
The same for-loop in C. | |||
<nowiki>int i; | |||
for (i = 0; i < 10; i++) { | |||
printf("Hello\n"); | |||
printf("World\n"); | |||
}</nowiki> | |||
And the for-loop in JavaScript. | |||
<nowiki>for (var i = 0; i < 10; i++) { | |||
console.log("Hello"); | |||
console.log("World"); | |||
}</nowiki> | |||
Indeed JavaScript does look like Java. And C too. However curly braces where not the only implications for a language to “look like Java”. Java is an imperative/object oriented style programming language. JavaScript also had to be an imperative/object oriented style language. | |||
Programming languages are made up of operators, conditional statements, loop statements and functions. Having conditional statements and loop statements are hallmarks of an “imperative” style programming language. Functional style languages tend to support operators and functions only. | |||
It is interesting that none of the three languages, Java, C++ and C, were functional programming languages. While C was an imperative programming language, C++ and Java were Imperative/Objected Oriented programming languages. By now you would have guessed that the three programming paradigms (styles) were imperative, object oriented and functional. There is one more, the declarative paradigm. | |||
The differences between these paradigms are because of the foundations on which they were based. Imperative and object oriented programming are based on the “turing machine”. Functional programming is based on “lambda calculus” and declarative programming is based on “first order logic”. In this post we will look at the differences between, imperative, object oriented and functional programming at a more practical level. | |||
An imperative programming language is one in which program state change is achieved by executing a series of statements, and does flow control primarily using conditional statements, loop statements and function calls. The program given below is a simple implementation of the JavaScript Array.join method in an imperative manner. | |||
<nowiki>function simpleJoin(stringArray) { | |||
var accumulator = ''; | |||
for (var i=0, l=stringArray.length; i < l; i++) { | |||
accumulator = accumulator + stringArray[i]; | |||
} | |||
return accumulator; | |||
}</nowiki> | |||
The code above is straight forward. We iterate through an array and add each string element to the accumulator and return the accumulator. We will now rewrite this function in an object oriented manner. Since JavaScript has an Array class, we will add this method to the Array class, so that every instance of this class has access to this function. JavaScript use prototypal inheritance and so we add this function to the Array prototype. | |||
<nowiki>Array.prototype.simpleJoin = function() { | |||
var accumulator = ""; | |||
for (var i=0, l=this.length; i < l; i++) { | |||
accumulator = accumulator + this[i]; | |||
} | |||
return accumulator; | |||
}</nowiki> | |||
As we can see, the object oriented version is quite like the imperative version, except that the function (method) is now a method of the class. Object oriented languages tend to be imperative languages also. | |||
Now let us write the functional version of this function. | |||
<nowiki>function simpleJoin(stringArray, i, accumulator) { | |||
if (i === stringArray.length) { | |||
return accumulator; | |||
} else { | |||
return simpleJoin(stringArray, i+1, accumulator+stringArray[i]) | |||
} | |||
}</nowiki> | |||
The first thing to note is that we are not using the for loop here for iteration. Instead we use recursion for iteration. Recursion happens when the function calls itself from within itself. Indeed this is one of the characteristics of a functional programming language. eg. Scheme does not have any loop statements. Instead it uses recursion for iteration. The function is called for the first time with the given array in stringArray, i set to 0, and accumulator set to "". The second time around the function is called from within itself with the same stringArray, i set to i + 1, and accumulator set to accumulator + stringArray[i]. And we continue the same way until i === stringArray.length when we return the accumulator. We will discuss recursion in detail later in a later post. Just remember we used recursion for doing iteration here. | |||
There is still something imperative about this function. The if statement. Functional languages tend to use expressions that evaluate to some value, instead of statements that don't evaluate to anything. So let us rewrite the function, to make it as functional as possible in JavaScript. | |||
<nowiki>function simpleJoin(stringArray, i, accumulator) { | |||
return (i === stringArray.length) ? accumulator : | |||
simpleJoin(stringArray, i + 1, accumulator + stringArray[i]) | |||
}</nowiki> | |||
Now this as functional as you can get with JavaScript. Instead of the if statement we return the value evaluated by the conditional operator ?. The conditional operator ? Takes a conditional expression and returns the value of one of the two expressions based the condition being true or false. The value of the first expression is returned if true and the second if false. | |||
We can see that the functional version is concise. Indeed one of the advantages of functional programming is that it lends itself to lesser code to accomplish the same thing, leading to better readability and maintainability. | |||
However in the case of JavaScript, as of now you cannot use recursion for doing iteration. You should continue to use the imperative or object oriented method for iteration. This is because JavaScript does not (yet) support “tail call optimization”. For doing proper recursion, tail call optimization is required. We will discuss tail recursion, and tail call optimization, and how to get around this problem in a future post. As of writing this post tail call optimization is expected in ecmascript 6. | |||
So is JavaScript an imperative language, or an object oriented language, or a functional language? It is a multi paradigm language. It does not have all the functional features implemented. But it is slowly getting there. This is also true of most other languages. Most languages (other than functional languages to begin with) have added functional features to various degrees over the years. A good example of the multi paradigm nature of JavaScript is the Array.forEach method. Here is a possible simple implementation. Note that all modern browsers have already implemented this. | |||
var | <nowiki>Array.prototype.forEach = function(callback) { | ||
for (var i = 0, len = this.length; i < len; ++i) { | |||
callback(this[i], i, this); | |||
} | |||
}</nowiki> | |||
In the code above the for-loop part of the code is imperative. Adding to the array prototype and usage of this is object oriented. Passing a function as an argument to another function (callback) is functional and is a feature of functional programming known as “higher order function”. In JavaScript, we take this for granted, passing functions as an argument. Surprisingly this was not a feature found in the most popular languages until recently. eg. You cannot pass functions as arguments in Java, though you can do it indirectly via Interfaces. Same is the case with C, though you can do it indirectly using pointers. | |||
Programming Abstractions | |||
What if JavaScript did not have the “passing functions as arguments feature”? The answer to this question is crucial to understanding what functional programming brings to the table. For one, we would not be able to write the Array.forEach function above. And even worse, every time we had to iterate over an array, we would have to write the same for-loop again and again. If we had Array.foreach we need to think only about writing the callback. We need to think only of what to do with each element of the array, and need not concern ourselves with iterating over the array. In other words we have “abstracted” away the iteration part of the problem, and freed ourselves to concentrate on each element of the array. | |||
Abstractions are about hiding away implementation details, thereby reducing and factoring out details, so that programmers can focus on the problem at a higher level. Abstractions can be code abstractions, as we saw in the example above, and data abstractions. Objected oriented programming is about combining code and data abstractions into one abstraction called the class. In functional programming we use functional features like first class functions, nested functions, anonymous functions and closures to abstract away code and sometimes even data. Monads are an esoteric feature of functional programming that can even abstract away program structure! | |||
Macro's are another feature that allows code abstraction. However macros are not a feature of functional programming. But they are found in all Lisp like functional languages like Lisp, Scheme, Clojure etc. There are attempts to bring Macro's to JavaScript and at the moment it is very much early days. | |||
A good example of the power of functional abstractions is jQuery. jQuery is the mother of all functional abstractions. It has abstracted away the JavaScript language itself! So much so that you wouldn't be surprised, if you found a jQuery programmer who knows very little or no JavaScript. As of Feb 2013 there was one publisher who had 32 jQuery books listed, and not a single JavaScript book! And jQuery has achieved so much mostly using only two functional features, functions as arguments and closures. | |||
First Class Functions and Closures | |||
The functional programming feature that Brendan Eich implemented in JavaScript was “first class functions”. Functions are first class if they are treated as “first class citizens” of that language. Which implies functions are treated just like all other variables. ie. You can pass them as arguments to functions, you can return them as values from other functions, or you can assign them to variables or data structures. We have seen “passing functions are arguments” earlier. Here is an example of assigning a function to a variable. | |||
<nowiki>function greet(name) { | |||
console.log("Hello " + name); | |||
} | |||
greet("John"); // "Hello John" | |||
var sayHello = greet; | |||
sayHello("Alex"); // "Hello Alex"</nowiki> | |||
Some programming language theorists consider “anonymous functions” as first class functions. Not to be outdone, Brendan Eich threw anonymous functions into the mix. This is like letting the cat among the pigeons so to speak. But not for Brendan Eich, he knew the solution to the problem. Here is an anonymous function in JavaScript. | |||
<nowiki>function(name) { | |||
console.log(“Hello “ + name); | |||
}</nowiki> | |||
If you noticed we did not give this function a name. After all, it is an anonymous function. If you try to run the code above, you will get an error. Something to the effect “you cannot run the code in this context”. And rightly so. They can only be assigned to something, or passed as arguments to a function. | |||
<nowiki>var sayHello = function(name) { | |||
console.log(“Hello “ + name); | |||
} | |||
sayHello("Jane"); // "Hello Jane"</nowiki> | |||
What if we wanted to change the greeting? Sometimes we would like to say “Hi” instead of “Hello”. We could create a generic “createGreeting” function, which would in turn “compose” another function for you, and return the new composed function. So if we wanted to sat “Hi” it would return a function, and if we wanted to say “Hello” it would return another function that says “Hello”. We can do all that because JavaScript supports first class functions, and we can return functions from other functions. Here is the code. | |||
<nowiki> | |||
function createGreeting(greeting) { | |||
return function(name) { | |||
console.log(greeting + " " + name); | |||
} | |||
} | |||
var sayHi = createGreeting("Hi"); | |||
sayHi("Jack"); // "Hi Jack" | |||
var sayHello = createGreeting("Hello"); | |||
sayHello("Jack"); // "Hello Jack"</nowiki> | |||
The createGreeting function takes a greeting as its argument. The function returns a newly created anonymous function. However the newly created anonymous function was created inside another function createGreeting. So it is also a nested function now. Now since our language supports anonymous functions it will also have to support nested functions. And when we return nested functions from our function we run into another problem. We will look at that in more detail. | |||
the | |||
The anonymous function takes a name argument and prints to the console greeting + name. The variable name is an argument to the anonymous function, and behaves just like any other variable defined within the function. In other words name is “local” to the anonymous function. But this is not true of the variable greeting. It is defined in another function called createGreeting and hence is “non local” to the anonymous function. However the anonymous function can access the variable greeting due to something called lexical scoping. | |||
The “scope” of a variable is its “visibility” within a program. “Lexical scope” means that visibility is limited to all the text (code). So when we say “local variables are lexically scoped” within a function, it means that the function's local variables are visible to all the text (code) in the function, even if the code is within another nested function. This also means that when you run the nested function outside the lexically scoped environment, the nested functions non local variable will not be visible. And there lies the problem of returning nested functions from another function. And indeed thats what we are doing here. | |||
<nowiki>var sayHi = createGreeting("Hi");</nowiki> | |||
In the line above we assign the returned anonymous function to variable sayHi. And call the function in the next line. | |||
<nowiki>sayHi(“Jack”)</nowiki> | |||
We are calling sayHi outside of createGreeting. And the greeting variable is not available outside of createGreeting. The variables it may access in the scope where it was defined, may not be available in the scope where it was actually called. Thats why languages like C don't support nested functions. For this to work the language needs to support another functional programming feature called “closures”. JavaScript supports closures. As matter of fact it has to support closures. Any language that supports first class functions and nested functions has to support closures. | |||
A function's closure is a reference to all its non local variables. In the previous example greeting was the non local variable and name was the local variable. A closure is a table of references to all of a functions non local variables. This allows the function to continue to refer to the non local variable even when the function is out of the scope of the variables. | |||
===Implementing Monads in JavaScript=== | |||
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html | |||
Consider the problem of doing a series of computations (calling a series of functions), and you want each successive computation to have access to the results of the previous computations. We will write a function called doComputations, and here is how a call to doComputations would look like. | |||
<nowiki> | |||
var result = doComputations( | |||
"a", function(scope) { | |||
return 2; | |||
}, | |||
"b", function(scope) { | |||
with (scope) { | |||
return a * 3; | |||
} | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return a + b; | |||
} | |||
} | |||
);</nowiki> | |||
The arguments to doComputaions are one or more "string - function" pairs and the last argument is just a "result" function. The string is a variable name which will be assigned the value returned from calling the function. So in the first case "a" will be assigned the value 2. What is interesting is that "a" is visible inside the next function whose value gets assigned to "b". And both "a" and "b" are visible inside the last function. Every function is called with a "scope" object which carries key value pairs corresponding to the previous computations carried out. Here we use the "with" statement to scope the "scope" object within the function. If you don't want to use the "with" statement you could access the variable from the scope object directly eg. scope.a, scope.b. The value returned by doComputations is the value returned by the last "result" function, in this case the final value is 8. And here is the definition of doComputations. | |||
<nowiki> | |||
function doComputations() { | |||
var args = arguments; | |||
var scope = {}; | |||
function iterator(i) { | |||
if (args.length === i + 1) {return args[i](scope);} | |||
var varName = args[i]; | |||
var func = args[i + 1]; | |||
var value = func(scope); | |||
scope[varName] = value; | |||
return iterator(i + 2); | |||
} | |||
return iterator(0); | |||
}</nowiki> | |||
Inside doComputations we define an iterator function, which recursively iterates over the arguments array of doComputations. In the first line of the iterator function we check to see if we have reached the last "result function", if yes we call it with scope and return the result. In the next three lines we create three variables initialised to the variable name, function, and value returned by calling the function with the scope. In the next line we attach the key-value to scope. And finally we make a recursive call to the iterator to do the next computation. In the last line of doComputations we start the iterator with initial values 0 for the index. | |||
Copy the two code fragments above into a file, add the a final line: | |||
<nowiki>console.log(result);</nowiki> | |||
and run it. You should get the result as 8. | |||
All this looks like lots of work just to add and multiply a couple of integers, but we have done something useful. For one we have abstracted away the nitty gritty of iterating over computations, with visibility of previous results, into a function called doComputations. | |||
Computations are not always so simple and straight forward as the one above. What if we wanted to abort the computations midway for some reason? eg. If any of the functions returns "null" we want to abort the computations. There are many other types of computations and to write a version of doComputations for each type is not a good idea. Instead we could make doComputations call another function between computations so that any thing different, we want to do, is done in this function. This function is passed to doComputations as its first argument. We will call this function "mBind". Now all we have to do is write a version of mBind for every type of computation. For every computation, doComputations will call mBind which in turn will call the next computation. First we write the mBind function to handle null values returned by any computation. | |||
<nowiki>var mBind = function(mValue, mFunction) { | |||
if (mValue === null) | |||
return null; | |||
else | |||
return mFunction(i + 2); | |||
}</nowiki> | |||
Now the iterator function will call mBind, which is passed as an argument to doComputations, which in turn will recursively call the iterator. | |||
/ | <nowiki>function doComputations(mBind) { | ||
var args = arguments; | |||
var scope = {}; | |||
function iterator(i) { | |||
if (args.length === i + 1) {return args[i](scope);} | |||
var varName = args[i]; | |||
var func = args[i + 1]; | |||
var value = func(scope); | |||
return mBind(value, function() { | |||
scope[varName] = value; | |||
return iterator(i + 2); | |||
}); | |||
} | |||
return iterator(1); | |||
}</nowiki> | |||
Below we call doComputations whose first argument is the mBind function. Also we want to abort the computations in case the browser does not support the console.log function. | |||
<nowiki> | |||
var result = doComputations(mBind, | |||
"a", function(scope) { | |||
if (typeof console === "object" && console.log) | |||
return 2; | |||
else | |||
return null; | |||
}, | |||
"b", function(scope) { | |||
with (scope) { | |||
return a * 3; | |||
} | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return a + b; | |||
} | |||
} | |||
);</nowiki> | |||
We can now use doComputations for various types of computations by simply changing the mBind function passed to it. It would be even better if we could predefine the mBind function for various types of computations. And that is what we will do below. We will also change the name of doComputations to doMonad. And we will add mBind as the property of an object called "monad". | |||
<nowiki>var maybeMonad = { | |||
mBind: function(mValue, mFunction) { | |||
if (mValue === null) | |||
return null; | |||
else | |||
return mFunction(mValue); | |||
} | |||
}; | |||
function doMonad(monad) { | |||
var args = arguments; | |||
var scope = {}; | |||
function iterator(i) { | |||
if (args.length === i + 1) {return args[i](scope);} | |||
var varName = args[i]; | |||
var func = args[i + 1]; | |||
var value = func(scope); | |||
return monad.mBind(value, function() { | |||
scope[varName] = value; | |||
return iterator(i + 2); | |||
}); | |||
} | |||
return iterator(1); | |||
}</nowiki> | |||
Compare the above code to the previous listing. It is pretty much the same, except that we have renamed doComputations, and the mBind function is now passed as the property of an object, and this object is called a monad, and in this specific case we called the monad the "maybeMonad". Because "maybe" the computations are carried out, or "maybe" they won't be. | |||
A monad MUST have two properties defined for it to be a proper monad. "mBind" and "mResult". We have not seen mResult so far. mResult is a wrapper function for the "result" function. So we add support for mResult in doMonad below. Also we define a new monad called the arrayMonad below and we do some computations with the it. | |||
if ( | <nowiki> | ||
function doMonad(monad) { | |||
var args = arguments, scope = {}; | |||
function iterator(i) { | |||
if (args.length === i + 1) { | |||
return monad.mResult(args[i](scope)); | |||
} | |||
var varName = args[i]; | |||
var func = args[i + 1]; | |||
var value = func(scope); | |||
return monad.mBind(value, function(value) { | |||
scope[varName] = value; | |||
return iterator(i + 2); | |||
}); | |||
} | |||
return iterator(1); | |||
} | |||
var arrayMonad = { | |||
mBind: function(mValue, mFunc) { | |||
var accum = []; | |||
mValue.forEach(function(elem){ | |||
accum = accum.concat(mFunc(elem)); | |||
}); | |||
return accum; | |||
}, | |||
mResult: function(value) { | |||
return [value]; | |||
} | |||
} | |||
var result = doMonad(arrayMonad, | |||
"a", function() { | |||
return [1, 2]; | |||
}, | |||
"b", function() { | |||
return [3, 4]; | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return a + b; | |||
} | |||
} | } | ||
); | |||
console.log(result);</nowiki> | |||
Running the code above will yield a result of [ 4, 5, 5, 6 ]. The computations using the arrayMonad each return an array. The final result function is called with values a and b, for each element of both arrays. ie it will be called with (1,3), (1,4), (2,3), (2,4). And the addition of each of the elements yields the returned array of [ 4, 5, 5, 6 ]. | |||
Using the arrayMonad let us implement a two dimensional iterator function in JavaScript called forEach2D. It will take 3 arguments, an iArray, a jArray, and a callback. The callback is called for each value of i and j. Here is the code below. | |||
<nowiki>function forEach2D(iArray, jArray, callback) { | |||
return doMonad(arrayMonad, | |||
"i", function() { | |||
return iArray; | |||
}, | |||
"j", function() { | |||
return jArray; | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return callback(i, j); | |||
} | |||
} | |||
); | |||
} | |||
var result = forEach2D([1, 2, 3], [4, 5, 6], function(i, j) { | |||
return [i, j]; | |||
}); | |||
console.log(result);</nowiki> | |||
Running the code above will yield result:[ [1, 4],[1, 5],[1, 6],[2, 4],[2, 5],[2, 6],[3, 4],[3, 5],[3, 6] ] | |||
How about a function for iterating over three arrays? A forEach3D function. Easy! | |||
<nowiki>function forEach3D(iArray, jArray, kArray, callback) { | |||
return doMonad(arrayMonad, | |||
"i", function() { | |||
return iArray; | |||
}, | |||
"j", function() { | |||
return jArray; | |||
}, | |||
"k", function() { | |||
return kArray; | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return callback(i, j, k); | |||
} | |||
} | |||
); | |||
} | |||
var result = forEach3D([1, 2], [3, 4], [5, 6], function(i, j, k) { | |||
return [i, j, k]; | |||
}); | |||
console.log(result);</nowiki> | |||
And running this code will print out: [ [1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6] ] | |||
You can begin to see the power of monads here. They abstract away the complicated part of your code and simplify the problem at hand. Monads are a hard concept to understand, and I hope that I have simplified its understanding here. If there is any part not clear enough please let me know. In the next post I hope to take a more in depth look and monads with some interesting examples. | |||
==The monad laws and state monad in JavaScript== | |||
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html | |||
In the previous post (please read the previous post before reading this post) we implemented three monads, the identity monad, maybe monad and array monad. However we did not get into the details of monads. We will do that in this post. Also we will implement the state monad. Here is an identity monad example. | |||
<nowiki>var identityMonad = { | |||
mBind: function(mValue, mFunction) { | |||
return mFunction(mValue); | |||
}, | |||
mResult: function(value) { | |||
return value; | |||
} | |||
}; | |||
} | var result = doMonad(identityMonad, | ||
"a", function(scope) { | |||
return 2; | |||
}, | |||
"b", function(scope) { | |||
with (scope) { | |||
return a * 3; | |||
} | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return a + b; | |||
} | |||
} | |||
); | |||
console.log(result);</nowiki> | |||
First we define the identityMonad that we use when calling doMonad. Each computation in doMonad MUST return a monadic value. eg. the first computation returns 2 a monadic value. However what gets assigned to "a" is not the monadic value but the value. This distinction is important. However in the case of the identity monad both the monadic value and value are the same. | |||
The mBind function takes a monadic value and a monadic function as its arguments. mBind then extracts the "value" out of the "monadic value" and calls the monadic function with the value. In this case no extraction is done because both are equal for the identity monad. The monadic function takes a "value" and returns a "monadic value". | |||
In the next computation "a" is available as the value and not the monadic value. In the result computation both "a" and "b" are available and we return "value a + b". Since we return a value and not a monadic value in the final computation, the transformation from "value" to "monadic value" is done by the mResult function which takes a value and returns a monadic value. In this case it returns the argument itself because the value and monadic value are the same. So mResult is the identity function, and thats why it is called the identity monad. | |||
We will look at a case where the "value" and "monadic value" are not the same. Using the array monad we will write a map function that doubles each element of an array. | |||
<nowiki> | |||
var arrayMonad = { | |||
mBind: function(mValue, mFunc) { | |||
var accum = []; | |||
mValue.forEach(function(elem){ | |||
accum = accum.concat(mFunc(elem)); | |||
}); | }); | ||
return accum; | |||
}, | |||
mResult: function(value) { | |||
return [value]; | |||
} | |||
}; | |||
var result = doMonad(arrayMonad, | |||
"i", function() { | |||
return [1, 2, 3]; | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return i * 2; | |||
} | |||
} | |||
);</nowiki> | |||
Running the above code will print the result [ 2, 4, 6 ]. The first function in doMonad returns a monadic value which is an array. However the "value" in a monadic value of array type is the value of each element. It is mBinds job to extract each value, and call the monadic function for each value, and thats what it does exactly. | |||
The result function returns i * 2 which is of type integer. However all monadic functions of a given monad MUST return monadic values of the same type. It is mResults job to convert the result type from integer to array. And that is what it does exactly. | |||
The monad laws | |||
To write our own monads, our monads must obay the monad laws. | |||
# mBind(mResult(x), mFunction) is equal to mFunction(x). | |||
# mBind(mValue, mResult) is equal to mValue. | |||
# mBind(mBind(mValue, mFunction), mFunction2) is equal to mBind(mValue, function(x){return mbind(mFunction(x), mFunction2)}). | |||
Now let us check to see if our array monad follows the monad laws. | |||
<nowiki> | |||
var mBind = arrayMonad.mBind; | |||
var mResult = arrayMonad.mResult; | |||
var mValue = [1, 2, 3]; | |||
var x = 4; | |||
var mFunction = function(x) { | |||
return [x * 2]; | |||
}; | |||
var mFunction2 = function(x) { | |||
return [x * 3]; | |||
}; | |||
// Check first law | |||
console.log(mBind(mResult(x), mFunction)); // [ 8 ] | |||
console.log(mFunction(x)); // [ 8 ] | |||
//Check second Law | |||
console.log(mBind(mValue, mResult)); // [ 1, 2, 3 ] | |||
console.log(mValue); // [ 1, 2, 3 ] | |||
//Check third Law | |||
console.log(mBind(mBind(mValue, mFunction), mFunction2)); // [ 6, 12, 18 ] | |||
console.log(mBind(mValue, function(x){return mBind(mFunction(x), mFunction2)})); // [ 6, 12, 18 ]</nowiki> | |||
===The State Monad=== | |||
So far we saw monadic values of types integer and array. But monadic values can also be functions! After all JavaScript supports first class functions, that can be treated as values. The state monad is just such a monad. The monadic value is a function. However it is important to differentiate between a monadic function and a monadic value of type function. | |||
The monadic function in a state monad, just like all monadic functions takes a value and returns a monadic value. It so happens that this monadic value is a function. | |||
The monadic value in a state monad is a function that takes a state, and returns a two element array, with a value and the new state respectively. The state can be of any type. it can be a an integer, string, array object or any other valid type. | |||
In the next example we maintain an immutable stack array over a set of computations. We define two monadic functions "push" and "pop". | |||
<nowiki> | |||
var stateMonad = { | |||
mBind: function(mValue, mFunc) { | |||
return function(state) { | |||
var compute = mValue(state); | |||
var value = compute[0]; | |||
var newState = compute[1]; | |||
return mFunc(value)(newState); | |||
}; | |||
}, | |||
mResult: function(value) { | |||
return function(state) { | |||
return [value, state]; | |||
}; | |||
} | |||
}; | |||
var push = function(value) { | |||
return function(state) { | |||
var newstate = [value]; | |||
return [undefined, newstate.concat(state)]; | |||
}; | |||
}; | |||
var pop = function() { | |||
return function(state) { | |||
var newstate = state.slice(1); | |||
return [state[0], newstate]; | |||
}; | }; | ||
}; | |||
var result = doMonad(stateMonad, | |||
"a", function(scope) { | |||
return push(5); | |||
}, | |||
"b", function(scope) { | |||
with (scope) { | |||
return push(10); | |||
} | |||
}, | |||
"c", function(scope) { | |||
with (scope) { | |||
return push(20); | |||
} | |||
}, | |||
"d", function(scope) { | |||
with (scope) { | |||
return pop(); | |||
} | |||
}, | |||
function(scope) { | |||
with(scope) { | |||
return d; | |||
} | |||
} | |||
); | |||
console.log(result([]));</nowiki> | |||
Running the code above will print [ 20, [ 10, 5 ] ]. | |||
20 is the value of the last "pop" computation, and the second value is the final state of the stack. | |||
First we will look at mResult. mResult is a monadic function that takes a value and returns a monadic value, which is a function that takes a state and returns an array with value and state. | |||
mBind returns a monadic value which is a function. So the result of doMonad is a function which you must call with an initial value for the stack. Which is [] in our case. Remember mBind is called with a monadic value and a monadic function. It has to extract the value out of the monadic value and call the monadic function with the value. Which it does in the three lines inside the returned monadic function. Notice that mFunc is called with the extracted value, and since its return value is a monadic value of type function, the function is called immediately with the new state. | |||
All the code in the last two posts are available as a monad library on github. | |||
==The Promise Monad in JavaScript== | |||
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html | |||
If you find it difficult to understand whats going on below, read the following posts. | |||
Implementing Monads in JavaScript | |||
The monad laws and state monad in JavaScript. | |||
We will go through an example of the promise monad in this post. The promise monad is available now in the monadjs library. The best way is too look at an example. We will write a nodejs command line program that will copy an input file into an output file asynchronously. We can run the program like this. | |||
<nowiki>$ node copy.js infile outfile</nowiki> | |||
The program has to do the following. | |||
Check the command line for infile and verify it exists, if not print an error and halt computations. | |||
if ( | Check if outfile is given in the command line otherwise print error and halt. | ||
Read infile content into memory and halt if error. | |||
Write content to outfile or print error to console. | |||
Halting of program in case of error to be done without throwing errors. | |||
Computations (1) (3) and (4) are asynchronous. (2) is synchronous because we are only checking process.argv. | |||
The monadic values of the promise monad are functions that take a continuation and promise to call the continuation either asynchronously or synchronously. The continuation is called with a value which is the result of the last computation. If the continuation is called with "null" the computations are halted. | |||
All the computations have access to the results of the previous computations via the "scope" variable. The results are stored in the variable names you give. | |||
Here is the source code of copy.js | |||
<nowiki> | |||
var monads = require("monadjs"); | |||
var fs = require("fs"); | |||
monads.doMonad(monads.promiseMonad, | |||
"infile", | |||
function(scope) { | |||
return function(continuation) { | |||
var fname = process.argv[2] || ""; | |||
fs.exists(fname, function (exists) { | |||
if (exists) { | |||
continuation(fname); | |||
} else { | |||
console.log("File does not exist: " + fname); | |||
continuation(null); | |||
} | |||
}); | |||
} | |||
}, | |||
"outfile", | |||
function(scope) { | |||
return function(continuation) { | |||
var fname = process.argv[3]; | |||
if (fname) { | |||
continuation(fname); | |||
} else { | |||
console.log("Output File Name is Required"); | |||
continuation(null); | |||
} | |||
} | |||
}, | |||
"contents", | |||
function(scope) { | |||
return function(continuation) { | |||
fs.readFile(scope.infile, function (err, data) { | |||
if (err) { | |||
console.log("Error reading File: " + scope.infile); | |||
continuation(null); | |||
} else { | |||
continuation(data); | |||
} | |||
}); | |||
} | |||
}, | |||
function(scope) { | |||
fs.writeFile(scope.outfile, scope.contents, function (err) { | |||
if (err) { | |||
console.log("Error writing File: " + scope.outfile); | |||
} | } | ||
}); | |||
} | |||
);</nowiki> | |||
I don't think the promise monad obeys the monad laws. But it works. It works only for sequential asynchronous calls though. What is interesting is that it allows you to break the program structure into bite size pieces and call them sequentially. Notice also the promise monad is implemented purely functionally, and no timing loops used. | |||
However you don't have to actually use the promise monad from this library. I have refactored and simplified everything in a simple promise library you can find here. | |||
<nowiki>/* | |||
dopromise | |||
Promise Library for JavaScript | |||
Copyright (c) 2013 Santosh Rajan | |||
License - MIT - https://github.com/santoshrajan/dopromise/blob/master/LICENSE | |||
*/ | |||
(function(exports){ | |||
if ( | var serial = function() { | ||
var args = arguments, scope = {} | |||
function iterator(i) { | |||
var func = args[i] | |||
}); | if (args.length === i + 1) { | ||
func.call(scope) | |||
} else { | |||
func.call(scope, function() { | |||
iterator(i + 1) | |||
}) | |||
} | |||
} | |||
iterator(0); | |||
} | |||
var parallel = function() { | |||
var args = Array.prototype.slice.call(arguments), | |||
last = args.pop() | |||
counter = args.length | |||
scope = {} | |||
done = function() { | |||
--counter | |||
if (counter === 0) { | |||
last.call(scope) | |||
} | } | ||
} | } | ||
args.forEach(function(f) { | |||
f.call(scope, done) | |||
}) | |||
} | |||
var loop = function(f) { | |||
var scope = {} | |||
function iterator() { | |||
f.call(scope, iterator) | |||
} | } | ||
iterator() | |||
} | } | ||
var | exports.version = "0.0.4" | ||
exports.doPromise = serial // for backward compatability | |||
exports.serial = serial | |||
exports.parallel = parallel | |||
exports.loop = loop | |||
})(typeof exports === 'undefined'? this.dopromise={}: exports);</nowiki> | |||
==Functors== | |||
[http://functionaljavascript.blogspot.ru/2013/07/functors.html Источник] | |||
Consider the function below. | |||
<nowiki>function plus1(value) { | |||
return value + 1 | |||
}</nowiki> | |||
It is just a function that takes an integer and adds one to it. Similarly we could could have another function plus2. We will use these functions later. | |||
<nowiki>function plus2(value) { | |||
return value + 2 | |||
}</nowiki> | |||
And we could write a generalised function to use any of these functions as and when required. | |||
<nowiki>function F(value, fn) { | |||
return fn(value) | |||
} | |||
F(1, plus1) ==>> 2</nowiki> | |||
This function will work fine as long as the value passed is an integer. Try an array. | |||
<nowiki>F([1, 2, 3], plus1) ==>> '1,2,31'</nowiki> | |||
Ouch. We took an array of integers, added an integer and got back a string! Not only did it do the wrong thing, we ended up with a string having started with an array. In other words our program also trashed the structure of the input. We want F to do the "right thing". The right thing is to "maintain structure" through out the operation. | |||
So what do we mean by "maintain structure"? Our function must "unwrap" the given array and get its elements. Then call the given function with every element. Then wrap the returned values in a new Array and return it. Fortunately JavaScript just has that function. Its called map. | |||
<nowiki>[1, 2, 3].map(plus1) ==>> [2, 3, 4]</nowiki> | |||
And map is a functor! | |||
A functor is a function, given a value and a function, does the right thing. | |||
''To be more specific.'' | |||
A functor is a function, given a value and a function, unwraps the values to get to its inner value(s), calls the given function with the inner value(s), wraps the returned values in a new structure, and returns the new structure. | |||
Thing to note here is that depending on the "Type" of the value, the unwrapping may lead to a value or a set of values. | |||
Also the returned structure need not be of the same type as the original value. In the case of map both the value and the returned value have the same structure (Array). The returned structure can be any type as long as you can get to the individual elements. So if you had a function that takes and Array and returns value of type Object with all the array indexes as keys, and corresponding values, that will also be a functor. | |||
In the case of JavaScript, filter is a functor because it returns an Array, however forEach is not a functor because it returns undefined. ie. forEach does not maintain structure. | |||
Functors come from category theory in mathematics, where functors are defined as "homomorphisms between categories". Let's draw some meaning out of those words. | |||
homo = same | |||
morphisms = functions that maintain structure | |||
category = type | |||
According to the theory, function F is a functor when for two composable ordinary functions f and g | |||
<nowiki>F(f . g) = F(f) . F(g)</nowiki> | |||
where . indicates composition. ie. functors must preserve composition. | |||
So given this equation we can prove wether a given function is indeed a functor or not. | |||
====Array Functor==== | |||
We saw that map is a functor that acts on type Array. Let us prove that the JavaScript Array.map function is a functor. | |||
<nowiki>function compose(f, g) { | |||
return function(x) {return f(g(x))} | |||
}</nowiki> | |||
Composing functions is about calling a set of functions, by calling the next function, with results of the previous function. Note that our compose function above works from right to left. g is called first then f. | |||
<nowiki>[1, 2, 3].map(compose(plus1, plus2)) ==>> [ 4, 5, 6 ] | |||
[1, 2, 3].map(plus2).map(plus1) ==>> [ 4, 5, 6 ]</nowiki> | |||
Yes! map is indeed a functor. | |||
Lets try some functors. You can write functors for values of any type, as long as you can unwrap the value and return a structure. | |||
String Functor | |||
So can we write a functor for type string? Can you unwrap a string? Actually you can, if you think of a string as an array of chars. So it is really about how you look at the value. We also know that chars have char codes which are integers. So we run plus1 on every char charcode, wrap them back to a string and return it. | |||
<nowiki> | |||
function stringFunctor(value, fn) { | |||
var chars = value.split("") | |||
return chars.map(function(char) { | |||
return String.fromCharCode(fn(char.charCodeAt(0))) | |||
}).join("") | |||
} | |||
stringFunctor("ABCD", plus1) ==>> "BCDE" | |||
</nowiki> | |||
You can begin to see how awesome functors are. You can actually write a parser using the string functor as the basis. | |||
====Function Functor==== | |||
In JavaScript functions are first class citizens. That means you can treat functions like any other value. So can we write a functor for value of type function? We should be able to! But how do we unwrap a function? You can unwrap a function by calling it and getting its return value. But we straight away run into a problem. To call the function we need its arguments. Remember that the functor only has the function that came in as the value. We can solve this by having the functor return a new function. This function is called with the arguments, and we will in turn call the value function with the argument, and call the original functors function with the value returned! | |||
<nowiki> | |||
function functionFunctor(value, fn) { | |||
return function(initial) { | |||
return function() { | |||
return fn(value(initial)) | |||
} | |||
} | |||
} | |||
var init = functionFunctor(function(x) {return x * x}, plus1) | |||
var final = init(2) | |||
final() ==> 5</nowiki> | |||
Our function functor really does nothing much, to say the least. But there a couple things of note here. Nothing happens until you call final. Every thing is in a state of suspended animation until you call final. The function functor forms the basis for more awesome functional stuff like maintaining state, continuation calling and even promises. You can write your own function functors to do these things! | |||
====MayBe Functor==== | |||
<nowiki>function mayBe(value, fn) { | |||
return value === null || value === undefined ? value : fn(value) | |||
}</nowiki> | |||
Yes, this is a valid functor. | |||
<nowiki>mayBe(undefined, compose(plus1, plus2)) ==>> undefined | |||
mayBe(mayBe(undefined, plus2), plus1) ==>> undefined | |||
mayBe(1, compose(plus1, plus2)) ==>> 4 | |||
mayBe(mayBe(1, plus2), plus1) ==>> 4</nowiki> | |||
So mayBe passes our functor test. There is no need for unrapping or wrapping here. It just returns nothing for nothing. Maybe is useful as a short circuiting function, which you can use as a substitute for code like | |||
<nowiki>if (result === null) { | |||
return null | |||
} else { | |||
doSomething(result) | |||
}</nowiki> | |||
====Identity Function==== | |||
<nowiki>function id(x) { | |||
return x | |||
}</nowiki> | |||
The function above is known as the identity function. It is just a function that returns the value passed to it. It is called so, because it is the identity in composition of functions in mathematics. | |||
We learned earlier that functors must preserve composition. However something I did not mention then, is that functors must also preserve identity. ie. | |||
<nowiki>F(value, id) = value</nowiki> | |||
Lets try this for map. | |||
<nowiki>[1, 2, 3].map(id) ==>> [ 1, 2, 3 ]</nowiki> | |||
===Type Signature=== | |||
The type signature of a function is the type of its argument and return value. So the type signature of our plus1 function is | |||
<nowiki>f: int -> int</nowiki> | |||
The type signature of the functor map depends on the type signature of the function argument. So if map is called with plus1 then its type signature is | |||
<nowiki>map: [int] -> [int]</nowiki> | |||
However the type signature of the given function need not be the same as above. We could have a function like | |||
<nowiki>f: int -> string</nowiki> | |||
in which the type signature of map would be | |||
<nowiki>map: [int] -> [string]</nowiki> | |||
The only restriction being that the type change does not affect the composability of the functor. So in general a functor's type signature can | |||
<nowiki>F: A -> B</nowiki> | |||
In other words map can take an array of integers and return an array of strings and would still be a functor. | |||
Monads are a special case of Functors whos type signature is | |||
<nowiki>M: A -> A</nowiki> | |||
==Monads== | |||
All the code for this post are available here.https://github.com/santoshrajan/monadjs | |||
Consider the map functor from the last chapter. We could use map to iterate over two arrays adding each element of the first to the second. | |||
<nowiki> | |||
var result = [1, 2].map(function(i) { | |||
return [3, 4].map(function(j) { | |||
return i + j | |||
}) | |||
}) | |||
console.log(result) ==>> [ [ 4, 5 ], [ 5, 6 ] ]</nowiki> | |||
The type signature of the inner function is | |||
<nowiki>f: int -> int</nowiki> | |||
and the type signature of the inner map is | |||
<nowiki>map: [int] -> [int]</nowiki> | |||
The type signature of the outer function is | |||
<nowiki>f: int -> [int]</nowiki> | |||
and the type signature of the outer map is | |||
<nowiki>map: [int] -> [[int]]</nowiki> | |||
This is the right behaviour you would expect from a functor. But this is not what we want. We want the result to be flattened like below. | |||
[ 4, 5, 5, 6 ] | |||
===Array Monad=== | |||
For that to happen, the type signature of a functor should always be restricted to | |||
<nowiki>F: [int] -> [int]</nowiki> | |||
But functors do not place any such restriction. But monads do. The type signature of an array monad is | |||
<nowiki>M: [T] -> [T]</nowiki> | |||
where T is a given type. That is why map is a functor but not a monad. That is not all. We have to put some type restriction on the function passed it too. The function cannot return any type it likes. We can solve this problem by restricting the function to return the Array type. So the function's type signature is restricted to | |||
<nowiki>f: T -> [T]</nowiki> | |||
This function is known as lift, Because it lifts the type to the required type. This is also known as the monadic function. And the original value given to the monad is known as the monadic value. Here is the arrayMonad. | |||
<nowiki>function arrayMonad(mv, mf) { | |||
var result = [] | |||
mv.forEach(function(v) { | |||
result = result.concat(mf(v)) | |||
}) | |||
return result | |||
}</nowiki> | |||
Now we can use the array monad to do our first calculation. | |||
<nowiki> | |||
console.log(arrayMonad([1,2,3], function(i) { | |||
return [i + 1] | |||
})) ==>> [ 2, 3, 4 ]</nowiki> | |||
Notice that our monadic function wraps the result in an array [i + 1]. Now let us try it with the two dimensional problem we started with. | |||
<nowiki>var result = arrayMonad([1, 2], function(i) { | |||
return arrayMonad([3, 4], function(j) { | |||
return [i + j] | |||
}) | |||
}) | |||
console.log(result) ==>> [ 4, 5, 5, 6 ]</nowiki> | |||
Now we begin to see the power of monads over functors. | |||
We can write a generic two dimensional iterator for arrays which will take two arrays and a callback, and call it for each element of both arrays. | |||
<nowiki>function forEach2d(array1, array2, callback) { | |||
return arrayMonad(array1, function(i) { | |||
return arrayMonad(array2, function(j) { | |||
return [callback(i,j)] | |||
}) | |||
}) | |||
}</nowiki> | |||
And we can try this function | |||
<nowiki>forEach2d([1, 2], [3,4], function(i, j) { | |||
return i + j | |||
}) ==>> [ 4, 5, 5, 6 ]</nowiki> | |||
Notice that the callback function is just a regular function, so we had to lift its return value [callback(i,j)] to an array. However all monads define a function to do the lifting. Its called mResult. We will add mResult to the arrayMonad function object. Also the concat function is inneficient as it creates a new array everytime. We will apply array push instead. Here is the final code for the array monad. | |||
<nowiki> | |||
function arrayMonad(mv, mf) { | |||
var result = [] | |||
mv.forEach(function(v) { | |||
Array.prototype.push.apply(result, mf(v)) | |||
}) | |||
return result | |||
} | |||
arrayMonad.mResult = function(v) { | |||
return [v] | |||
}</nowiki> | |||
and rewrite forEach2d | |||
<nowiki> | |||
function forEach2d(array1, array2, callback) { | |||
return arrayMonad(array1, function(i) { | |||
return arrayMonad(array2, function(j) { | |||
return arrayMonad.mResult(callback(i,j)) | |||
}) | |||
}) | |||
}</nowiki> | |||
As an exersice I will leave it to the reader to implement forEach3d. | |||
The arrayMonad is a monadic function and is otherwise known as bind or mbind. For a function to be a monad it must define atleast the functions mbind and mresult. | |||
===Identity Monad=== | |||
The identity monad is the simplest of all monads, named so because it's mresult is the identity function. | |||
<nowiki> | |||
function indentityMonad(mv, mf) { | |||
return mf(mv) | |||
} | |||
identityMonad.mResult = function(v) { | |||
return v | |||
}</nowiki> | |||
It is not a very useful monad. But it is a valid monad. | |||
===Maybe Monad=== | |||
The maybe Monad is similar to the identity monad, except that it will not call the monadic function for values null or undefined. In fact it boild down to the same mayBe functor we saw in the last chapter. | |||
<nowiki> | |||
function mayBeMonad(mv, mf) { | |||
return mv === null || mv === undefined || mv === false ? null : mf(mv) | |||
} | |||
mayBeMonad.mResult = function(v) { | |||
return v | |||
}</nowiki> | |||
===The Monad Laws=== | |||
* The First Monad Law | |||
'''M(mResult(x), mf) = mf(x)''' | |||
Which means whatever mResult does to x to turn x into a monadic value, M will unwrap that monadic value before applying it to monadic function mf. Let us test this on our array monad. | |||
<nowiki>var x = 4; | |||
function mf(x) { | |||
return [x * 2] | |||
} | |||
arrayMonad(arrayMonad.mResult(x), mf) ==>> [ 8 ] | |||
mf(x) ==>> [ 8 ]</nowiki> | |||
* The Second Monad Law | |||
'''M(mv, mResult) = mv''' | |||
Which means whatever mBind does to extract mv's value, mResult will undo that and turn the value back to a monadic value. This ensures that mResult is a monadic function. Let us test it. This is equivalent to the preserve identity case of the functor. | |||
<nowiki>arrayMonad([1, 2, 3], arrayMonad.mResult) ==>> [ 1, 2, 3 ]</nowiki> | |||
* The Third Monad Law | |||
'''M(M(mv, mf), mg)) = M(mv, function(x){return M(mf(x), mg)})''' | |||
What this is saying is that it doesn't matter if you apply mf to mv and then to mg, or apply mv to a monadic function that is a composition of mf and mg. Let us test this. | |||
<nowiki> | |||
function mg(x) { | |||
return [x * 3] | |||
} | |||
arrayMonad(arrayMonad([1, 2, 3], mf), mg) ==>> [ 6, 12, 18 ] | |||
arrayMonad([1, 2, 3], function(x) { | |||
return arrayMonad(mf(x), mg) | |||
}) ==>> [ 6, 12, 18 ]</nowiki> | |||
===doMonad=== | |||
We know that a monadic function takes a value and returns a monadic value. A monad takes a monadic value and a monadic function and returns a monadic value. What if a monadic function calls a monad with a monadic value and itself and returns the result? That would be a valid monadic function because it returns a monadic value. | |||
The function doMonad does exactly this. It takes a monad and an array of monadic values and a callback as its arguments. It defines a monadic function that recursively calls the monad with each monadic value and itself. It breaks out of the loop when there are no more monadic values left. It returns the callback called with each unwrapped value of the monadic value. The callback cb is curried in a closure called wrap and is visible to mf. The curry function is from the chapter on currying. | |||
<nowiki> | |||
function curry(fn, numArgs) { | |||
numArgs = numArgs || fn.length | |||
return function f(saved_args) { | |||
return function() { | |||
var args = saved_args.concat(Array.prototype.slice.call(arguments)) | |||
return args.length === numArgs ? fn.apply(null, args) : f(args) | |||
} | |||
}([]) | |||
} | |||
function doMonad(monad, values, cb) { | |||
function wrap(curriedCb, index) { | |||
return function mf(v) { | |||
return (index === values.length - 1) ? | |||
monad.mResult(curriedCb(v)) : | |||
monad(values[index + 1], wrap(curriedCb(v), index + 1)) | |||
} | } | ||
}, | } | ||
return monad(values[0], wrap(curry(cb), 0)) | |||
} | |||
doMonad(arrayMonad, [[1, 2], [3, 4]], function(x, y) { | |||
return x + y | |||
}) //==>> [ 4, 5, 5, 6 ]</nowiki> | |||
We don't need the forEach2d function we wrote earlier! And the best is yet to come! | |||
Array comprehension using doMonad | |||
We can write a generic array comprehension function called FOR which takes a set of arrays and a callback in its arguments. | |||
<nowiki> | |||
function FOR() { | |||
var args = [].slice.call(arguments) | |||
callback = args.pop() | |||
return doMonad(arrayMonad, args, callback) | |||
} | |||
}()) | FOR([1, 2], [3, 4], function(x, y) { | ||
return x + y | |||
}) //==>> [ 4, 5, 5, 6 ] | |||
FOR([1, 2], [3, 4], [5, 6], function(x, y, z) { | |||
return x + y + z | |||
}) //==>> [ 9, 10, 10, 11, 10, 11, 11, 12 ]</nowiki> | |||
How awesome is that! | |||
===State Monad=== | |||
In the last chapter on functors we saw function fucntor that takes a value of type function. Similarly monadic values can also be functions. However it is important to distinguish between monadic functions and monadic values that are functions. The type signature of a monadic function is | |||
<nowiki>mf: v -> mv</nowiki> | |||
ie. takes a value and lifts it to a monadic value. Note that the monadic value itself is a function. So mf will return a function mv. | |||
The type signature of a monadic value which is a function depends on whatever that function is doing as the case may be. In the case of the state monad the type signature of its monadic value is | |||
<nowiki>mv: state -> [value, new state]</nowiki> | |||
The monadic value function takes a state and returns an array containing a value and a new state. The state can be of any type array, string, integer, anything. | |||
The stateMonad takes a monadic value and a monadic function and returns a function to which we have to pass the initial state. The initial state is passed mv which returns a value. mf is then called with this value and mf returns a monadic value which is a function. We must call this function with the newstate. Phew! | |||
<nowiki> | |||
function stateMonad(mv, mf) { | |||
return function(state) { | |||
var compute = mv(state) | |||
var value = compute[0] | |||
var newState = compute[1] | |||
return mf(value)(newState) | |||
} | |||
}</nowiki> | }</nowiki> | ||
And mResult for the state monad is | |||
<nowiki> | |||
stateMonad.mResult = function(value) { | |||
return function(state) { | |||
return [value, state]; | |||
} | |||
}</nowiki> | |||
===Parser Monad=== | |||
A parser is function that takes a string matches the string based on some criteria and returns the matched part and the remainder. Lets write the type signature of the function. | |||
<nowiki>parser: string -> [match, newstring]</nowiki> | |||
This looks like the monadic value of the state monad, with state restricted to the type string. But thats not all, the parser will return null if the string did not match the criteria. So lets write the parser monad to reflect the changes. | |||
<nowiki> | |||
function parserMonad(mv, mf) { | |||
return function(str) { | |||
var compute = mv(str) | |||
if (compute === null) { | |||
return null | |||
} else { | |||
return mf(compute[0])(compute[1]) | |||
} | |||
} | |||
} | |||
parserMonad.mResult = function(value) { | |||
return function(str) { | |||
return [value, str]; | |||
} | |||
}</nowiki> | |||
As we saw earlier Monads require you to define atleast two functions, mBind (the monad function itself) and mResult. But that is not all. Optionally you can define two more functions, mZero and mPlus. | |||
mZero is the definition of "Nothing" for the monad. eg. for the arrayMonad, mZero would be []. In the case of the parser monad mZero is defined as follows. (mZero must have the same type signature of the monadic value). | |||
<nowiki>parserMonad.mZero = function(str) { | |||
return null | |||
}</nowiki> | |||
mPlus is a function that takes monadic values as its arguments, and ignores the mZero's among them. How the accepted values are handled depends on the individual monad. For the parser monad, mZero will take a set of parsers (parser monad's monadic values) and will return the value returned by the first parser to return a non mZero (null) value. | |||
== | <nowiki>parserMonad.mPlus = function() { | ||
var parsers = Array.prototype.slice.call(arguments) | |||
return function(str) { | |||
var result, i | |||
for (i = 0; i < parsers.length; ++i) { | |||
result = parsers[i](str) | |||
if (result !== null) { | |||
break; | |||
} | |||
} | |||
return result | |||
} | |||
}</nowiki> | |||
===Continuation Monad=== | |||
The continuation monad takes a bit to understand. In the chapter on function composition we saw that the composition of two functions f and g is given by | |||
<nowiki>(f . g) = f(g(x))</nowiki> | |||
f is known as the continuation of g. | |||
We also know that we can wrap values in a function by creating a closure. In the example below the inner function has a value wrapped in its closure. | |||
<nowiki> | |||
function(value) { | |||
return function() { | |||
// value can be accessed here | |||
} | |||
}</nowiki> | |||
The monadic value of a continuation monad, is a function that takes a continuation function and calls the continuation with its wrapped value. This is just like the inner function above called with a continuation function and we we can write it as | |||
<nowiki>function(continuation) { | |||
return continuation(value) | |||
}</nowiki> | |||
The mResult function of monad takes a value and lifts it to a monadic value. So we can write the mResult function for the continuation monad. | |||
<nowiki>var mResult = function(value) { | |||
return function(continuation) { | |||
return continuation(value) | |||
} | |||
}</nowiki> | |||
So mResult is a function that takes a value, returns a monadic value which you call with a continuation. | |||
The continuation monad itself or mBind is more complicated. | |||
<nowiki>var continuationMonad = function(mv, mf) { | |||
return function(continuation) { | |||
// we will add to here next | |||
} | |||
}</nowiki> | |||
First it will return a function you need to call with a continuation. Thats easy. But how can it unwrap the value inside mv? mv accepts a continuation, but calling mv with the continuation will not do. We need to unwrap the value in mv and call mf first. So we need to trick mv into giving us the value first by calling it with our own continuation thus. | |||
<nowiki>mv(function(value) { | |||
// gotcha! the value | |||
})</nowiki> | |||
We can add this function to the code above. | |||
<nowiki> | |||
var continuationMonad = function(mv, mf) { | |||
return function(continuation) { | |||
return mv(function(value) { | |||
// gotcha! the value | |||
}) | |||
} | |||
}</nowiki> | |||
Now all we have to do is call mf with the value. We know a monadic function takes a value and returns a monadic value. So we call the returned monadic value from mf with the continuation. Phew! Here is the complete code for the continuation monad. | |||
<nowiki> | |||
var continuationMonad = function(mv, mf) { | |||
return function(continuation) { | |||
return mv(function(value) { | |||
return mf(value)(continuation) | |||
}) | |||
} | |||
} | |||
continuationMonad.mResult = function(value) { | |||
return function(continuation) { | |||
return continuation(value) | |||
} | |||
}</nowiki> | |||
[ | ==JavaScript MD5== | ||
/* | |||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message | |||
* Digest Algorithm, as defined in RFC 1321. | |||
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 | |||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet | |||
* Distributed under the BSD License | |||
* See http://pajhome.org.uk/crypt/md5 for more info. | |||
*/ | |||
/* | |||
* Configurable variables. You may need to tweak these to be compatible with | |||
* the server-side, but the defaults work in most cases. | |||
*/ | |||
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ | |||
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ | |||
/* | |||
* These are the functions you'll usually want to call | |||
* They take string arguments and return either hex or base-64 encoded strings | |||
*/ | |||
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); } | |||
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); } | |||
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); } | |||
function hex_hmac_md5(k, d) | |||
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); } | |||
function b64_hmac_md5(k, d) | |||
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); } | |||
function any_hmac_md5(k, d, e) | |||
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); } | |||
/* | |||
* Perform a simple self-test to see if the VM is working | |||
*/ | |||
function md5_vm_test() | |||
{ | |||
return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72"; | |||
} | |||
/* | |||
* Calculate the MD5 of a raw string | |||
*/ | |||
function rstr_md5(s) | |||
{ | |||
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8)); | |||
} | |||
/* | |||
* Calculate the HMAC-MD5, of a key and some data (raw strings) | |||
*/ | |||
function rstr_hmac_md5(key, data) | |||
{ | |||
var bkey = rstr2binl(key); | |||
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8); | |||
var ipad = Array(16), opad = Array(16); | |||
for(var i = 0; i < 16; i++) | |||
{ | |||
ipad[i] = bkey[i] ^ 0x36363636; | |||
opad[i] = bkey[i] ^ 0x5C5C5C5C; | |||
} | |||
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); | |||
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128)); | |||
} | |||
/* | |||
* Convert a raw string to a hex string | |||
*/ | |||
function rstr2hex(input) | |||
{ | |||
try { hexcase } catch(e) { hexcase=0; } | |||
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; | |||
var output = ""; | |||
var x; | |||
for(var i = 0; i < input.length; i++) | |||
{ | |||
x = input.charCodeAt(i); | |||
output += hex_tab.charAt((x >>> 4) & 0x0F) | |||
+ hex_tab.charAt( x & 0x0F); | |||
} | |||
return output; | |||
} | |||
/* | |||
* Convert a raw string to a base-64 string | |||
*/ | |||
function rstr2b64(input) | |||
{ | |||
try { b64pad } catch(e) { b64pad=''; } | |||
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |||
var output = ""; | |||
var len = input.length; | |||
for(var i = 0; i < len; i += 3) | |||
{ | |||
var triplet = (input.charCodeAt(i) << 16) | |||
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) | |||
| (i + 2 < len ? input.charCodeAt(i+2) : 0); | |||
for(var j = 0; j < 4; j++) | |||
{ | |||
if(i * 8 + j * 6 > input.length * 8) output += b64pad; | |||
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); | |||
} | |||
} | |||
return output; | |||
} | |||
/* | |||
* Convert a raw string to an arbitrary string encoding | |||
*/ | |||
function rstr2any(input, encoding) | |||
{ | |||
var divisor = encoding.length; | |||
var i, j, q, x, quotient; | |||
/* Convert to an array of 16-bit big-endian values, forming the dividend */ | |||
var dividend = Array(Math.ceil(input.length / 2)); | |||
for(i = 0; i < dividend.length; i++) | |||
{ | |||
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); | |||
} | |||
/* | |||
* Repeatedly perform a long division. The binary array forms the dividend, | |||
* the length of the encoding is the divisor. Once computed, the quotient | |||
* forms the dividend for the next step. All remainders are stored for later | |||
* use. | |||
*/ | |||
var full_length = Math.ceil(input.length * 8 / | |||
(Math.log(encoding.length) / Math.log(2))); | |||
var remainders = Array(full_length); | |||
for(j = 0; j < full_length; j++) | |||
{ | |||
quotient = Array(); | |||
x = 0; | |||
for(i = 0; i < dividend.length; i++) | |||
{ | |||
x = (x << 16) + dividend[i]; | |||
q = Math.floor(x / divisor); | |||
x -= q * divisor; | |||
if(quotient.length > 0 || q > 0) | |||
quotient[quotient.length] = q; | |||
} | |||
remainders[j] = x; | |||
dividend = quotient; | |||
} | |||
/* Convert the remainders to the output string */ | |||
var output = ""; | |||
for(i = remainders.length - 1; i >= 0; i--) | |||
output += encoding.charAt(remainders[i]); | |||
return output; | |||
} | |||
/* | |||
* Encode a string as utf-8. | |||
* For efficiency, this assumes the input is valid utf-16. | |||
*/ | |||
function str2rstr_utf8(input) | |||
{ | |||
var output = ""; | |||
var i = -1; | |||
var x, y; | |||
while(++i < input.length) | |||
{ | |||
/* Decode utf-16 surrogate pairs */ | |||
x = input.charCodeAt(i); | |||
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; | |||
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) | |||
{ | |||
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); | |||
i++; | |||
} | |||
/* Encode output as utf-8 */ | |||
if(x <= 0x7F) | |||
output += String.fromCharCode(x); | |||
else if(x <= 0x7FF) | |||
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), | |||
0x80 | ( x & 0x3F)); | |||
else if(x <= 0xFFFF) | |||
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), | |||
0x80 | ((x >>> 6 ) & 0x3F), | |||
0x80 | ( x & 0x3F)); | |||
else if(x <= 0x1FFFFF) | |||
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), | |||
0x80 | ((x >>> 12) & 0x3F), | |||
0x80 | ((x >>> 6 ) & 0x3F), | |||
0x80 | ( x & 0x3F)); | |||
} | |||
return output; | |||
} | |||
/* | |||
* Encode a string as utf-16 | |||
*/ | |||
function str2rstr_utf16le(input) | |||
{ | |||
var output = ""; | |||
for(var i = 0; i < input.length; i++) | |||
output += String.fromCharCode( input.charCodeAt(i) & 0xFF, | |||
(input.charCodeAt(i) >>> 8) & 0xFF); | |||
return output; | |||
} | |||
function str2rstr_utf16be(input) | |||
{ | |||
var output = ""; | |||
for(var i = 0; i < input.length; i++) | |||
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, | |||
input.charCodeAt(i) & 0xFF); | |||
return output; | |||
} | |||
/* | |||
* Convert a raw string to an array of little-endian words | |||
* Characters >255 have their high-byte silently ignored. | |||
*/ | |||
function rstr2binl(input) | |||
{ | |||
var output = Array(input.length >> 2); | |||
for(var i = 0; i < output.length; i++) | |||
output[i] = 0; | |||
for(var i = 0; i < input.length * 8; i += 8) | |||
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32); | |||
return output; | |||
} | |||
/* | |||
* Convert an array of little-endian words to a string | |||
*/ | |||
function binl2rstr(input) | |||
{ | |||
var output = ""; | |||
for(var i = 0; i < input.length * 32; i += 8) | |||
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF); | |||
return output; | |||
} | |||
/* | |||
* Calculate the MD5 of an array of little-endian words, and a bit length. | |||
*/ | |||
function binl_md5(x, len) | |||
{ | |||
/* append padding */ | |||
x[len >> 5] |= 0x80 << ((len) % 32); | |||
x[(((len + 64) >>> 9) << 4) + 14] = len; | |||
var a = 1732584193; | |||
var b = -271733879; | |||
var c = -1732584194; | |||
var d = 271733878; | |||
for(var i = 0; i < x.length; i += 16) | |||
{ | |||
var olda = a; | |||
var oldb = b; | |||
var oldc = c; | |||
var oldd = d; | |||
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); | |||
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); | |||
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); | |||
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); | |||
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); | |||
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); | |||
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); | |||
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); | |||
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); | |||
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); | |||
c = md5_ff(c, d, a, b, x[i+10], 17, -42063); | |||
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); | |||
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); | |||
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); | |||
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); | |||
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); | |||
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); | |||
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); | |||
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); | |||
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); | |||
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); | |||
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); | |||
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); | |||
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); | |||
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); | |||
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); | |||
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); | |||
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); | |||
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); | |||
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); | |||
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); | |||
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); | |||
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); | |||
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); | |||
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); | |||
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); | |||
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); | |||
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); | |||
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); | |||
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); | |||
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); | |||
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); | |||
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); | |||
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); | |||
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); | |||
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); | |||
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); | |||
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); | |||
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); | |||
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); | |||
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); | |||
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); | |||
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); | |||
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); | |||
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); | |||
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); | |||
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); | |||
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); | |||
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); | |||
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); | |||
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); | |||
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); | |||
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); | |||
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); | |||
a = safe_add(a, olda); | |||
b = safe_add(b, oldb); | |||
c = safe_add(c, oldc); | |||
d = safe_add(d, oldd); | |||
} | |||
return Array(a, b, c, d); | |||
} | |||
/* | |||
* These functions implement the four basic operations the algorithm uses. | |||
*/ | |||
function md5_cmn(q, a, b, x, s, t) | |||
{ | |||
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); | |||
} | |||
function md5_ff(a, b, c, d, x, s, t) | |||
{ | |||
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); | |||
} | |||
function md5_gg(a, b, c, d, x, s, t) | |||
{ | |||
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); | |||
} | |||
function md5_hh(a, b, c, d, x, s, t) | |||
{ | |||
return md5_cmn(b ^ c ^ d, a, b, x, s, t); | |||
} | |||
function md5_ii(a, b, c, d, x, s, t) | |||
{ | |||
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); | |||
} | |||
/* | |||
* Add integers, wrapping at 2^32. This uses 16-bit operations internally | |||
* to work around bugs in some JS interpreters. | |||
*/ | |||
function safe_add(x, y) | |||
{ | |||
var lsw = (x & 0xFFFF) + (y & 0xFFFF); | |||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16); | |||
return (msw << 16) | (lsw & 0xFFFF); | |||
} | |||
/* | |||
* Bitwise rotate a 32-bit number to the left. | |||
*/ | |||
function bit_rol(num, cnt) | |||
{ | |||
return (num << cnt) | (num >>> (32 - cnt)); | |||
} | |||
Источники: | |||
<hr> | |||
* [http://pajhome.org.uk/crypt/md5/md5.html md5.js] | |||
* [https://tproger.ru/blogs/js-obfuscation/ Прячем JavaScript-код на фронтенде от посторонних] |
Текущая версия от 21:13, 22 июля 2019
Фишечки
- Техника скрытой идентификации системы и браузера.
...
Определение производится на основе выделения свойственных для разных браузеров шаблонов состояния свойств в JavaScript и характеристик времени выполнения операций, зависящих от особенностей работы JIT, CPU и механизмов выделения памяти. Определение свойств производится через генерацию списка всех объектов, доступных из JavaScript. Как оказалось число объектов напрямую коррелирует с браузерным движком и его версией.
function getProperties(o) { var result = []; while (o !== null) { result = result.concat(Reflect.ownKeys(o)); o = Object.getPrototypeOf(o); } return result; } ...
Например, для Firefox заявлена поддержка 2247 свойств, в то время как реальное число рекурсивно определённых свойств составляет 15709 (в Tor Browser - 15639), для Chrome заявлено 2698 свойств, а реально предлагается 13570 (в Chrome для Android - 13119). Число и значения свойств меняются от версии к версии браузера и при применении различных операционных систем.
Значения и наличие тех или иных свойств можно использовать для определения типа ОС. Например, в Kubuntu свойство window.innerWidth выставляется в значение 1000, а в Windows 10 - в 1001. В Windows доступно свойство window.navigator.activeVRDisplays, а в Linux его нет. Для Android предоставляется множество специфичных вызовов, но нет window.SharedWorker. Для идентификации операционной системы также предлагается использовать анализ параметров WebGL, состояние которых зависит от драйверов. Кроме того, вызов WEBGL_debug_renderer_infoextension позволяет получить информацию о движке отрисовки OpenGL, который для каждой операционной системы разный.
Для определения CPU применяется оценка различий во времени выполнения различных типовых блоков кода, обработка которых зависит от архитектуры набора команд с учётом поведения JIT (определяется сколько регистров CPU будет задействовано и в каких случаях JIT сгенерирует эффективный код с оптимизациями и вовлечением расширенных инструкций, а когда нет). Для определения типа системы распределения памяти и операционной системы также измеряется различие времени выделения памяти для различных структур, по которым можно судить о размере блоков памяти.
Определённые в ходе выполнения скрипта параметры сравниваются с эталонными значениями, свойственными для заранее протестированных окружений. В ходе проверки разработанная техника позволила точно определить 40 различных тестовых окружений, определив версии используемых браузеров, производителя CPU, применяемую операционную систему и факт запуска на реальном оборудовании или в виртуальной машине.
Как бы учебник
- Операторы JavaScript
- Массивы JavaScript
- Объекты JavaScript
- Особенности функций в JavaScript
- Базовые Namespace паттерны JavaScript
- Шаблоны проектирования JavaScript
- ООП JavaScript - наследование
- Область видимости переменной в Javascript
- Клиентский (браузерный) JavaScript
- JavaScript Strict Mode
- Масштабируемые JavaScript приложения
- 10 несуразностей и секретов JavaScript
- Используем console на полную
Крайне полезная информация
Это надо обработать
- Тесты производительности циклов, работы с массивами и т.д.
- JavaScript Гарден
- Модульный подход в JavaScript
- Случайное перемешивание массива
- Javascript наследование для чайников
- Операторы, их особенности в JS
- JavaScript Comparison and Logical Operators
- Путь асинхронного самурая
- «Лапша» из callback-ов — будьте проще
- Как избавиться от пристрастия к синхронности
- Спагетти в последовательном вызове асинхронных функций. Теория и практика
- javascript - XMLHttpRequest в цикле
- javascript - XMLHttpRequest подробный анализ примера
- Многопоточный яваскрипт
- работаем с объектами в примерах
- 10 лучших функций на JavaScript - в комментах тьма полезностей
- ОПТИМИЗИРУЕМ JS
- http://pyramidin.narod.ru/clientref13/ -очень хорошо описаны функции, их стандартные методы, свойства и использование
- Побитовые или поразрядные операции в конце для js
- Прототипы в JavaScript
- Стыкуем компоненты в JavaScript
- Стыкуем асинхронные скрипты
- GCC: интеграция с Google Closure Library
- 3d -javascipt
- свой Uploader с нуля
- document-write
- создаем элементы DOM
- Заполнение массива одинаковыми значениями
- Производительность простых и сложных конструкций в JavaScript
- Почему функция map не работает с некоторыми массивами в JavaScript и что с этим делать
обработка событий в примерах
Контекст
- Ключевое слово this в javascript — учимся определять контекст на практике
- Привязка контекста (this) к функции в javascript и частичное применение функций
- Правильный захват контекста в Javascript
- JavaScript: что делает Function.call.apply(…)?
- Стратегия (шаблон проектирования)
Библиотеки
- JavaScript-фреймворк qooxdoo 1.6 - новое слово в вебинтерфейсах
- Новость про qooxdoo 1.6
- JS библиотека для работы с большими числамиДемо
- 3D библиотека java script
Несколько интересных проектов с помощью которых можно динамически создавать векторные рисунки.
- Статья - Пробуем ремесло рисования с помощью ArtisanJS
- Страница разработчика ArtisanJS
- Высокопроизводительная библиотека динамического рисования Html5 - полностью самодостаточная
- Кросс-браузерное решение
- Поддерживается как свободное рисование так и работа с векторными формами
- Pixi.js — 2D движок с прозрачной поддержкой WebGL
Полезные трюки
http://habrahabr.ru/post/155093/
Как работает Array.prototype.slice.call
Для того, чтобы из аргументов JavaScript функции "отрезать" первые Х значений, используется метод:
Array.prototype.slice.call(arguments, X);
Например такая функция вернет "3,4":
(function(){ var args = Array.prototype.slice.call(arguments, 2); alert(args); // Returns: 3,4 })(1, 2, 3, 4);
Таким образом, такой подход используется когда нужно срезать входящие параметры функции JavaScript.
Array.prototype.concat.apply
Для объединения более двух массивов используется concat().
var a = [1, 2], b = ["x", "y"], c = [true, false]; var d = a.concat(b, c); console.log(d); // [1, 2, "x", "y", true, false];
For concatenating just two arrays, using push.apply() can be used instead for the case of adding elements from one array to the end of another without producing a new array. With slice() it can also be used instead of concat() but there appears to be no performance advantage from doing this.
var a = [1, 2], b = ["x", "y"]; a.push.apply(a, b); console.log(a); // [1, 2, "x", "y"];
Современный метод bind
В современном JavaScript для привязки функций есть метод bind. Он поддерживается большинством современных браузеров, за исключением IE<9, но легко эмулируется.
Этот метод позволяет привязать функцию к нужному контексту, и даже к аргументам.
Синтаксис bind:
var wrapper = func.bind(context[, arg1, arg2...])
- func Произвольная функция
- wrapper Функция-обёртка, которую возвращает вызов bind. Она вызывает func, фиксируя контекст и, если указаны, первые аргументы.
- context Обертка wrapper будет вызывать функцию с контекстом this = context.
- arg1, arg2, … Если указаны аргументы arg1, arg2... — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове.
Простейший пример, фиксируем только this:
function f() { alert(this.name); } var user = { name: "Вася" }; var f2 = f.bind(user); f2(); // выполнит f с this = user
Использование для привязки метода sayHi к новому объекту User:
function User() { this.id = 1; this.sayHi = function() { alert(this.id); }.bind(this); } var user = new User(); setTimeout(user.sayHi, 1000); // выведет "1"
Object.defineProperty или как сделать код капельку лучше
оригинал статьи тоже ребята старались
Этот краткий пост-заметку или температурный бред (в Одессе похолодало, да) хочу посвятить такой прекрасной функции, как Object.defineProperty (и Object.defineProperties). Активно использую её уже около двух месяцев, так как поддержка старых браузеров (в том числе и IE8) в проекте, который я сейчас реализую, не требуется (завидуйте).
Как положено статье на хабре, приведу краткое описание того, что она делает. Object.defineProperty добавляет новое свойство, обладающее неким нестандартным для обычного свойства поведением, и принимает три аргумента:
- Объект, который мы модифицируем, добавляя новое свойство
- Свойство (строка), которое, собственно, хотим добавить
- Дескриптор: объект, содержащий «настройки» нового свойства, например аццессоры (геттер, сеттер)
Дескриптор может содержать следующие свойства:
- value (любое значение: строка, функция...) — значение, которое получит определяемое свойство объекта (геттер и сеттер в данном случае определить нельзя)
- writable (true/false) — можно ли перезаписать значение свойства (аццессоры тоже не доступны)
- get (функция) — геттер (value и writable определить нельзя)
- set (функция) — сеттер (value и writable определить нельзя)
- configurable (true/false) — можно ли переопределить дескриптор (использовать Object.defineProperty над тем же свойством)
- enumerable (true/false) — будет ли свойство перечисляться через for..in и доступно в Object.keys (плохая формулировка)
var o = {}; Object.defineProperty(o, "a", {value : 37, writable : true, enumerable : true, configurable : true}); var bValue; Object.defineProperty(o, "b", {get : function(){ return bValue; }, set : function(newValue){ bValue = newValue; }, enumerable : true, configurable : true});
Лучше меня объяснит MDN Object/defineProperty. Благо, даже английский знать не надо, и так всё понятно.
Если нужно определить сразу несколько свойств, можно использовать Object.defineProperties, который принимает два аргумента: объект, требующий изменений и объект с определяемыми ключами. MDN: Object/defineProperties.
// Код сперт с MDN Object.defineProperties(obj, { "property1": { value: true, writable: true }, "property2": { value: "Hello", writable: false } // etc. etc. });
Теперь соль. Чего я вообще решил это запостить?
Так как в упомянутом выше проекте мне приходится использовать defineProperty не просто активно, а очень активно, код стал, мягко говоря, некрасивым. Пришла в голову простейшая идея (как я до этого раньше-то не додумался?), расширить прототип Object, сделав код сильно компактнее. Плохой тон, скажете вы, засерать прототип Object новыми методами.
Откуда вообще взялось это мнение? Потому что все объекты унаследуют это свойство, которое, при обычной модификации прототипа, становится перечисляемым в for..in. На душе становится тепло, когда вспоминаешь о том, что я описал выше, а именно, о свойстве дескриптора enumerable. Действительно, расширив прототип таким образом:
Object.defineProperty( Object.prototype, 'logOk' { value: function() { console.log('ok') }, enumerable: false });
все объекты получат этот метод, но, при этом, он будет неперечисляемым (не нужно каждый раз использовать hasOwnProperty для проверки, есть ли такое свойство):
var o = {a: 1, b: 2} for( var i in o ) console.log( i, o[ i ] ); > a 1 > b 2 o.logOk(); > ok
Собственно то, ради чего я тут графоманю.
Во-первых определим метод define, чтоб каждый раз не вызывать перегруженную, на мой взгляд, конструкцию. Во-вторых определим метод extendNotEnum, который расширяет объект неперечисляемыми свойствами.
Object.defineProperties( Object.prototype, { define: { value: function( key, descriptor ) { if( descriptor ) { Object.defineProperty( this, key, descriptor ); } else { Object.defineProperties( this, key ); } return this; }, enumerable: false }, extendNotEnum: { value: function( key, property ) { if( property ) { this.define( key, { value: property, enumerable: false, configurable: true }); } else { for( var prop in key ) if( key.hasOwnProperty( prop ) ){ this.extendNotEnum( prop, key[ prop ] ); } } }, enumerable: false } });
Использование:
var o = { a: 1 }; o.define( 'randomInt', { get: function() { return 42; } }); o.extendNotEnum({ b: 2; }); for( var i in o ) console.log( i, o[ i ] ); > a 1 > randomInt 42 console.log( o.b ); > 2
И пошла… Еще два удобных метода:
Object.prototype.extendNotEnum({ extend: function() { var args = Array.prototype.slice.call( arguments ); args.unshift( this ); return $.extend.apply( null, args ); // если jQuery надоест, можно просто переписать под себя }, each: function( callback ) { return $.each( this, callback ); // аналогично } }); o.extend({c: 3}); // тупо добавляет новые свойства в объект o.each(function( key, value ) { // просто повторяет механизм $.each, перебирая все ключи и свойства });
Добавлять новые свойства можно до бесконечности, никто вам за это руку не откусит.
Вывод
Играйте в Денди, пишите на Javascript, который становится всё лучше и лучше.
комментарии
Еще один финт ушами для тех, кто определяет свойства прототипа только однажды, после определения функции-конструктора:
// запускается в консоли
Object.defineProperty( Object.prototype, 'awesomeprototype', {
set: function( object ) {
for( var prop in object ) {
Object.defineProperty( this.prototype, prop, {
value: object[ prop ],
enumerable: false
});
}
}
});
var X = function() {}
X.awesomeprototype = {
method: function() { alert( 'ok' ) }
};
var x = new X
x.method()
Стоит отметить что в литералах объектов свойства можно задавать через геттер и сеттер:
var o = { __someProperty : 42, get someProperty() { return this.__someProperty; }, set someProperty(v) { this.__someProperty = v; } }; o.someProperty; // 42 o.someProperty = 56; o.someProperty; // 56
Ну это так, к слову :)
deferred
- Асинхронные API и объект Deferred в деталяхGitHub
- Объект Deferred.
- Асинхронное программирование: концепция Deferred
- Deferred: все подробности
Шаблонизация в JavaScript
DOM
dom2json
http://coderepos.org/share/browser/lang/javascript/dom2json/dom2json.js?rev=30780 skips empty attributes that IE implicitly adds
/* * $Id: dom2json.js,v 0.2 2008/06/15 07:04:10 dankogai Exp dankogai $ */ (function(){ dom2json = function(dom){ var type = dom.nodeType; if (type == 3) return dom.nodeValue; if (type == 1){ var json = []; json.push(dom.nodeName); var attrs = dom.attributes; if (attrs.length){ var attr = {}; for (var i = 0, l = attrs.length; i < l; i++){ attr[attrs[i].name] = attrs[i].value; } if (0 /*@cc_on +1@*/){ attr['style'] = dom.style; } json.push(attr); } if (! dom.hasChildNodes()) return json; var kids = dom.childNodes; for (var i = 0, l = kids.length; i < l; i++){ var kjson = arguments.callee(kids[i]); if (kjson) json.push(kjson); } return json; } }; json2dom = function(json){ var i = 0; var tag = json[i++] var dom = document.createElement(tag); if (json[i].constructor == Object){ var attr = json[i++]; for (var name in attr){ dom.setAttribute(name, attr[name]); } } for (var l = json.length; i < l; i++){ dom.appendChild( json[i].constructor == Array ? arguments.callee(json[i]) : document.createTextNode(json[i]) ); } return dom; } })();
обход дерева DOM
/** * Рекурсивное перечисление дочерних элементов * * @param DomNode node * Родительский элемент, чьи дочерние узлы нужно перечислять. * * @return void */ function enumChildNodes(node) { // если нам передали элемент if (node && 1 == node.nodeType) { // берем его первый дочерний узел var child = node.firstChild; // пока узлы не закончились while (child) { // если этот узел является элементом if (1 == child.nodeType) { // что-то делаем с найденным элементом alert('элемент ' + child.tagName); // рекурсивно перечисляем дочерние узлы enumChildNodes(child); }; // переходим к следующему узлу child = child.nextSibling; }; }; }; // перечисляем содержимое body enumChildNodes(document.body);
Обход child nodes (потомков) определнного элемента в Javascript
JavaScript на заметку Комментировать Речь о том как корректно обойти элементы потомки DOM для данного элемента, простите за тафтологию
var object = document.getElementById('el'); for (var childItem in object.childNodes) { if (object.childNodes[childItem].nodeType == 1) object.childNodes[childItem].style.color = '#FF0000';
В данном примере мы берем некоторый элемент с id = 'el' и проходим по массиву его потомков (childNodes), при этом проверяем nodeType потомка, чтобы он был элементом страницы и если так, то окрашиваем его в КРАСНЫЙ цвет.
В принципе на базе этой конструкции можно организовать рекурсивный обход дерева всех потомков заданного элемента, если конечно возникнет такая необходимость, но пока я представляю только упрощенный вариант.
Здесь следует обратить внимание на проверку свойства nodeType == 1, дело в том что без этой проверки в обработку попадут и разрывы строк, т.е. символы "\n", которые тоже воспинимаются как ноды. Т.е. например конструкция вида:
так быстрее и меньше кода
var object = document.getElementById(‘el’).getElementsByTagName(‘*’); for(var i=0;object[i];i++){ object[i].style.color = ‘#FF0000′;
Sun, getElementsByTagName не поддерживает ИЕ, смысл его использовать?
К сожалению заметил, что данная конструкция в Opera выполняется некорректно (цикл for проходится два раза), никто с таким не встречался? Где-то всё же закралась моя ошибка или это ошибка браузера (проверял в 9.20, 9.50, 10.20), замечу, что в остальных браузерах всё выполняется верно.
var object = document.getElementById(‘el’); for (var childItem in object.childNodes) { } var object=document.getElementById(‘el’); for(var i=0;i<object.childNodes.length;i++) { if (object.childNodes[i].nodeType == 1) { // тут что-либо делаем } }
Functional JavaScript
Introduction to Functional JavaScript
JavaScript was a functional programming language even before it got its name! Back in 1995 Netscape Communications the makers of the Netscape Browser realized that their browser needed a scripting language embedded in HTML. They hired Brendan Eich to embed the Scheme programming language in HTML. Scheme was a full fledged functional programming language. To his dismay “the powers that be” encouraged Brendan to come up with a more traditional programming language. His first cut was a language called “Mocha” in May 1995, and it retained that name till December 1995. It was first renamed “LiveScript” and soon after that Netscape and Sun did a license agreement and it was called “JavaScript”. But by then Brendan Eich had sneaked in some “functional programming” features into JavaScript.
The story gets more complicated, and we will delve into it. Because this story will tell you why JavaScript is what it is today. Brendan Eich claims that hiring him to embed the Scheme language, might actually have been a “bait and switch operation”. But at that point in time Netscape was also negotiating with Sun to embed Java in the browser. Note that JavaScript was for embedding in HTML while Java was for embedding in the Browser. The idea was that Java would be used for component development while JavaScript would be used for lightweight scripting within HTML.
We don't know what actually transpired, but the orders from above to Brendan where clear. The new scripting language must “look like Java” and must be “object based”. Any hopes Brendan might have harboured for Scheme, where now out of the window. We will see why.
Programming Paradigms We can only speculate on what “look like Java” meant. However we can be certain that it had to be a “curly brace” language. Curly brace languages define statement blocks using curly braces, the “{“ and “}” characters. Indeed Java syntax was fashioned on another curly brace language “C++”, which in turn was fashioned on “C”. Here is how a for-loop looks in Java.
for (int i = 0; i < 10; i++) { System.out.println("Hello"); System.out.println("World"); }
The same for-loop in C.
int i; for (i = 0; i < 10; i++) { printf("Hello\n"); printf("World\n"); }
And the for-loop in JavaScript.
for (var i = 0; i < 10; i++) { console.log("Hello"); console.log("World"); }
Indeed JavaScript does look like Java. And C too. However curly braces where not the only implications for a language to “look like Java”. Java is an imperative/object oriented style programming language. JavaScript also had to be an imperative/object oriented style language.
Programming languages are made up of operators, conditional statements, loop statements and functions. Having conditional statements and loop statements are hallmarks of an “imperative” style programming language. Functional style languages tend to support operators and functions only.
It is interesting that none of the three languages, Java, C++ and C, were functional programming languages. While C was an imperative programming language, C++ and Java were Imperative/Objected Oriented programming languages. By now you would have guessed that the three programming paradigms (styles) were imperative, object oriented and functional. There is one more, the declarative paradigm.
The differences between these paradigms are because of the foundations on which they were based. Imperative and object oriented programming are based on the “turing machine”. Functional programming is based on “lambda calculus” and declarative programming is based on “first order logic”. In this post we will look at the differences between, imperative, object oriented and functional programming at a more practical level.
An imperative programming language is one in which program state change is achieved by executing a series of statements, and does flow control primarily using conditional statements, loop statements and function calls. The program given below is a simple implementation of the JavaScript Array.join method in an imperative manner.
function simpleJoin(stringArray) { var accumulator = ''; for (var i=0, l=stringArray.length; i < l; i++) { accumulator = accumulator + stringArray[i]; } return accumulator; }
The code above is straight forward. We iterate through an array and add each string element to the accumulator and return the accumulator. We will now rewrite this function in an object oriented manner. Since JavaScript has an Array class, we will add this method to the Array class, so that every instance of this class has access to this function. JavaScript use prototypal inheritance and so we add this function to the Array prototype.
Array.prototype.simpleJoin = function() { var accumulator = ""; for (var i=0, l=this.length; i < l; i++) { accumulator = accumulator + this[i]; } return accumulator; }
As we can see, the object oriented version is quite like the imperative version, except that the function (method) is now a method of the class. Object oriented languages tend to be imperative languages also.
Now let us write the functional version of this function.
function simpleJoin(stringArray, i, accumulator) { if (i === stringArray.length) { return accumulator; } else { return simpleJoin(stringArray, i+1, accumulator+stringArray[i]) } }
The first thing to note is that we are not using the for loop here for iteration. Instead we use recursion for iteration. Recursion happens when the function calls itself from within itself. Indeed this is one of the characteristics of a functional programming language. eg. Scheme does not have any loop statements. Instead it uses recursion for iteration. The function is called for the first time with the given array in stringArray, i set to 0, and accumulator set to "". The second time around the function is called from within itself with the same stringArray, i set to i + 1, and accumulator set to accumulator + stringArray[i]. And we continue the same way until i === stringArray.length when we return the accumulator. We will discuss recursion in detail later in a later post. Just remember we used recursion for doing iteration here.
There is still something imperative about this function. The if statement. Functional languages tend to use expressions that evaluate to some value, instead of statements that don't evaluate to anything. So let us rewrite the function, to make it as functional as possible in JavaScript.
function simpleJoin(stringArray, i, accumulator) { return (i === stringArray.length) ? accumulator : simpleJoin(stringArray, i + 1, accumulator + stringArray[i]) }
Now this as functional as you can get with JavaScript. Instead of the if statement we return the value evaluated by the conditional operator ?. The conditional operator ? Takes a conditional expression and returns the value of one of the two expressions based the condition being true or false. The value of the first expression is returned if true and the second if false.
We can see that the functional version is concise. Indeed one of the advantages of functional programming is that it lends itself to lesser code to accomplish the same thing, leading to better readability and maintainability.
However in the case of JavaScript, as of now you cannot use recursion for doing iteration. You should continue to use the imperative or object oriented method for iteration. This is because JavaScript does not (yet) support “tail call optimization”. For doing proper recursion, tail call optimization is required. We will discuss tail recursion, and tail call optimization, and how to get around this problem in a future post. As of writing this post tail call optimization is expected in ecmascript 6.
So is JavaScript an imperative language, or an object oriented language, or a functional language? It is a multi paradigm language. It does not have all the functional features implemented. But it is slowly getting there. This is also true of most other languages. Most languages (other than functional languages to begin with) have added functional features to various degrees over the years. A good example of the multi paradigm nature of JavaScript is the Array.forEach method. Here is a possible simple implementation. Note that all modern browsers have already implemented this.
Array.prototype.forEach = function(callback) { for (var i = 0, len = this.length; i < len; ++i) { callback(this[i], i, this); } }
In the code above the for-loop part of the code is imperative. Adding to the array prototype and usage of this is object oriented. Passing a function as an argument to another function (callback) is functional and is a feature of functional programming known as “higher order function”. In JavaScript, we take this for granted, passing functions as an argument. Surprisingly this was not a feature found in the most popular languages until recently. eg. You cannot pass functions as arguments in Java, though you can do it indirectly via Interfaces. Same is the case with C, though you can do it indirectly using pointers.
Programming Abstractions What if JavaScript did not have the “passing functions as arguments feature”? The answer to this question is crucial to understanding what functional programming brings to the table. For one, we would not be able to write the Array.forEach function above. And even worse, every time we had to iterate over an array, we would have to write the same for-loop again and again. If we had Array.foreach we need to think only about writing the callback. We need to think only of what to do with each element of the array, and need not concern ourselves with iterating over the array. In other words we have “abstracted” away the iteration part of the problem, and freed ourselves to concentrate on each element of the array.
Abstractions are about hiding away implementation details, thereby reducing and factoring out details, so that programmers can focus on the problem at a higher level. Abstractions can be code abstractions, as we saw in the example above, and data abstractions. Objected oriented programming is about combining code and data abstractions into one abstraction called the class. In functional programming we use functional features like first class functions, nested functions, anonymous functions and closures to abstract away code and sometimes even data. Monads are an esoteric feature of functional programming that can even abstract away program structure!
Macro's are another feature that allows code abstraction. However macros are not a feature of functional programming. But they are found in all Lisp like functional languages like Lisp, Scheme, Clojure etc. There are attempts to bring Macro's to JavaScript and at the moment it is very much early days.
A good example of the power of functional abstractions is jQuery. jQuery is the mother of all functional abstractions. It has abstracted away the JavaScript language itself! So much so that you wouldn't be surprised, if you found a jQuery programmer who knows very little or no JavaScript. As of Feb 2013 there was one publisher who had 32 jQuery books listed, and not a single JavaScript book! And jQuery has achieved so much mostly using only two functional features, functions as arguments and closures.
First Class Functions and Closures The functional programming feature that Brendan Eich implemented in JavaScript was “first class functions”. Functions are first class if they are treated as “first class citizens” of that language. Which implies functions are treated just like all other variables. ie. You can pass them as arguments to functions, you can return them as values from other functions, or you can assign them to variables or data structures. We have seen “passing functions are arguments” earlier. Here is an example of assigning a function to a variable.
function greet(name) { console.log("Hello " + name); } greet("John"); // "Hello John" var sayHello = greet; sayHello("Alex"); // "Hello Alex"
Some programming language theorists consider “anonymous functions” as first class functions. Not to be outdone, Brendan Eich threw anonymous functions into the mix. This is like letting the cat among the pigeons so to speak. But not for Brendan Eich, he knew the solution to the problem. Here is an anonymous function in JavaScript.
function(name) { console.log(“Hello “ + name); }
If you noticed we did not give this function a name. After all, it is an anonymous function. If you try to run the code above, you will get an error. Something to the effect “you cannot run the code in this context”. And rightly so. They can only be assigned to something, or passed as arguments to a function.
var sayHello = function(name) { console.log(“Hello “ + name); } sayHello("Jane"); // "Hello Jane"
What if we wanted to change the greeting? Sometimes we would like to say “Hi” instead of “Hello”. We could create a generic “createGreeting” function, which would in turn “compose” another function for you, and return the new composed function. So if we wanted to sat “Hi” it would return a function, and if we wanted to say “Hello” it would return another function that says “Hello”. We can do all that because JavaScript supports first class functions, and we can return functions from other functions. Here is the code.
function createGreeting(greeting) { return function(name) { console.log(greeting + " " + name); } } var sayHi = createGreeting("Hi"); sayHi("Jack"); // "Hi Jack" var sayHello = createGreeting("Hello"); sayHello("Jack"); // "Hello Jack"
The createGreeting function takes a greeting as its argument. The function returns a newly created anonymous function. However the newly created anonymous function was created inside another function createGreeting. So it is also a nested function now. Now since our language supports anonymous functions it will also have to support nested functions. And when we return nested functions from our function we run into another problem. We will look at that in more detail.
The anonymous function takes a name argument and prints to the console greeting + name. The variable name is an argument to the anonymous function, and behaves just like any other variable defined within the function. In other words name is “local” to the anonymous function. But this is not true of the variable greeting. It is defined in another function called createGreeting and hence is “non local” to the anonymous function. However the anonymous function can access the variable greeting due to something called lexical scoping.
The “scope” of a variable is its “visibility” within a program. “Lexical scope” means that visibility is limited to all the text (code). So when we say “local variables are lexically scoped” within a function, it means that the function's local variables are visible to all the text (code) in the function, even if the code is within another nested function. This also means that when you run the nested function outside the lexically scoped environment, the nested functions non local variable will not be visible. And there lies the problem of returning nested functions from another function. And indeed thats what we are doing here.
var sayHi = createGreeting("Hi");
In the line above we assign the returned anonymous function to variable sayHi. And call the function in the next line.
sayHi(“Jack”)
We are calling sayHi outside of createGreeting. And the greeting variable is not available outside of createGreeting. The variables it may access in the scope where it was defined, may not be available in the scope where it was actually called. Thats why languages like C don't support nested functions. For this to work the language needs to support another functional programming feature called “closures”. JavaScript supports closures. As matter of fact it has to support closures. Any language that supports first class functions and nested functions has to support closures.
A function's closure is a reference to all its non local variables. In the previous example greeting was the non local variable and name was the local variable. A closure is a table of references to all of a functions non local variables. This allows the function to continue to refer to the non local variable even when the function is out of the scope of the variables.
Implementing Monads in JavaScript
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html
Consider the problem of doing a series of computations (calling a series of functions), and you want each successive computation to have access to the results of the previous computations. We will write a function called doComputations, and here is how a call to doComputations would look like.
var result = doComputations( "a", function(scope) { return 2; }, "b", function(scope) { with (scope) { return a * 3; } }, function(scope) { with(scope) { return a + b; } } );
The arguments to doComputaions are one or more "string - function" pairs and the last argument is just a "result" function. The string is a variable name which will be assigned the value returned from calling the function. So in the first case "a" will be assigned the value 2. What is interesting is that "a" is visible inside the next function whose value gets assigned to "b". And both "a" and "b" are visible inside the last function. Every function is called with a "scope" object which carries key value pairs corresponding to the previous computations carried out. Here we use the "with" statement to scope the "scope" object within the function. If you don't want to use the "with" statement you could access the variable from the scope object directly eg. scope.a, scope.b. The value returned by doComputations is the value returned by the last "result" function, in this case the final value is 8. And here is the definition of doComputations.
function doComputations() { var args = arguments; var scope = {}; function iterator(i) { if (args.length === i + 1) {return args[i](scope);} var varName = args[i]; var func = args[i + 1]; var value = func(scope); scope[varName] = value; return iterator(i + 2); } return iterator(0); }
Inside doComputations we define an iterator function, which recursively iterates over the arguments array of doComputations. In the first line of the iterator function we check to see if we have reached the last "result function", if yes we call it with scope and return the result. In the next three lines we create three variables initialised to the variable name, function, and value returned by calling the function with the scope. In the next line we attach the key-value to scope. And finally we make a recursive call to the iterator to do the next computation. In the last line of doComputations we start the iterator with initial values 0 for the index.
Copy the two code fragments above into a file, add the a final line:
console.log(result);
and run it. You should get the result as 8.
All this looks like lots of work just to add and multiply a couple of integers, but we have done something useful. For one we have abstracted away the nitty gritty of iterating over computations, with visibility of previous results, into a function called doComputations.
Computations are not always so simple and straight forward as the one above. What if we wanted to abort the computations midway for some reason? eg. If any of the functions returns "null" we want to abort the computations. There are many other types of computations and to write a version of doComputations for each type is not a good idea. Instead we could make doComputations call another function between computations so that any thing different, we want to do, is done in this function. This function is passed to doComputations as its first argument. We will call this function "mBind". Now all we have to do is write a version of mBind for every type of computation. For every computation, doComputations will call mBind which in turn will call the next computation. First we write the mBind function to handle null values returned by any computation.
var mBind = function(mValue, mFunction) { if (mValue === null) return null; else return mFunction(i + 2); }
Now the iterator function will call mBind, which is passed as an argument to doComputations, which in turn will recursively call the iterator.
function doComputations(mBind) { var args = arguments; var scope = {}; function iterator(i) { if (args.length === i + 1) {return args[i](scope);} var varName = args[i]; var func = args[i + 1]; var value = func(scope); return mBind(value, function() { scope[varName] = value; return iterator(i + 2); }); } return iterator(1); }
Below we call doComputations whose first argument is the mBind function. Also we want to abort the computations in case the browser does not support the console.log function.
var result = doComputations(mBind, "a", function(scope) { if (typeof console === "object" && console.log) return 2; else return null; }, "b", function(scope) { with (scope) { return a * 3; } }, function(scope) { with(scope) { return a + b; } } );
We can now use doComputations for various types of computations by simply changing the mBind function passed to it. It would be even better if we could predefine the mBind function for various types of computations. And that is what we will do below. We will also change the name of doComputations to doMonad. And we will add mBind as the property of an object called "monad".
var maybeMonad = { mBind: function(mValue, mFunction) { if (mValue === null) return null; else return mFunction(mValue); } }; function doMonad(monad) { var args = arguments; var scope = {}; function iterator(i) { if (args.length === i + 1) {return args[i](scope);} var varName = args[i]; var func = args[i + 1]; var value = func(scope); return monad.mBind(value, function() { scope[varName] = value; return iterator(i + 2); }); } return iterator(1); }
Compare the above code to the previous listing. It is pretty much the same, except that we have renamed doComputations, and the mBind function is now passed as the property of an object, and this object is called a monad, and in this specific case we called the monad the "maybeMonad". Because "maybe" the computations are carried out, or "maybe" they won't be.
A monad MUST have two properties defined for it to be a proper monad. "mBind" and "mResult". We have not seen mResult so far. mResult is a wrapper function for the "result" function. So we add support for mResult in doMonad below. Also we define a new monad called the arrayMonad below and we do some computations with the it.
function doMonad(monad) { var args = arguments, scope = {}; function iterator(i) { if (args.length === i + 1) { return monad.mResult(args[i](scope)); } var varName = args[i]; var func = args[i + 1]; var value = func(scope); return monad.mBind(value, function(value) { scope[varName] = value; return iterator(i + 2); }); } return iterator(1); } var arrayMonad = { mBind: function(mValue, mFunc) { var accum = []; mValue.forEach(function(elem){ accum = accum.concat(mFunc(elem)); }); return accum; }, mResult: function(value) { return [value]; } } var result = doMonad(arrayMonad, "a", function() { return [1, 2]; }, "b", function() { return [3, 4]; }, function(scope) { with(scope) { return a + b; } } ); console.log(result);
Running the code above will yield a result of [ 4, 5, 5, 6 ]. The computations using the arrayMonad each return an array. The final result function is called with values a and b, for each element of both arrays. ie it will be called with (1,3), (1,4), (2,3), (2,4). And the addition of each of the elements yields the returned array of [ 4, 5, 5, 6 ].
Using the arrayMonad let us implement a two dimensional iterator function in JavaScript called forEach2D. It will take 3 arguments, an iArray, a jArray, and a callback. The callback is called for each value of i and j. Here is the code below.
function forEach2D(iArray, jArray, callback) { return doMonad(arrayMonad, "i", function() { return iArray; }, "j", function() { return jArray; }, function(scope) { with(scope) { return callback(i, j); } } ); } var result = forEach2D([1, 2, 3], [4, 5, 6], function(i, j) { return [i, j]; }); console.log(result);
Running the code above will yield result:[ [1, 4],[1, 5],[1, 6],[2, 4],[2, 5],[2, 6],[3, 4],[3, 5],[3, 6] ]
How about a function for iterating over three arrays? A forEach3D function. Easy!
function forEach3D(iArray, jArray, kArray, callback) { return doMonad(arrayMonad, "i", function() { return iArray; }, "j", function() { return jArray; }, "k", function() { return kArray; }, function(scope) { with(scope) { return callback(i, j, k); } } ); } var result = forEach3D([1, 2], [3, 4], [5, 6], function(i, j, k) { return [i, j, k]; }); console.log(result);
And running this code will print out: [ [1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6] ]
You can begin to see the power of monads here. They abstract away the complicated part of your code and simplify the problem at hand. Monads are a hard concept to understand, and I hope that I have simplified its understanding here. If there is any part not clear enough please let me know. In the next post I hope to take a more in depth look and monads with some interesting examples.
The monad laws and state monad in JavaScript
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html
In the previous post (please read the previous post before reading this post) we implemented three monads, the identity monad, maybe monad and array monad. However we did not get into the details of monads. We will do that in this post. Also we will implement the state monad. Here is an identity monad example.
var identityMonad = { mBind: function(mValue, mFunction) { return mFunction(mValue); }, mResult: function(value) { return value; } }; var result = doMonad(identityMonad, "a", function(scope) { return 2; }, "b", function(scope) { with (scope) { return a * 3; } }, function(scope) { with(scope) { return a + b; } } ); console.log(result);
First we define the identityMonad that we use when calling doMonad. Each computation in doMonad MUST return a monadic value. eg. the first computation returns 2 a monadic value. However what gets assigned to "a" is not the monadic value but the value. This distinction is important. However in the case of the identity monad both the monadic value and value are the same.
The mBind function takes a monadic value and a monadic function as its arguments. mBind then extracts the "value" out of the "monadic value" and calls the monadic function with the value. In this case no extraction is done because both are equal for the identity monad. The monadic function takes a "value" and returns a "monadic value".
In the next computation "a" is available as the value and not the monadic value. In the result computation both "a" and "b" are available and we return "value a + b". Since we return a value and not a monadic value in the final computation, the transformation from "value" to "monadic value" is done by the mResult function which takes a value and returns a monadic value. In this case it returns the argument itself because the value and monadic value are the same. So mResult is the identity function, and thats why it is called the identity monad.
We will look at a case where the "value" and "monadic value" are not the same. Using the array monad we will write a map function that doubles each element of an array.
var arrayMonad = { mBind: function(mValue, mFunc) { var accum = []; mValue.forEach(function(elem){ accum = accum.concat(mFunc(elem)); }); return accum; }, mResult: function(value) { return [value]; } }; var result = doMonad(arrayMonad, "i", function() { return [1, 2, 3]; }, function(scope) { with(scope) { return i * 2; } } );
Running the above code will print the result [ 2, 4, 6 ]. The first function in doMonad returns a monadic value which is an array. However the "value" in a monadic value of array type is the value of each element. It is mBinds job to extract each value, and call the monadic function for each value, and thats what it does exactly.
The result function returns i * 2 which is of type integer. However all monadic functions of a given monad MUST return monadic values of the same type. It is mResults job to convert the result type from integer to array. And that is what it does exactly.
The monad laws
To write our own monads, our monads must obay the monad laws.
- mBind(mResult(x), mFunction) is equal to mFunction(x).
- mBind(mValue, mResult) is equal to mValue.
- mBind(mBind(mValue, mFunction), mFunction2) is equal to mBind(mValue, function(x){return mbind(mFunction(x), mFunction2)}).
Now let us check to see if our array monad follows the monad laws.
var mBind = arrayMonad.mBind; var mResult = arrayMonad.mResult; var mValue = [1, 2, 3]; var x = 4; var mFunction = function(x) { return [x * 2]; }; var mFunction2 = function(x) { return [x * 3]; }; // Check first law console.log(mBind(mResult(x), mFunction)); // [ 8 ] console.log(mFunction(x)); // [ 8 ] //Check second Law console.log(mBind(mValue, mResult)); // [ 1, 2, 3 ] console.log(mValue); // [ 1, 2, 3 ] //Check third Law console.log(mBind(mBind(mValue, mFunction), mFunction2)); // [ 6, 12, 18 ] console.log(mBind(mValue, function(x){return mBind(mFunction(x), mFunction2)})); // [ 6, 12, 18 ]
The State Monad
So far we saw monadic values of types integer and array. But monadic values can also be functions! After all JavaScript supports first class functions, that can be treated as values. The state monad is just such a monad. The monadic value is a function. However it is important to differentiate between a monadic function and a monadic value of type function.
The monadic function in a state monad, just like all monadic functions takes a value and returns a monadic value. It so happens that this monadic value is a function.
The monadic value in a state monad is a function that takes a state, and returns a two element array, with a value and the new state respectively. The state can be of any type. it can be a an integer, string, array object or any other valid type.
In the next example we maintain an immutable stack array over a set of computations. We define two monadic functions "push" and "pop".
var stateMonad = { mBind: function(mValue, mFunc) { return function(state) { var compute = mValue(state); var value = compute[0]; var newState = compute[1]; return mFunc(value)(newState); }; }, mResult: function(value) { return function(state) { return [value, state]; }; } }; var push = function(value) { return function(state) { var newstate = [value]; return [undefined, newstate.concat(state)]; }; }; var pop = function() { return function(state) { var newstate = state.slice(1); return [state[0], newstate]; }; }; var result = doMonad(stateMonad, "a", function(scope) { return push(5); }, "b", function(scope) { with (scope) { return push(10); } }, "c", function(scope) { with (scope) { return push(20); } }, "d", function(scope) { with (scope) { return pop(); } }, function(scope) { with(scope) { return d; } } ); console.log(result([]));
Running the code above will print [ 20, [ 10, 5 ] ].
20 is the value of the last "pop" computation, and the second value is the final state of the stack.
First we will look at mResult. mResult is a monadic function that takes a value and returns a monadic value, which is a function that takes a state and returns an array with value and state.
mBind returns a monadic value which is a function. So the result of doMonad is a function which you must call with an initial value for the stack. Which is [] in our case. Remember mBind is called with a monadic value and a monadic function. It has to extract the value out of the monadic value and call the monadic function with the value. Which it does in the three lines inside the returned monadic function. Notice that mFunc is called with the extracted value, and since its return value is a monadic value of type function, the function is called immediately with the new state.
All the code in the last two posts are available as a monad library on github.
The Promise Monad in JavaScript
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html
If you find it difficult to understand whats going on below, read the following posts. Implementing Monads in JavaScript The monad laws and state monad in JavaScript.
We will go through an example of the promise monad in this post. The promise monad is available now in the monadjs library. The best way is too look at an example. We will write a nodejs command line program that will copy an input file into an output file asynchronously. We can run the program like this.
$ node copy.js infile outfile
The program has to do the following.
Check the command line for infile and verify it exists, if not print an error and halt computations. Check if outfile is given in the command line otherwise print error and halt. Read infile content into memory and halt if error. Write content to outfile or print error to console. Halting of program in case of error to be done without throwing errors. Computations (1) (3) and (4) are asynchronous. (2) is synchronous because we are only checking process.argv.
The monadic values of the promise monad are functions that take a continuation and promise to call the continuation either asynchronously or synchronously. The continuation is called with a value which is the result of the last computation. If the continuation is called with "null" the computations are halted.
All the computations have access to the results of the previous computations via the "scope" variable. The results are stored in the variable names you give. Here is the source code of copy.js
var monads = require("monadjs"); var fs = require("fs"); monads.doMonad(monads.promiseMonad, "infile", function(scope) { return function(continuation) { var fname = process.argv[2] || ""; fs.exists(fname, function (exists) { if (exists) { continuation(fname); } else { console.log("File does not exist: " + fname); continuation(null); } }); } }, "outfile", function(scope) { return function(continuation) { var fname = process.argv[3]; if (fname) { continuation(fname); } else { console.log("Output File Name is Required"); continuation(null); } } }, "contents", function(scope) { return function(continuation) { fs.readFile(scope.infile, function (err, data) { if (err) { console.log("Error reading File: " + scope.infile); continuation(null); } else { continuation(data); } }); } }, function(scope) { fs.writeFile(scope.outfile, scope.contents, function (err) { if (err) { console.log("Error writing File: " + scope.outfile); } }); } );
I don't think the promise monad obeys the monad laws. But it works. It works only for sequential asynchronous calls though. What is interesting is that it allows you to break the program structure into bite size pieces and call them sequentially. Notice also the promise monad is implemented purely functionally, and no timing loops used.
However you don't have to actually use the promise monad from this library. I have refactored and simplified everything in a simple promise library you can find here.
/* dopromise Promise Library for JavaScript Copyright (c) 2013 Santosh Rajan License - MIT - https://github.com/santoshrajan/dopromise/blob/master/LICENSE */ (function(exports){ var serial = function() { var args = arguments, scope = {} function iterator(i) { var func = args[i] if (args.length === i + 1) { func.call(scope) } else { func.call(scope, function() { iterator(i + 1) }) } } iterator(0); } var parallel = function() { var args = Array.prototype.slice.call(arguments), last = args.pop() counter = args.length scope = {} done = function() { --counter if (counter === 0) { last.call(scope) } } args.forEach(function(f) { f.call(scope, done) }) } var loop = function(f) { var scope = {} function iterator() { f.call(scope, iterator) } iterator() } exports.version = "0.0.4" exports.doPromise = serial // for backward compatability exports.serial = serial exports.parallel = parallel exports.loop = loop })(typeof exports === 'undefined'? this.dopromise={}: exports);
Functors
Consider the function below.
function plus1(value) { return value + 1 }
It is just a function that takes an integer and adds one to it. Similarly we could could have another function plus2. We will use these functions later.
function plus2(value) { return value + 2 }
And we could write a generalised function to use any of these functions as and when required.
function F(value, fn) { return fn(value) } F(1, plus1) ==>> 2
This function will work fine as long as the value passed is an integer. Try an array.
F([1, 2, 3], plus1) ==>> '1,2,31'
Ouch. We took an array of integers, added an integer and got back a string! Not only did it do the wrong thing, we ended up with a string having started with an array. In other words our program also trashed the structure of the input. We want F to do the "right thing". The right thing is to "maintain structure" through out the operation.
So what do we mean by "maintain structure"? Our function must "unwrap" the given array and get its elements. Then call the given function with every element. Then wrap the returned values in a new Array and return it. Fortunately JavaScript just has that function. Its called map.
[1, 2, 3].map(plus1) ==>> [2, 3, 4]
And map is a functor!
A functor is a function, given a value and a function, does the right thing.
To be more specific.
A functor is a function, given a value and a function, unwraps the values to get to its inner value(s), calls the given function with the inner value(s), wraps the returned values in a new structure, and returns the new structure.
Thing to note here is that depending on the "Type" of the value, the unwrapping may lead to a value or a set of values.
Also the returned structure need not be of the same type as the original value. In the case of map both the value and the returned value have the same structure (Array). The returned structure can be any type as long as you can get to the individual elements. So if you had a function that takes and Array and returns value of type Object with all the array indexes as keys, and corresponding values, that will also be a functor.
In the case of JavaScript, filter is a functor because it returns an Array, however forEach is not a functor because it returns undefined. ie. forEach does not maintain structure.
Functors come from category theory in mathematics, where functors are defined as "homomorphisms between categories". Let's draw some meaning out of those words.
homo = same morphisms = functions that maintain structure category = type According to the theory, function F is a functor when for two composable ordinary functions f and g
F(f . g) = F(f) . F(g)
where . indicates composition. ie. functors must preserve composition.
So given this equation we can prove wether a given function is indeed a functor or not.
Array Functor
We saw that map is a functor that acts on type Array. Let us prove that the JavaScript Array.map function is a functor.
function compose(f, g) { return function(x) {return f(g(x))} }
Composing functions is about calling a set of functions, by calling the next function, with results of the previous function. Note that our compose function above works from right to left. g is called first then f.
[1, 2, 3].map(compose(plus1, plus2)) ==>> [ 4, 5, 6 ] [1, 2, 3].map(plus2).map(plus1) ==>> [ 4, 5, 6 ]
Yes! map is indeed a functor.
Lets try some functors. You can write functors for values of any type, as long as you can unwrap the value and return a structure.
String Functor So can we write a functor for type string? Can you unwrap a string? Actually you can, if you think of a string as an array of chars. So it is really about how you look at the value. We also know that chars have char codes which are integers. So we run plus1 on every char charcode, wrap them back to a string and return it.
function stringFunctor(value, fn) { var chars = value.split("") return chars.map(function(char) { return String.fromCharCode(fn(char.charCodeAt(0))) }).join("") } stringFunctor("ABCD", plus1) ==>> "BCDE"
You can begin to see how awesome functors are. You can actually write a parser using the string functor as the basis.
Function Functor
In JavaScript functions are first class citizens. That means you can treat functions like any other value. So can we write a functor for value of type function? We should be able to! But how do we unwrap a function? You can unwrap a function by calling it and getting its return value. But we straight away run into a problem. To call the function we need its arguments. Remember that the functor only has the function that came in as the value. We can solve this by having the functor return a new function. This function is called with the arguments, and we will in turn call the value function with the argument, and call the original functors function with the value returned!
function functionFunctor(value, fn) { return function(initial) { return function() { return fn(value(initial)) } } } var init = functionFunctor(function(x) {return x * x}, plus1) var final = init(2) final() ==> 5
Our function functor really does nothing much, to say the least. But there a couple things of note here. Nothing happens until you call final. Every thing is in a state of suspended animation until you call final. The function functor forms the basis for more awesome functional stuff like maintaining state, continuation calling and even promises. You can write your own function functors to do these things!
MayBe Functor
function mayBe(value, fn) { return value === null || value === undefined ? value : fn(value) }
Yes, this is a valid functor.
mayBe(undefined, compose(plus1, plus2)) ==>> undefined mayBe(mayBe(undefined, plus2), plus1) ==>> undefined mayBe(1, compose(plus1, plus2)) ==>> 4 mayBe(mayBe(1, plus2), plus1) ==>> 4
So mayBe passes our functor test. There is no need for unrapping or wrapping here. It just returns nothing for nothing. Maybe is useful as a short circuiting function, which you can use as a substitute for code like
if (result === null) { return null } else { doSomething(result) }
Identity Function
function id(x) { return x }
The function above is known as the identity function. It is just a function that returns the value passed to it. It is called so, because it is the identity in composition of functions in mathematics.
We learned earlier that functors must preserve composition. However something I did not mention then, is that functors must also preserve identity. ie.
F(value, id) = value
Lets try this for map.
[1, 2, 3].map(id) ==>> [ 1, 2, 3 ]
Type Signature
The type signature of a function is the type of its argument and return value. So the type signature of our plus1 function is
f: int -> int
The type signature of the functor map depends on the type signature of the function argument. So if map is called with plus1 then its type signature is
map: [int] -> [int]
However the type signature of the given function need not be the same as above. We could have a function like
f: int -> string
in which the type signature of map would be
map: [int] -> [string]
The only restriction being that the type change does not affect the composability of the functor. So in general a functor's type signature can
F: A -> B
In other words map can take an array of integers and return an array of strings and would still be a functor.
Monads are a special case of Functors whos type signature is
M: A -> A
Monads
All the code for this post are available here.https://github.com/santoshrajan/monadjs
Consider the map functor from the last chapter. We could use map to iterate over two arrays adding each element of the first to the second.
var result = [1, 2].map(function(i) { return [3, 4].map(function(j) { return i + j }) }) console.log(result) ==>> [ [ 4, 5 ], [ 5, 6 ] ]
The type signature of the inner function is
f: int -> int
and the type signature of the inner map is
map: [int] -> [int]
The type signature of the outer function is
f: int -> [int]
and the type signature of the outer map is
map: [int] -> [[int]]
This is the right behaviour you would expect from a functor. But this is not what we want. We want the result to be flattened like below.
[ 4, 5, 5, 6 ]
Array Monad
For that to happen, the type signature of a functor should always be restricted to
F: [int] -> [int]
But functors do not place any such restriction. But monads do. The type signature of an array monad is
M: [T] -> [T]
where T is a given type. That is why map is a functor but not a monad. That is not all. We have to put some type restriction on the function passed it too. The function cannot return any type it likes. We can solve this problem by restricting the function to return the Array type. So the function's type signature is restricted to
f: T -> [T]
This function is known as lift, Because it lifts the type to the required type. This is also known as the monadic function. And the original value given to the monad is known as the monadic value. Here is the arrayMonad.
function arrayMonad(mv, mf) { var result = [] mv.forEach(function(v) { result = result.concat(mf(v)) }) return result }
Now we can use the array monad to do our first calculation.
console.log(arrayMonad([1,2,3], function(i) { return [i + 1] })) ==>> [ 2, 3, 4 ]
Notice that our monadic function wraps the result in an array [i + 1]. Now let us try it with the two dimensional problem we started with.
var result = arrayMonad([1, 2], function(i) { return arrayMonad([3, 4], function(j) { return [i + j] }) }) console.log(result) ==>> [ 4, 5, 5, 6 ]
Now we begin to see the power of monads over functors.
We can write a generic two dimensional iterator for arrays which will take two arrays and a callback, and call it for each element of both arrays.
function forEach2d(array1, array2, callback) { return arrayMonad(array1, function(i) { return arrayMonad(array2, function(j) { return [callback(i,j)] }) }) }
And we can try this function
forEach2d([1, 2], [3,4], function(i, j) { return i + j }) ==>> [ 4, 5, 5, 6 ] Notice that the callback function is just a regular function, so we had to lift its return value [callback(i,j)] to an array. However all monads define a function to do the lifting. Its called mResult. We will add mResult to the arrayMonad function object. Also the concat function is inneficient as it creates a new array everytime. We will apply array push instead. Here is the final code for the array monad.
function arrayMonad(mv, mf) { var result = [] mv.forEach(function(v) { Array.prototype.push.apply(result, mf(v)) }) return result } arrayMonad.mResult = function(v) { return [v] }
and rewrite forEach2d
function forEach2d(array1, array2, callback) { return arrayMonad(array1, function(i) { return arrayMonad(array2, function(j) { return arrayMonad.mResult(callback(i,j)) }) }) }
As an exersice I will leave it to the reader to implement forEach3d.
The arrayMonad is a monadic function and is otherwise known as bind or mbind. For a function to be a monad it must define atleast the functions mbind and mresult.
Identity Monad
The identity monad is the simplest of all monads, named so because it's mresult is the identity function.
function indentityMonad(mv, mf) { return mf(mv) } identityMonad.mResult = function(v) { return v }
It is not a very useful monad. But it is a valid monad.
Maybe Monad
The maybe Monad is similar to the identity monad, except that it will not call the monadic function for values null or undefined. In fact it boild down to the same mayBe functor we saw in the last chapter.
function mayBeMonad(mv, mf) { return mv === null || mv === undefined || mv === false ? null : mf(mv) } mayBeMonad.mResult = function(v) { return v }
The Monad Laws
- The First Monad Law
M(mResult(x), mf) = mf(x)
Which means whatever mResult does to x to turn x into a monadic value, M will unwrap that monadic value before applying it to monadic function mf. Let us test this on our array monad.
var x = 4; function mf(x) { return [x * 2] } arrayMonad(arrayMonad.mResult(x), mf) ==>> [ 8 ] mf(x) ==>> [ 8 ]
- The Second Monad Law
M(mv, mResult) = mv
Which means whatever mBind does to extract mv's value, mResult will undo that and turn the value back to a monadic value. This ensures that mResult is a monadic function. Let us test it. This is equivalent to the preserve identity case of the functor.
arrayMonad([1, 2, 3], arrayMonad.mResult) ==>> [ 1, 2, 3 ]
- The Third Monad Law
M(M(mv, mf), mg)) = M(mv, function(x){return M(mf(x), mg)})
What this is saying is that it doesn't matter if you apply mf to mv and then to mg, or apply mv to a monadic function that is a composition of mf and mg. Let us test this.
function mg(x) { return [x * 3] } arrayMonad(arrayMonad([1, 2, 3], mf), mg) ==>> [ 6, 12, 18 ] arrayMonad([1, 2, 3], function(x) { return arrayMonad(mf(x), mg) }) ==>> [ 6, 12, 18 ]
doMonad
We know that a monadic function takes a value and returns a monadic value. A monad takes a monadic value and a monadic function and returns a monadic value. What if a monadic function calls a monad with a monadic value and itself and returns the result? That would be a valid monadic function because it returns a monadic value.
The function doMonad does exactly this. It takes a monad and an array of monadic values and a callback as its arguments. It defines a monadic function that recursively calls the monad with each monadic value and itself. It breaks out of the loop when there are no more monadic values left. It returns the callback called with each unwrapped value of the monadic value. The callback cb is curried in a closure called wrap and is visible to mf. The curry function is from the chapter on currying.
function curry(fn, numArgs) { numArgs = numArgs || fn.length return function f(saved_args) { return function() { var args = saved_args.concat(Array.prototype.slice.call(arguments)) return args.length === numArgs ? fn.apply(null, args) : f(args) } }([]) } function doMonad(monad, values, cb) { function wrap(curriedCb, index) { return function mf(v) { return (index === values.length - 1) ? monad.mResult(curriedCb(v)) : monad(values[index + 1], wrap(curriedCb(v), index + 1)) } } return monad(values[0], wrap(curry(cb), 0)) } doMonad(arrayMonad, [[1, 2], [3, 4]], function(x, y) { return x + y }) //==>> [ 4, 5, 5, 6 ]
We don't need the forEach2d function we wrote earlier! And the best is yet to come!
Array comprehension using doMonad We can write a generic array comprehension function called FOR which takes a set of arrays and a callback in its arguments.
function FOR() { var args = [].slice.call(arguments) callback = args.pop() return doMonad(arrayMonad, args, callback) } FOR([1, 2], [3, 4], function(x, y) { return x + y }) //==>> [ 4, 5, 5, 6 ] FOR([1, 2], [3, 4], [5, 6], function(x, y, z) { return x + y + z }) //==>> [ 9, 10, 10, 11, 10, 11, 11, 12 ]
How awesome is that!
State Monad
In the last chapter on functors we saw function fucntor that takes a value of type function. Similarly monadic values can also be functions. However it is important to distinguish between monadic functions and monadic values that are functions. The type signature of a monadic function is
mf: v -> mv
ie. takes a value and lifts it to a monadic value. Note that the monadic value itself is a function. So mf will return a function mv.
The type signature of a monadic value which is a function depends on whatever that function is doing as the case may be. In the case of the state monad the type signature of its monadic value is
mv: state -> [value, new state]
The monadic value function takes a state and returns an array containing a value and a new state. The state can be of any type array, string, integer, anything.
The stateMonad takes a monadic value and a monadic function and returns a function to which we have to pass the initial state. The initial state is passed mv which returns a value. mf is then called with this value and mf returns a monadic value which is a function. We must call this function with the newstate. Phew! function stateMonad(mv, mf) { return function(state) { var compute = mv(state) var value = compute[0] var newState = compute[1] return mf(value)(newState) } } And mResult for the state monad is
stateMonad.mResult = function(value) { return function(state) { return [value, state]; } }
Parser Monad
A parser is function that takes a string matches the string based on some criteria and returns the matched part and the remainder. Lets write the type signature of the function.
parser: string -> [match, newstring]
This looks like the monadic value of the state monad, with state restricted to the type string. But thats not all, the parser will return null if the string did not match the criteria. So lets write the parser monad to reflect the changes.
function parserMonad(mv, mf) { return function(str) { var compute = mv(str) if (compute === null) { return null } else { return mf(compute[0])(compute[1]) } } } parserMonad.mResult = function(value) { return function(str) { return [value, str]; } }
As we saw earlier Monads require you to define atleast two functions, mBind (the monad function itself) and mResult. But that is not all. Optionally you can define two more functions, mZero and mPlus.
mZero is the definition of "Nothing" for the monad. eg. for the arrayMonad, mZero would be []. In the case of the parser monad mZero is defined as follows. (mZero must have the same type signature of the monadic value).
parserMonad.mZero = function(str) { return null }
mPlus is a function that takes monadic values as its arguments, and ignores the mZero's among them. How the accepted values are handled depends on the individual monad. For the parser monad, mZero will take a set of parsers (parser monad's monadic values) and will return the value returned by the first parser to return a non mZero (null) value.
parserMonad.mPlus = function() { var parsers = Array.prototype.slice.call(arguments) return function(str) { var result, i for (i = 0; i < parsers.length; ++i) { result = parsers[i](str) if (result !== null) { break; } } return result } }
Continuation Monad
The continuation monad takes a bit to understand. In the chapter on function composition we saw that the composition of two functions f and g is given by
(f . g) = f(g(x))
f is known as the continuation of g.
We also know that we can wrap values in a function by creating a closure. In the example below the inner function has a value wrapped in its closure.
function(value) { return function() { // value can be accessed here } }
The monadic value of a continuation monad, is a function that takes a continuation function and calls the continuation with its wrapped value. This is just like the inner function above called with a continuation function and we we can write it as
function(continuation) { return continuation(value) }
The mResult function of monad takes a value and lifts it to a monadic value. So we can write the mResult function for the continuation monad.
var mResult = function(value) { return function(continuation) { return continuation(value) } }
So mResult is a function that takes a value, returns a monadic value which you call with a continuation.
The continuation monad itself or mBind is more complicated.
var continuationMonad = function(mv, mf) { return function(continuation) { // we will add to here next } }
First it will return a function you need to call with a continuation. Thats easy. But how can it unwrap the value inside mv? mv accepts a continuation, but calling mv with the continuation will not do. We need to unwrap the value in mv and call mf first. So we need to trick mv into giving us the value first by calling it with our own continuation thus.
mv(function(value) { // gotcha! the value })
We can add this function to the code above.
var continuationMonad = function(mv, mf) { return function(continuation) { return mv(function(value) { // gotcha! the value }) } }
Now all we have to do is call mf with the value. We know a monadic function takes a value and returns a monadic value. So we call the returned monadic value from mf with the continuation. Phew! Here is the complete code for the continuation monad.
var continuationMonad = function(mv, mf) { return function(continuation) { return mv(function(value) { return mf(value)(continuation) }) } } continuationMonad.mResult = function(value) { return function(continuation) { return continuation(value) } }
JavaScript MD5
/* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as defined in RFC 1321. * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for more info. */ /* * Configurable variables. You may need to tweak these to be compatible with * the server-side, but the defaults work in most cases. */ var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ /* * These are the functions you'll usually want to call * They take string arguments and return either hex or base-64 encoded strings */ function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); } function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); } function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); } function hex_hmac_md5(k, d) { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); } function b64_hmac_md5(k, d) { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); } function any_hmac_md5(k, d, e) { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); } /* * Perform a simple self-test to see if the VM is working */ function md5_vm_test() { return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72"; } /* * Calculate the MD5 of a raw string */ function rstr_md5(s) { return binl2rstr(binl_md5(rstr2binl(s), s.length * 8)); } /* * Calculate the HMAC-MD5, of a key and some data (raw strings) */ function rstr_hmac_md5(key, data) { var bkey = rstr2binl(key); if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8); var ipad = Array(16), opad = Array(16); for(var i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); return binl2rstr(binl_md5(opad.concat(hash), 512 + 128)); } /* * Convert a raw string to a hex string */ function rstr2hex(input) { try { hexcase } catch(e) { hexcase=0; } var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; var output = ""; var x; for(var i = 0; i < input.length; i++) { x = input.charCodeAt(i); output += hex_tab.charAt((x >>> 4) & 0x0F) + hex_tab.charAt( x & 0x0F); } return output; } /* * Convert a raw string to a base-64 string */ function rstr2b64(input) { try { b64pad } catch(e) { b64pad=; } var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var output = ""; var len = input.length; for(var i = 0; i < len; i += 3) { var triplet = (input.charCodeAt(i) << 16) | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) | (i + 2 < len ? input.charCodeAt(i+2) : 0); for(var j = 0; j < 4; j++) { if(i * 8 + j * 6 > input.length * 8) output += b64pad; else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); } } return output; } /* * Convert a raw string to an arbitrary string encoding */ function rstr2any(input, encoding) { var divisor = encoding.length; var i, j, q, x, quotient; /* Convert to an array of 16-bit big-endian values, forming the dividend */ var dividend = Array(Math.ceil(input.length / 2)); for(i = 0; i < dividend.length; i++) { dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); } /* * Repeatedly perform a long division. The binary array forms the dividend, * the length of the encoding is the divisor. Once computed, the quotient * forms the dividend for the next step. All remainders are stored for later * use. */ var full_length = Math.ceil(input.length * 8 / (Math.log(encoding.length) / Math.log(2))); var remainders = Array(full_length); for(j = 0; j < full_length; j++) { quotient = Array(); x = 0; for(i = 0; i < dividend.length; i++) { x = (x << 16) + dividend[i]; q = Math.floor(x / divisor); x -= q * divisor; if(quotient.length > 0 || q > 0) quotient[quotient.length] = q; } remainders[j] = x; dividend = quotient; } /* Convert the remainders to the output string */ var output = ""; for(i = remainders.length - 1; i >= 0; i--) output += encoding.charAt(remainders[i]); return output; } /* * Encode a string as utf-8. * For efficiency, this assumes the input is valid utf-16. */ function str2rstr_utf8(input) { var output = ""; var i = -1; var x, y; while(++i < input.length) { /* Decode utf-16 surrogate pairs */ x = input.charCodeAt(i); y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) { x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); i++; } /* Encode output as utf-8 */ if(x <= 0x7F) output += String.fromCharCode(x); else if(x <= 0x7FF) output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), 0x80 | ( x & 0x3F)); else if(x <= 0xFFFF) output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), 0x80 | ((x >>> 6 ) & 0x3F), 0x80 | ( x & 0x3F)); else if(x <= 0x1FFFFF) output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), 0x80 | ((x >>> 12) & 0x3F), 0x80 | ((x >>> 6 ) & 0x3F), 0x80 | ( x & 0x3F)); } return output; } /* * Encode a string as utf-16 */ function str2rstr_utf16le(input) { var output = ""; for(var i = 0; i < input.length; i++) output += String.fromCharCode( input.charCodeAt(i) & 0xFF, (input.charCodeAt(i) >>> 8) & 0xFF); return output; } function str2rstr_utf16be(input) { var output = ""; for(var i = 0; i < input.length; i++) output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, input.charCodeAt(i) & 0xFF); return output; } /* * Convert a raw string to an array of little-endian words * Characters >255 have their high-byte silently ignored. */ function rstr2binl(input) { var output = Array(input.length >> 2); for(var i = 0; i < output.length; i++) output[i] = 0; for(var i = 0; i < input.length * 8; i += 8) output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32); return output; } /* * Convert an array of little-endian words to a string */ function binl2rstr(input) { var output = ""; for(var i = 0; i < input.length * 32; i += 8) output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF); return output; } /* * Calculate the MD5 of an array of little-endian words, and a bit length. */ function binl_md5(x, len) { /* append padding */ x[len >> 5] |= 0x80 << ((len) % 32); x[(((len + 64) >>> 9) << 4) + 14] = len; var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; for(var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); c = md5_ff(c, d, a, b, x[i+10], 17, -42063); b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); } return Array(a, b, c, d); } /* * These functions implement the four basic operations the algorithm uses. */ function md5_cmn(q, a, b, x, s, t) { return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); } function md5_ff(a, b, c, d, x, s, t) { return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); } function md5_gg(a, b, c, d, x, s, t) { return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); } function md5_hh(a, b, c, d, x, s, t) { return md5_cmn(b ^ c ^ d, a, b, x, s, t); } function md5_ii(a, b, c, d, x, s, t) { return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ function safe_add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /* * Bitwise rotate a 32-bit number to the left. */ function bit_rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); }
Источники: