Created
August 29, 2017 11:32
-
-
Save davo/59c7d9e908fb8ab9c5014c2e246814c1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Domain Specific Languages in Coffee Script | |
# ## Title | |
# ## What's Domain Specific Languages | |
# ## Great Examples in Coffee Script: | |
#### Socket.io | |
#### ZappaJS | |
#### Orpheus | |
class User extends Orpheus | |
constructor: -> | |
@has 'book' | |
@str 'about_me' | |
@num 'points' | |
@set 'likes' | |
@zset 'ranking' | |
@map @str 'fb_id' | |
@str 'fb_secret' | |
user('rada') | |
.name.hset('radagaisus') | |
.points.hincrby(5) | |
.points_by_time.zincrby(5, new Date().getTime()) | |
.books.sadd('dune') | |
# ## Tips & Tricks for writing DSLs in Coffee Script | |
#### Object.defineProperties | |
# Let's start with cool simple tricks. `Object.defineProperties` | |
# and `__defineGetter__` are cool properties that aren't supported | |
# in a cross browser fashion, but they give a cool sense of what | |
# we are talking about here, and if you write NodeJS you can, and | |
# should, use them extensively. | |
# So `__defineGetter__` is an obsolete function that was supported | |
# in FF and Chrome. It can set properties on the object prototype | |
# and when those properties are referenced it calls a function. | |
# `__defineGetter__` is responsible for the cool socket.io DSL: | |
socket.broadcast.emit 'joined', | |
user: user.to_json() | |
# Here broadcast is used as a flag to note that this message | |
# should be send to all the users who are listening. It's implemented | |
# like this: | |
Client.prototype.__defineGetter__('broadcast', function () { | |
this.flags.broadcast = true; | |
}); | |
# So `__defineGetter__`, and the more complicated `Object.defineProperty` | |
# gives us a really cool way for adding flags to our libraries. We will | |
# abstract this later in a cool fashion. | |
#### Writing Dynamic Functions | |
# There are a few cases where you want to write several functions in | |
# one go. Those functions might do very similar things so you think | |
# to yourself, because you like to abstract things, because you are | |
# a programmer, 'Hey, I can abstract this!'. Now you have two problems. | |
# It's pretty simple to create these dynamic functions in JS and CS, | |
# the only pitfall is that you have to make sure you closure all your | |
# variables correctly. | |
# Remember this nasty problem? | |
for (var i = 0; i < 4; i++) { | |
setTimeout(function() { | |
console.log(i); | |
}, i*100); | |
} | |
# By the time the anonymous function inside setTimeout gets called, | |
# `i` is equal to 4, so all the 4 functions prints 4! | |
# So to fix this we do: | |
for (var i = 0; i < 4; i++) { | |
setTimeout( | |
(function(i) { | |
return function() { | |
console.log(i); | |
} | |
})(i) | |
, i*100); | |
} | |
# Which looks really ugly, but it creates a closure - another anonymous function | |
# that covers up i as a local variable and makes sure it's protected from the loop. | |
# In CoffeeScript it's way nicer: | |
for i in [0...4] | |
do (i) -> | |
setTimeout -> | |
console.log i | |
, i * 100 | |
# If we'll have time we'll add later a helper that flips the setTimeout arguments | |
# to get this: | |
flip = (fn) -> | |
return -> | |
fn.apply(this, arguments.slice(0).reverse()) | |
timeout = flip setTimeout | |
for i in [0...4] | |
do (i) -> | |
timeout i * 100, -> | |
console.log i | |
# Which is as clean as it gets. | |
# So we got that covered. Not let's actually write metaprogramming functions. | |
# We'll start with a simple and easy wrapper that lets us use the functions | |
# `error`, `warn`, `debug`, `log` and `info` even when we don't have the | |
# console available. | |
# Let's start by defining a global property that checks is we are in dev mode | |
# or in production: | |
window._debug = | |
live : !! document.location.host.match 'mysite.com' | |
# Sets the debug level to 5 on development to show | |
# all messages and to 0 in production to show only | |
# errors. Tweak these values in the console if you | |
# want to see more / less errors. | |
if debug.live | |
then debug.level = 0 | |
else debug.level = 5 | |
# An easy way to see all the logs and filter them | |
window.logs = [] | |
# Adds an `error()`, `warn`, `info()` and `log()` functions | |
# to the window object. These functions are similar to the | |
# console functions available in sane browsers. They: | |
# | |
# - Delegate displaying the messages to the console, | |
# if the console is available and the log level is | |
# high enough. | |
# - Pushes the log messages as {msg: 'string', data: {...}} | |
# to window.logs for better inspection and filtering. | |
# | |
for msg_type, log_level in ['error', 'warn', 'info', 'log'] | |
do (msg_type, log_level) => | |
@[msg_type] = (msg = '', data) -> | |
# Log to Console | |
if _debug.live and _debug.level >= log_level | |
if not $.browser.msie and console? | |
console[msg_type].apply console, Array::slice.call arguments | |
# Add to our logs | |
window.logs.push | |
msg : msg | |
data: data | |
# Awesome. Now, a way more complicated example. We showed Orpheus before. | |
# What it does is give you a syntax similar to ActiveRecord for your redis | |
# objects: | |
class User extends Orpheus | |
constructor: -> | |
@has 'book' | |
@str 'about_me' | |
@num 'points' | |
@set 'likes' | |
@zset 'ranking' | |
@map @str 'fb_id' | |
@str 'fb_secret' | |
# And it gives you a nice way to query it with chaining: | |
user('Almog') | |
.points.hincrby(5) | |
.ranking.zincrby(5, new Date().getTime()) | |
.books.sadd('dune') | |
# How it does this, basically, is that is has a large list of all the redis | |
# commands: | |
str: [ | |
'hdel', | |
'hexists', | |
'hget', | |
'hsetnx', | |
'hset' | |
] | |
# And what it does is it goes through all the model properties, | |
# and based on each property type it creates the relevant functions | |
# on the model. So, a way simpler implementation of this can look like: | |
for property, info of @model | |
# Creates the property on the object | |
@[key] = {} | |
# Creates the property commands | |
for f in commands[info.type] | |
@[key][f] = (args...) => | |
@_commands.push args | |
# BackboneJS and most other MVCs do the same thing with the events object | |
class View extends Module | |
@include Events | |
events: {} | |
# The constructor builds the access to the parameters received, | |
# passes them to initialize and delegates the events | |
constructor: (o) -> | |
# o - el, template, everything you need to access with 'this' | |
_.extend this, o | |
@initialize(o) | |
@delegate_events() | |
_delegateEventSplitter: /^(\S+)\s*(.*)$/ | |
# Add event listeners from the @events hash | |
delegate_events: -> | |
# Match the event and the element, separated by space | |
events = for k,v of @events | |
k.match(/^(\S+)\s*(.*)$/)[1..2].concat v | |
for [event,element,fn] in events | |
do (event, element, fn) => | |
_fn = (e) => | |
@[fn](e, @$(e.currentTarget)) | |
# $('.view').on 'click', '.view-header', func | |
if element | |
then @$(@el).on event, element, _fn | |
else @$(@el).on event, _fn | |
#### Using the Context | |
# So context switching is kind of a more complicated issue, but bear with | |
# me here. Context switching, or 'Using the Context', is a simple method | |
# that gives functions a different context to operate on. Context means all | |
# all local `this` variables the function has access to. A good example of | |
# this ZappaJS: | |
require('zappajs') -> | |
@get '/': -> | |
@render 'index', | |
title: 'Zappa!' | |
stylesheet: '/index.css' | |
@get '/logout', -> | |
@request.logOut() | |
@response.redirect 'back' | |
@post '/:name/data.json': -> | |
@send | |
email: "#{@params.name}@example.com" | |
# So the cool thing that Zappa does here is that you get all | |
# this special variables like `@params` and `@render` available | |
# to you through the local this context. What's even more cool | |
# is that code highlighting for `@functions` is differnet and | |
# that's nice. | |
# You don't need to receive this variables as arguments or | |
# anything, they are just available in the context. The | |
# trick to accomplish this is very simple. | |
hello = (fn) -> | |
fn.apply {name: 'Almog'}, ['Almog'] | |
hello = -> | |
console.log "Hello #{@name}" | |
# So `.apply` is a really cool tool. It's a function that's | |
# sits on `Function` prototype. It calls a that `Function` | |
# with the first argument to `.apply` as the context and the | |
# second argument as the arguments. So we could also do: | |
hello: = (name) -> | |
console.log "Hello #{name}" | |
# So what does zappa do is create a giant list of context | |
# variables: | |
ctx = | |
app : app | |
settings: app.settings | |
request : req | |
query : req.query | |
params : req.params | |
body : req.body | |
session : req.session | |
response: res | |
next : next | |
send : -> res.send.apply res, arguments | |
json : -> res.json.apply res, arguments | |
# And then call your function with them: | |
fn.apply(ctx, []) | |
#### Referencing Object Literals with the Fat Arrow | |
# This is a hack, and it should be used with caution. This hack | |
# allows us to write object literals and reference them with @ | |
# or this, without problems: | |
# A very ugky hack to access object literals with the fat arrow. | |
# So the important line is `_this = obj` where obj is the name | |
# of the object. And then you work on the object regularly by | |
# extending it and adding functions to it. This means we can | |
# reference the object literal with the this shorthand, and we | |
# can also *bind* functions to it, which is great. | |
_this = obj = {} | |
$.extend obj, | |
hello: => @stuff() | |
# And it compiles to: | |
var obj, | |
_this = this; | |
_this = obj; | |
$.extend(obj, { | |
hello: function() { | |
return _this.stuff() | |
} | |
}); | |
#### Mixins and Modules | |
#### Publish / Subscribe | |
#### Hooks | |
#### Building Fixtures | |
#### Working with Arguments | |
# A lot of libraries offer functions that can receive a variadic number | |
# of arguments and behave differently based on the number of arguments, | |
# the type of the arguments and even the values of the arguments. Just | |
# think about jQuery's `$.Ajax()` or `$.fn.animate()` that have tons of | |
# different options and function signatures. You can call animate like | |
# this: | |
# `$.fn..animate( properties [, duration] [, easing] [, complete] )` | |
$('#text-box').animate left: '500px', 1000, 'swing', -> | |
alert "Animation Completed!" | |
# Or like this: | |
# `$.fn..animate( properties, options )` | |
$('#text-box').animate | |
left: '500px' | |
top : '100px' | |
, | |
easing : 'swing' | |
duration: 1000 | |
complete: -> alert "Animation Completed!" | |
# So there are a lot of ways we want to call functions. | |
# Sometimes we need to support ugly versions to be backward | |
# compatible. Sometimes we have functions that tries to do | |
# convention over configuration and gives a lot of default | |
# values. | |
# Let's go over a few things that help us deal with this when | |
# we write our own libraries. | |
# In JavaScript, there are a lot of weird special variables | |
# for dealing with functions: `arguments` probably is the most | |
# well known. | |
# `arguments` is a special local property of `Function` that | |
# is kinda like an array (it has a `.length` propery) but | |
# kinda not like an array (you can't slice and dice it). | |
# You got a lot of sometimes obsolete or deprecated or not | |
# cross-browser supported properties of `arguments`: | |
# Say we call this function with `fn(1,2,3)` | |
fn = -> | |
# [1,2,3] | |
log arguments | |
# this function, as a string | |
log arguments.callee | |
# The function that called this function | |
log arguments.callee.caller | |
# The Arguments length: 3 | |
log arguments.length | |
# Proper way of converting arguments to an array: | |
Array::slice.call arguments | |
# In CoffeeScript we got a few additions to our arsenal. | |
# First, we can use default values: | |
say = (text = "Hello, World") -> text | |
say() # 'Hello, World' | |
say "Hi" # 'Hi' | |
# Note that Coffee uses `text == null` to set the default: | |
say = function(text) { | |
if (text == null) { | |
text = "Hello, World"; | |
} | |
return console.log(text); | |
}; | |
# By the way, you can add a lot of funky stuff as the default value: | |
Requests.Monitor = (@request = $.ajax('/user.json')) -> | |
@request.done (data) -> | |
log "The Server Responded with", data | |
# This, ugly and highly discouraged code, compiles to: | |
Requests.Monitor = function(request) { | |
this.request = request != null ? request : $.ajax('/user.json'); | |
return this.request.done(function(data) { | |
return log("The Server Responded with", data); | |
}); | |
}; | |
# Okay. So another cool thing with Coffee is Splats. Splats group together | |
# a number of arguments as an array. | |
# Sums a variadic list of numbers. `.reduce()` only available | |
# in ES5 so don't use it in the browser without shims! | |
sum = (numbers...) -> numbers.reduce (s, n) -> s + n | |
# So we can call sum with any number of arguments and it will work: | |
sum 1, 2, 3, 4 # 10 | |
sum 1, 2 # 3 | |
# Here's another way more complicated example: | |
# AsyncJS is a great library, kinda like UnderscoreJS | |
# but for hadnling asynchronous control flow. This | |
# comes really handy for coordinating a lot of requests | |
# to the server, animating stuff, and server side things. | |
# | |
# `series` is a function that can take a variadic number | |
# of functions as arguments. Each of those functions is | |
# expected to call the first argument they receive - the | |
# callback - when they are done executing. | |
# So, for example, to issue a series of animation functions: | |
move_left = (cb) -> | |
$('.stuff').animate left: 500, 1000, cb | |
move_down = (cb) -> | |
$('.stuff').animate top: 500, 2000, cb | |
annoy = -> alert "Done!" | |
series move_left, move_down, annoy | |
# Now, for the implementation of series: | |
series: (tasks...) -> | |
completed = 0 | |
iterate = -> | |
tasks[completed] -> | |
completed++ | |
if completed isnt tasks.length | |
iterate() | |
iterate() | |
# Coffee Script does a lot of things to create correct splats. Let's | |
# break it down: | |
log = (msg='ping', args...) -> | |
if console and console.log | |
if args | |
then console.log(msg, args) | |
else console.log(msg) | |
# So log is a very simple way of logging stuff even in dumb browsers | |
# that don't have the `console` object available. Let's see the JS: | |
# Coffee puts all the variable declarations at the top of the file. | |
var log, | |
# it references the Array slice for breaking the arguments down | |
__slice = [].slice; | |
log = function() { | |
var args, msg; | |
# msg is the first argument | |
msg = arguments[0], | |
# If the arguments.length is bigger than 2 then args | |
# is all the arguments after the first one, otherwise | |
# it's just an empty array. | |
args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; | |
# Sets the default message value | |
if (msg == null) { | |
msg = 'ping'; | |
} | |
# Actual `log` logic! | |
if (console && console.log) { | |
if (args) { | |
return console.log(msg, args); | |
} else { | |
return console.log(msg); | |
} | |
} | |
}; | |
# Another cool but more advanced thing Coffee does is the opposite | |
# of Splatting, breaking an array down to an arguments list: | |
log = (msg='ping', args...) -> | |
if console and console.log | |
console.log msg, args... | |
# So `console.log msg, args...` compiles to: | |
return console.log.apply(console, [msg].concat(__slice.call(args))); | |
# We'll cover `apply` and all the funky stuff that's going on here later. | |
# Another cool and unknown gem in JS is that there's a length property to | |
# functions. So, inside functions, `arguments.length` gives you the actual | |
# number of arguments this function received. But _outside_ functions you | |
# can get the number of arguments this function _expects_ with `fn.length`. | |
# Which is really cool. Here's a real world example for that detects | |
# async behavior based on `fn.length`. | |
# There's a nice idiom in a lot of JavaScript test frameworks | |
# like qUnit, Jasmine and Mocha. If your test runs asynchronously - | |
# uses timeouts, ajax, animations - you do something like: | |
describe 'Animation Callback', -> | |
it 'gets called', (done) -> | |
$('.hello').animate top: 1000, 2000, -> | |
done() | |
# So how does this frameworks know when test functions are async or sync? | |
# Here's a snippet from Mocha's source code: | |
function Runnable(title, fn) { | |
this.title = title; | |
this.fn = fn; | |
this.async = fn && fn.length; | |
this.sync = ! this.async; | |
this._timeout = 2000; | |
this._slow = 75; | |
this.timedOut = false; | |
} | |
# Here `fn` is the test function. If `fn` expects an argument | |
# (done, in our example), then we note it's an async test. | |
# Pretty simple, right? Ruby's Capybara breaks a lot of sweat | |
# to get this behavior. | |
# #### Few Rules of Thumb for writing DSL functions: | |
# - Only use default values in simple functions, as the last arguments | |
coffee.make = (table, suger = 1, type = "hafooch") -> | |
coffee.make 10 | |
coffee.make 10, 2, 'latte' | |
# This is simple and logical. We always have to add the table mumber | |
# of the people who ordered the coffee. In larger degree suger is usually | |
# 1 and type is usually Hafooch. | |
# - Use Options Object for more complicated functions | |
# Objects give us a much more readable and versatile version of | |
# calling functions. They are also more verbose, as they are | |
# basically like named arguments. | |
# There's an idiom for adding defaults to objects. Just like when you | |
# write jQuery plugins, you can use `_.extend` or `$._extend` or your | |
# own extend to add them. | |
coffee.make = (table, o) -> | |
defaults = | |
sugar: 1 | |
type : 'hafooch' | |
defaults = _.extend defaults, o | |
coffee.make = (table, o) -> | |
defaults = | |
sugar: 1 | |
type : 'hafooch' | |
defaults = $.extend defaults, o | |
coffee.make = (table, o) -> | |
defaults = | |
sugar: 1 | |
type : 'hafooch' | |
defaults = defaults[k] = v for k,v of o | |
# We can write a nice helper for adding the defaults idiom: | |
defaults = (o, defaults) -> | |
@options = defaults[k] = v for k,v of o | |
# And now you can make coffee with: | |
coffee.make = (table, o) -> | |
# Create defaults | |
defaults o, | |
sugar: 1 | |
type : 'hafooch' | |
# Use them | |
if @options.sugar > 1 | |
@suger(@options.sugar) | |
# - Use functions like blocks in Ruby, add the end | |
# Don't repeat setTimeout's mistage, always request | |
# functions as the last argument for you function. | |
coffee.make = (table, o, cb) -> | |
# Create defaults | |
defaults o, | |
sugar: 1 | |
type : 'hafooch' | |
# Internal make coffee function | |
@create(@options) | |
# Serve the coffee | |
@serve(table) | |
# Call the passed callback on complete | |
fn(table,o,fn) | |
# Which is nice: | |
coffee.make 10, sugar: 2, -> | |
log "Coffee Served" | |
# Use Objects for Multiple Functions | |
# If you got a few hooks your function can use add them inside | |
# the options object, otherwise it's too confusing: | |
coffee.make | |
table : 5 | |
sugar : 1 | |
type : 'mocha' | |
before_serve : -> | |
log "Coffee about to be Served" | |
complete : -> | |
log "Coffee Served" | |
# ## Return Values | |
#### TODO: Implicit Return Values | |
#### Return Valuable things or nothing | |
#### Chaining | |
# Chaining is an awesome and simple pattern. You just add `return this` | |
# at the end of your library's functions so they can be called one | |
# after another. In the words of Martin Fowler: | |
# > Make modifier methods return the host object, so that multiple | |
# > modifiers can be invoked in a single expression: | |
# So let's create a simple chained function using `__defineGetter__`. | |
# The function, `$.fn.tap()` is a jQuery plugin that simply logs | |
# the current elements the operation is working on: | |
$('#hello').tap.show() | |
# So we can just drop in `.tap` into our code and continue as usual | |
# whenever we want to debug and make sure we caught the correct | |
# set of elements: | |
jQuery.fn.__defineGetter__ 'tap', -> | |
console.log "Working with Elements: ", this | |
return this | |
# Woho! | |
# Another example, from OrpheusJS: | |
user('rada') | |
.name.hset('radagaisus') | |
.points.hincrby(5) | |
.points_by_time.zincrby(5, new Date().getTime()) | |
.books.sadd('dune') | |
## Writing DSLs | |
# At the start of this lecture we talked a bit about adding flags | |
# to our libraries using `__defineGetter__`. defineGetter is an | |
# ugly function, and most of the techniques we talked about, while | |
# I tried to explain them in a simple fashion, gives us a really | |
# complicated code. So while our library has a very cool and slick | |
# interface it will look ugly on the inside. An easy way to combat | |
# that is to write it in an even more abstract fashion using a DSL | |
# for writing DSLs. | |
# Let's write a simple DSL for adding flags to a function, just like | |
# the one you can find in socket.io, using everything we talked about | |
# in this lecture, including applying the context, and splats. | |
# We are going to create the function `flags` that takes a variadic | |
# number of arguments as flags, and a function, and returns a modifed | |
# version of this function that has access to the flags information. | |
# A good way to use the `flags` helper is to mix it in to your library. | |
# So we got two flags in the socket io library, one of them deprecated: | |
/** | |
* Broadcast flag. | |
* | |
* @api public | |
*/ | |
Client.prototype.__defineGetter__('broadcast', function () { | |
this.flags.broadcast = true; | |
}); | |
/** | |
* JSON flag (deprecated) | |
* | |
* @api public | |
*/ | |
Client.prototype.__defineGetter__('json', function () { | |
this.flags.broadcast = true; | |
}); | |
# And using the `flags` helper, TJ could have write it down like this: | |
class Client | |
constructor: -> | |
@mixin flags 'json', 'brodcast', @emit | |
emit: (data, args...) -> | |
if @flags.broadcast | |
# Logic.. | |
if @flags.json | |
# Logic.. | |
return this | |
# People can just use this as: | |
socket.broadcast.emit 'stuff' | |
socket.json.emit 'more stuff' | |
# And it will just work. | |
# So `flags` does a few things: | |
# 1. Actually create the flags on the object's prototype | |
# 2. Sets them to false on initialize | |
# 3. Resets them to false after the function has been called | |
flags = (flags..., fn) -> | |
@flags ||= {} | |
# Create the flags | |
for flag in flags | |
do (flag) => | |
@::__defineGetter__ flag, -> | |
@flags[flag] = true | |
# Set all the flags to false | |
for flag in flags | |
@flags[flag] = false | |
# Reference to the old function | |
old_fn = fn | |
# Create the new function. It first | |
# calls the old function, passing the | |
# arguments, and then resets the flags | |
# to false. | |
@[fn] = -> | |
res = old_fn.apply(this, arguments) | |
for flag in flags | |
@flags[flag] = false | |
# Return value of the original function | |
return res | |
##### Lazy Loading | |
# Another cool trick, that I stole from GruntJS, is that you can use | |
# defineProperties to lazy load libraries hooked into the object. | |
# So when we use | |
utils.mkdirp | |
# It will lazily require it. | |
Object.defineProperties(utils, | |
[ | |
// Wrapper to `require('mkdirp')` | |
'mkdirp', | |
// Wrapper to `require('rimraf')` | |
'rimraf', | |
// Wrapper to `require('./fetch')`, internal tarball helper using | |
// mikeal/request, zlib and isaacs/tar. | |
'./fetch' | |
].reduce(function( descriptor, api ) { | |
return ( | |
// Add the Identifier and define a get accessor that returns | |
// a fresh or cached copy of the API; remove any | |
// non-alphanumeric characters (in the case of "./fetch") | |
descriptor[ api.replace(/\W/g, '') ] = { | |
get: function() { return require(api); } | |
}, | |
// Return the |descriptor| object at the end of the expression, | |
// continuing the the reduction. | |
descriptor | |
); | |
// Prime the "initialVal" with an empty object | |
}, {}) | |
); | |
# So let's write this in CoffeeScript as a mixin to your classes / objects: | |
# Helper for flipping arguments on a function | |
Object::flip = (fn, args...) -> | |
@[fn].apply(this, args.reverse()) | |
# so we can call setTimeout with | |
setTimeout 1000, -> | |
log "timeout!" | |
# Mixin to lazy load packages: | |
packages = (packages....) -> | |
@defineProperties this, | |
packages.flip 'reduce', {}, (descriptor, api) -> | |
descriptor[api.replace(/\W/g, '')] = -> require api | |
return descriptor | |
#### MVC | |
##### Funky Mixins for Pagination, Modals, Tooltips, Form Builders, ListViews | |
#### Functional Programming | |
##### Currying | |
##### Applying | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment