How to translate code from Lisp (MIT Schema) into JavaScript using Ramda?

Viewed 246

I'm currently teaching myself functional programming.

I'm trying to translate the following:

(define a 3)
(define b (+ a 1))

(* (cond ((> a b) a)
         ((< a b) b)
         (else -1))
   (+ a 1))

into JavaScript (using Ramda).

One could use nested ternaries, but I like to using the cond function from Ramda. Here is what I did:

const a = 3;
const b = 3 + 1;

cond([[() => a > b, () => a], [() => a < b, () => b], [T, () => -1]])() * (a + 1)

The problem I have with this is that I had to use these functions (e.g. () => 3) instead of just their value (e.g. a).

Is there any way to avoid these functions? Or is there another better way of doing this in JavaScript (maybe even without Rambda)?

I would like to avoid statements such as if, for and switch.

An alternative way to solve this would be using:

import gt from "ramda/src/gt";
import lt from "ramda/src/lt";

const a = () => 3;
const b = () => a() + 1;


cond([[gt(a, b), a], [lt(a, b), b], [T, () => -1]])() * (a() + 1);

Which complicates a and b, since they will always have to be called (see a() + 1).

EDIT:

For some reason the last code where I define a and b as functions doesn't work

3 Answers

Ramda is auto curried, so you can invoke the function with some of the parameters, and get a new function back. For example:

const { pipe, cond, gt, lt, T, always, identity, multiply } = R

const a = 3
const b = 3 + 1

const fn = (a) => pipe(
  cond([
    [gt(a), always(a)],
    [lt(a), identity],
    [T, always(-1)]
  ]),
  multiply(a + 1)
)

const result = fn(a)(b)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

I think you're working under a bit of a misapprehension. cond is designed to give you a function. It's really not designed for creating a scalar value for an expression. Of course it can do so, simply by immediately calling the function it generates. But that's not the point of it. Moreover, it really is not meant for calling with nullary functions (except in the case of passing T for the default condition.). Again, you can do so, but it's working against the grain of the tool.

Although Ramda takes inspiration from both LISP-style and ML-style functional languages, and although I am personally more a fan of the former, Ramda is closer to the ML world, especially to Haskell. So the main activity it is meant to support is to build functions by composing other functions.

If I were to solve this problem, I probably wouldn't use any Ramda whatsoever, choosing something like this:

const larger = (a, b) => (a > b) ? a : (b > a) ? b : -1
const foo = (a, b) => (a + 1) * larger(a, b)

foo(3, 4) //=> 16
foo(6, 3) //=> 42
foo(3, 3) //=> -4

Or, if I didn't need to reuse larger, I might inline it like this:

const foo = (a, b) => (a + 1) * ((a > b) ? a : (b > a) ? b : -1)

Of course I can write this in Ramda, and in a point-free fashion:

const larger = cond([[gt, unapply(head)], [lt, unapply(last)], [T, always(-1)]])
const foo = converge(multiply, [inc, larger])

Or again, I might inline larger, or replace unapply(head) with nthArg(0) and unapply(last) with nthArg(1).

But none of these options is as readable as the original. Ramda adds nothing here that I can see. Note that I am a big fan of Ramda; I started the library and am one of its principle authors. But I don't think it should be used for every problem.

The reason why you don't have to wrap everything in thunks (i.e. functions that take no arguments) in Scheme is because cond is a macro that expands to nested ifs, in your case:

(if (> a b) a
  (if (< a b) b
    -1))

So nope, if you want to avoid ternary operators and wrapping everything in thunks you don't have much of a choice with vanilla JS.

If you don't mind using non-standard JS, you could implement cond with a macro with Sweet.js...

Related