javascriptsanctuary

How does Curry differs between Sanctuary and Ramda?


I am stuck with the curry examples in "Professor's Frisby..." when using Sanctuary instead of Ramda. I get error: "‘curry2’ expected at most three arguments but received five arguments." while with Ramda works fine. I am sure I am doing something wrong, but I can't figure it out.

Following the book's example:

var match = curry2((what, str) => {return str.match(what);});
var hasSpaces = match(/\s+/g);
var filter = curry2((f, ary) => {return ary.filter(f);});
var f2 = filter(hasSpaces, ["tori_spelling", "tori amos"]);

I get

TypeError: Function applied to too many arguments
curry2 :: ((a, b) -> c) -> a -> b -> c
‘curry2’ expected at most three arguments but received five arguments.

Solution

  • Sanctuary is much stricter than Ramda. It ensures that functions are only ever applied to the correct number of arguments and that the arguments are of the expected types. S.add(42, true), for example, is a type error, whereas R.add(42, true) evaluates to 43.

    The problem in your case is that Array#filter applies the given function to three arguments (element, index, array). hasSpaces, though, expects exactly one argument.

    The solution is to use S.filter rather than Array#filter:

    const match = S.curry2((what, str) => str.match(what));
    const hasSpaces = match(/\s+/g);
    const f2 = S.filter(hasSpaces, ['tori_spelling', 'tori amos']);
    

    Having made this change, another type error is revealed:

    TypeError: Invalid value
    
    filter :: (Applicative f, Foldable f, Monoid f) => (a -> Boolean) -> f a -> f a
                                                             ^^^^^^^
                                                                1
    
    1)  null :: Null
    
    The value at position 1 is not a member of ‘Boolean’.
    
    See https://github.com/sanctuary-js/sanctuary-def/tree/v0.12.1#Boolean for information about the Boolean type.
    

    S.filter expects a predicate as its first argument. In strict terms a predicate is a function which returns either true or false. String#match, though, returns either null or an array of matches.

    The solution is to use S.test rather than String#match:

    const hasSpaces = S.test(/\s+/);
    const f2 = S.filter(hasSpaces, ['tori_spelling', 'tori amos']);
    

    At this point the definition of hasSpaces is so clear that there's not much value in giving it a name. We can write the code as a single expression:

    S.filter(S.test(/\s/), ['tori_spelling', 'tori amos'])
    

    Note that the pattern can be simplified from /\s+/g to /\s/. The g flag has no effect when using S.test, and the + isn't necessary since we're interested in strings with spaces but we're not interested in counting the spaces.