3

Consider this Lisp macro:

(defmacro doif (x y) `(if ,x ,y))

The same in Haskell:

doif x y = if x then (Just y) else Nothing

Is it possible to achieve this elegance in the "lisp-y" language of JavaScript? (the use of the word "lisp-y" comes from the author of the JS language)

johnbakers
  • 24,158
  • 24
  • 130
  • 258
  • You want to execute `y` if `x` is `true` in javascript? – thefourtheye Oct 14 '13 at 09:23
  • 4
    No, you cannot get this elegance because neither JS has macros nor it is non-strict language. Please don't try to use word elegance with JS, it leads to depression;) – Ankur Oct 14 '13 at 09:34
  • @Ankur I disagree. JavaScript is a very elegant language. Yes, it is impure and it doesn't have algebraic data types or pattern matching like Haskell does. Nevertheless it is still a very powerful language. JavaScript supports functional, object-oriented and procedural styles of programming - the big three. Hence you can mix and match whichever style of programming you prefer. In addition it has prototypal inheritance (which is my opinion is an elegant solution to inheritance and code reuse). In addition the learning curve of JS is not very steep unlike Haskell. BTW I love Haskell and FP too. – Aadit M Shah Oct 14 '13 at 10:18
  • @AaditMShah: That was just my experience :). Anyway.. check out http://wtfjs.com/ – Ankur Oct 14 '13 at 10:26
  • @Ankur Every language has its quirks. For example some people may find the following code in Haskell to be perfectly valid (even though it isn't): `(test :: Float -> Int -> Int -> Float) test a b c = a * (b - c)`. This is an actual question: http://stackoverflow.com/q/19019093/783743 The point is that good programmers can program in any language, and some languages are more suitable than others for certain applications. For example it's easy to write a game in JS because it integrates well with CSS and HTML. Writing a game in Haskell OTOH requires more effort. Even with the reactor pattern. =) – Aadit M Shah Oct 14 '13 at 10:38
  • @AaditMShah: A good programmer can program in any language BUT real world is not about writing Fibonacci function, its about designing and implementing systems of various scale and that's where pain lies. – Ankur Oct 14 '13 at 10:50
  • @Ankur Which is precisely why JavaScript programmers all over the world spend so much time and effort to create abstractions that make JavaScript less sucky every passing day. Consider the statistics: http://redmonk.com/sogrady/2013/02/28/language-rankings-1-13/ JavaScript is the most popular language on GitHub and StackOverflow.Perhaps it's because you __have to use JavaScript for any sort of interactivity on the web.__ Necessity is the most important thing in life (http://www.codinghorror.com/blog/2005/03/on-necessity.html) and JS is necessary. Hence people strive to make it better everyday. – Aadit M Shah Oct 14 '13 at 11:06
  • @Ankur: Javascript language is horrible (even if reasonably powerful), but Javascript runtime environment is very nice (thanks to the billions of dollars that got invested in it). So if you want the runtime but you hate the language then just compile from a decent language to Javascript and you're done. – 6502 Oct 14 '13 at 20:05
  • 1
    @6502: I agree. These days you can compile Haskell, F#, Scala etc to JS. JS runtime like V8 and various mozilla's monkeys :) are amazing tools but they have to consider the JS language semantics and that sort of tie their hands and .. behold.. then you have DOM :) – Ankur Oct 15 '13 at 04:51
  • @Ankur Please don't remind me of the DOM. It brings back bad memories and I shudder to think that I have to deal with it in my next project! ;( – Aadit M Shah Oct 15 '13 at 13:03

3 Answers3

3

Yes, it is possible. If you simply want to a one-to-one mapping of the lisp-y code then you could use the conditional operator as follows:

x ? y : null // note that x and y are expressions, not values: they aren't eval'd

If you don't mind the result of the expression being a falsy value (instead of an explicit null value) then you could use the guard operator instead:

x && y // reminder: x and y are expressions - they aren't evaluated until needed

If you want to create your own doif syntax then you can use hygienic macros provided by sweet.js as follows:

macro doif {
    rule { ($x:expr) ($y:expr) } => { $x ? $y : null }
}

The above macro allows you to write code like this:

doif (x < 100) (x + 1)

The above code gets converted to:

x < 100 ? x + 1 : null

When using either of these operators if the condition x is falsy then y is not evaluated.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • `x ? y : null` is not same as the lisp code. The `x` and `y` can be any expression (not just some variables holding value) which basically mean both x and y will get "evaluated" in this form. What is required that `y` is not evaluated before checking `x` – Ankur Oct 14 '13 at 10:12
  • 3
    @Ankur The conditional operator evaluates only one of its second and third arguments, so it is like the lisp code in that respect. – Daniel Wagner Oct 14 '13 at 10:14
  • @Ankur I beg to differ. The `x` and `y` in this case are not variables. They are placeholders for any expression. That's the reason I provided the macro definition `doif`. If you see carefully `x` and `y` are of type `expr` (any expression). In either case the expression `y` is only evaluated if `x` is truthy. – Aadit M Shah Oct 14 '13 at 10:15
  • @Ankur If you don't believe me then take a look at the demos: 1) `true ? alert("Hello World!") : null` (will alert `"Hello World!"`): http://jsfiddle.net/n9kF4/ 2) `false ? alert("Hello World!") : null` (will not alert `"Hello World!"`): http://jsfiddle.net/n9kF4/1/ – Aadit M Shah Oct 14 '13 at 11:25
1

I suppose the usual way to emulate laziness in a non-lazy language is to manually thunkify. I don't know Javascript very well, so I'm going to write pseudocode instead and hope that you can paint Javascript syntax on the idea.

function doif(x, y) {
    if(x) { return y(); }
    else  { return undefined; }
}

And a sample call:

function expensive() {
    // do a lot of work here
}
doif(false, expensive); // runs very fast
doif(true , expensive); // runs very slow
doif(true , expensive); // runs very slow (unlike Haskell, but I think like Lisp)
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • I don't think `y` must be invoked. The OP wrote `doif x y = if x then (Just y) else Nothing`. Hence even if `y` is a function (which in all probability it isn't) it's only being wrapped in a `Maybe` value. It's never being called. In my humble opinion the correct conversion into JavaScript is `x ? y : null`. – Aadit M Shah Oct 14 '13 at 10:23
  • @AaditMShah My reading of the question is that we'd like to delay the computation of something until later (in this example, "later" is "after we know whether some condition is true or not"). Putting the computation inside a function is one way to delay it -- and calling the function is the way to stop delaying. – Daniel Wagner Oct 14 '13 at 10:27
  • The OP doesn't want to delay a computation per se. He just wants an expression to return `y` if `x` evaluates to a truthy value. For example consider the following lisp code: `(doif (< x 100) (+ x 1))`. This should translate to `x < 100 ? x + 1 : null`. Translating it into `doif(x < 100, function () { return x + 1; })` is unnecessary boilerplate. In either case the second expression is only evaluated if the condition is truthy which is why you can do things like `condition ? alert("Hello World!") : null`. The `alert` function will only be invoked if `condition` is truthy. – Aadit M Shah Oct 14 '13 at 10:45
  • 1
    @AaditMShah Your comment, "in either case the second expression is only evaluated if the condition is truthy", is exactly what I mean by delaying a computation! You are delaying the evaluation of the second expression until you know more about the condition. While it's true that the conditional operator does this *for this precise use case*, there are plenty of other useful patterns of delay that are not just "if such-and-such a condition is true". It is this more general use case that my answer is aimed at handling. – Daniel Wagner Oct 14 '13 at 10:48
  • No, your `doif` function is not more general than the conditional operator. This is how you have defined the `doif` function: `function doif(x, y) { if (x) return y(); }`. This is exactly what the conditional operator does. In fact you could write your `doif` function more succinctly as `function doif(x, y) { return x ? y() : ; }`. Hence your `doif` function is not more general than the conditional operator. In fact the conditional operator is more general than your `doif` function because it doesn't expect `y` to be a function. You can do `x ? y() : ;` but you can't do `doif(x < 100, x + 1)`. – Aadit M Shah Oct 14 '13 at 10:57
  • @AaditMShah I think you're missing the point. Yes, `doif` is no more general than `?:`. But the idea embodied there (use a function to delay computation and call the function to force computation) *is* significantly more general. Additionally, your final objection is off-base: while you may not be able to literally write `doif(x < 100, x+1)`, you *can* write `function incr() { return (x+1); }; doif(x < 100, incr)`. – Daniel Wagner Oct 14 '13 at 11:04
  • I understand what you are implying: naming an expression so that it can be reused later. However my argument is that you don't need a `doif` function at all. You can do the exact same thing using the conditional operator too: `x < 100 ? incr() : null`. If you don't like the syntax then you can use [hygienic macros](http://sweetjs.org/ "sweet.js") to write it as `doif (x < 100) (incr())`. It's better to write use the conditional operator because: 1) there's no function call overhead (it's faster) 2) it's easier to read and understand. BTW you may wish to curry `incr` so as to write `incr(x)`. – Aadit M Shah Oct 14 '13 at 11:18
  • @AaditMShah This is not about naming; if Javascript has anonymous functions, I'd be happy using them instead. You can not implement all short-circuiting operators just in terms of `?:`. Hygienic macros are part of a different language that compiles to Javascript. I agree that the technique I'm suggesting here may be slower and less idiomatic than a built-in operator when one such exists. This is unrelated to currying, and changing `incr` in the way you suggest is also not currying. Whether your suggested change to `incr` is desirable depends on intent, which so far we both have been avoiding. – Daniel Wagner Oct 14 '13 at 11:37
  • JavaScript __does__ have anonymous functions. You __can__ write `doif(x < 100, function () { return x + 1; })`. I don't see how this is better than simply writing `x < 100 ? x + 1 : null` though. Seriously, why __would__ you want to write code like that? Yes you can't implement all short-circuiting operators in terms of `?:` which is why you also have the guard (`&&`) and default (`||`) operators: http://seanmonstar.com/post/707078771/guard-and-default-operators. There are lots of languages that compile to JS. Try: `function incr(x) { return function () { return x + 1 }; }`. It's more generic. – Aadit M Shah Oct 14 '13 at 11:48
  • @AaditMShah I try to give an explanation of why you would want to write code like this -- that is, code that takes advantage of lazy evaluation -- [in this answer](http://stackoverflow.com/questions/7868507/non-trivial-lazy-evaluation). Included in that answer, you will discover that you can not implement all short-circuiting operators just in terms of `?:`, `&&`, and `||`; sometimes you want a domain-specific short-circuiting operator, which are hard to do in a strict language and require some tricks like the one I'm suggesting here. – Daniel Wagner Oct 14 '13 at 12:07
  • I hear what you're conveying: lazy evaluation is indeed very helpful. Nevertheless in this case your `doif` function is just unnecessary boilerplate. You can do everything the `doif` function does, and more, using the conditional operator (`?:`). I fail to see what __"trickery you've done to implement a new domain-specific short-circuiting operator in JavaScript."__ To drive this point home consider the expression `condition ? alert("Hello World!") : null`. Here `alert` will only be invoked __iff__ `condition` evaluates to a truthy value. It's lazy. You don't need a new function is all I say. – Aadit M Shah Oct 14 '13 at 12:44
  • @AaditMShah I agree with you: `doif` is not a useful function. It is, however, an illustration of a useful idea. That is all I'm claiming -- and all I've been claiming since the beginning of our discussion. I hope you can agree with me, too. – Daniel Wagner Oct 14 '13 at 12:49
  • 1
    That I do agree. Implementing lazy evaluation in a strict language like JavaScript using first-class functions is very useful. Hence +1 for that. =) BTW since we're talking about laziness in JavaScript you might like the following projects: 1) [Lazy.js](http://danieltao.com/lazy.js/ "Lazy.js") 2) [prelude.ls](http://preludels.com/ "prelude.ls - a functionally oriented utility library in LiveScript") 3) [LiveScript](http://livescript.net/ "LiveScript - a language which compiles to JavaScript") Happy hacking in JavaScript (or LiveScript - take your pick). =D – Aadit M Shah Oct 14 '13 at 12:58
1

You can implements macros in Javascript because the compiler is part of the runtime (you can eval a string and get back code). However to get close to Lisp you need to implement your own full language because eval can only produce full functions, not expressions.

If instead you're just thinking about delayed evaluation this can be done trivially by wrapping the code in a closure:

function my_if(condition, then_part, else_part) {
    return condition ? then_part() : else_part();
}

my_if(foo < bar,
      function(){ return bar -= foo; },
      function(){ return foo -= bar; });

You can of course also create a closure that will encapsulate the whole operation by delaying also the test...

function my_if(condition, then_part, else_part) {
    return function() {
        return condition() ? then_part() : else_part();
    }
}

var my_prog = my_if(function(){ return foo < bar; },
                    function(){ return bar -= foo; },
                    function(){ return foo -= bar; });

my_prog(); // executes the code
6502
  • 112,025
  • 15
  • 165
  • 265