1

I don't understand this shortcut notation.

Two examples:

  1. propEq (String → a → Object → Boolean)

  2. sortBy (Ord b => (a → b) → [a] → [a])

Can you decipher it?

And yes, I know how to find its docs, but this documentation is rather poorly described

propEq

sortBy

John Taylor
  • 729
  • 3
  • 11
  • 22
  • 1
    I don't know what ramda.js is but those look like type signatures. – melpomene Sep 09 '17 at 21:22
  • 1
    It looks like a curried type signature. I'd start with [reading the (very sparse) documentation](http://ramdajs.com/), and link to a specific example that uses this. – user2864740 Sep 09 '17 at 21:27
  • Yes, I read it, but they didn't explain this notation. Maybe it is THAT obvious, but not for me. – John Taylor Sep 09 '17 at 21:29
  • Then taking those notations *and* the functions themselves (or example usages), how do they aline? – user2864740 Sep 09 '17 at 21:30
  • sortBy: Sorts the list according to the supplied function. So why Ord b => (a → b) → [a] → [a]? What does [a] → [a] stand for? I'm really trying to crack that... – John Taylor Sep 09 '17 at 21:37
  • 3
    I wrote a detailed answer to this [in two](https://stackoverflow.com/a/40364494/1243641) [parts](https://stackoverflow.com/a/40364892/1243641), reiterating the information on the [Ramda Wiki](https://github.com/ramda/ramda/wiki/Type-Signatures). – Scott Sauyet Sep 10 '17 at 00:15
  • Thanks @ScottSauyet for your very detailed answer! – John Taylor Sep 10 '17 at 13:09

3 Answers3

1

Ramda has not the easiest documentation to understand, that's for sure. Here is how I interpret this, hope it brings some clarity:

In ramda, most if not all functions are curried. That means that any function in ramda can be called with all the required arguments or less. If less than the required number of arguments are provided, the result will be another function that can receive the remaining arguments. When the function is fully applied, then it will return the result.

So for example, let's take propEq. As you wrote, its signature is:

propEq (String → a → Object → Boolean)

So what does this mean? First what the function is supposed to do: It will take a property from an object, compared to a given object, and return the comparison as a boolean.

First of all the rightmost attribute is the return value. When fully applied, propEq will return a boolean. So the last part is clear. Then it remains:

String → a → Object

this are the arguments I mentioned above, the key, the value, and the object. So one valid call would be:

R.propEq('age', 30, { age: 30 }) --> true

given ramda composability, this actually can be broken in multiple calls:

R.propEq('age')(30)({ age: 30 }) --> true
Mario F
  • 45,569
  • 6
  • 37
  • 38
1

This looks a lot like Haskell. In Haskell you have:

sortBy :: (Ord b) => (a -> b) -> [a] -> [a]

The thing on the right is a type signature.

[A] is the type of lists where the elements have type A.

A -> B is the type of a function that takes an A and returns a B.

-> is right associative: A -> B -> C means A -> (B -> C), i.e. a function that takes an A and returns a function that takes a B and returns a C.

Haskell has no argument lists. Instead all functions are curried in the way described above: A function of 2 arguments is actually a function of one argument that returns another function (that takes the other argument and returns the real result). Another way to look at it is to squint and say a function type has the form A1 -> A2 -> ... -> An -> R, where Ai are the argument types and R is the result type.

Identifiers starting with an uppercase letter are real types. Identifiers starting with a lowercase letters are type variables (which can be used as any type you want).

=> separates type constraints from the actual type. Here we have Ord b, which requires that the type used for b supports ordering (i.e. <, > operations).

Putting it all together:

sortBy is a function that takes an argument of type a -> b and returns a result of type [a] -> [a].

The argument of type a -> b is another function, one that maps values from some type a to some type b. b must be an ordered type.

The result of type [a] -> [a] is another function, one that maps lists of values of type a to lists of the same type.

The idea is that if you want to sort a list of values that shouldn't be compared directly, you can use sortBy with a helper function that extracts a comparison value from each list element. sortBy then returns a sorted list (according to the comparison values).

Sample usage:

sortBy length ["don't", "fear", "the", "reaper"]
=> ["the", "fear", "don't", "reaper"]

Here we use String as our a and Int as our b (because length :: String -> Int).

Note that function application is left associative: f x y actually means (f x) y (i.e. apply the function f to an argument x, then apply the result (which must be another function) to y).


As for how this applies to JavaScript ... no idea.

Maybe they want you to call it as sortBy(getComparisonValue, inputList) or sortBy(getComparisonValue)(inputList)?

melpomene
  • 84,125
  • 8
  • 85
  • 148
0

I don't personally know Ramda, but this is pretty clearly Haskell syntax.

f :: Type

f is something with the type Type. JS:

var f = Type(...)

f :: [Type]

f is a list of somethings, all with type Type. JS:

var f = [Type(...), Type(...), ...]

f :: Type -> Type

f is a function taking a value of Type returning a value of Type

var f = function(t) { return Type(...) }
f(Type(...))

f :: Type -> Type -> Type

The function arrow (->) is right-associative. This is equivalent to

f :: Type -> (Type -> Type)

This means that f takes a Type and returns a function that takes a Type and returns a Type. This is how Haskell does functions with more than one parameter. This technique is called currying

var f = function(l) { return function(r) { return Type(...) } }
f(Type(...))(Type(...))
// Ramda does dark magic that allows you to do vvvv as well
f(Type(...), Type(...))

f :: a -> b -> a

Lowercase identifiers in types are type variables. In the definition of f, it is not known what a and b are, and external code can pass whatever they want. Functions with type variables satisfy "parametricity". f promises that it doesn't care how a and b work, which restricts what it can do. In Haskell, f has only one possible implementation, which is:

var left = function(l) { return function(r) { return l } }
left(x)(y) === x
left(1)("a") === 1
// If it's a Ramda function
left(1, "a") === 1

max :: Ord a => a -> a -> a

(...) => specifies a "constraint." This function works for any type a, as long as there is some way to ordering to it, which is expressed by the constraint Ord a. max is still somewhat parametric: the only part of a it can inspect is its order; it won't be able to analyze anything beyond that.

var max = function(a) { return function(b) { return a > b ? a : b } }
max(1)(94) === 94
max({})(5) // Not allowed
// Since {} and 5 aren't the same type, the above call to max is bad
// JS is not strongly typed enough to warn you, but it just won't work out
max({})({})
// Object does not have an ordering to it, so the above call is bad,
// because the (Ord a) constraint is violated.

propEq :: String -> a -> Object -> Boolean

propEq takes a string, some value of any type, an Object, and then does something to them, returning Boolean. According to the documentation, it might help to think of it as

propEq :: String -> a -> (Object -> Boolean)

This takes a property name and value for that property, returning a predicate that's true for matching objects.


sortBy :: (Ord b) => (a -> b) -> [a] -> [a]

sortBy takes a function, and it needs the return type to be ordered. It can then sort a list of as, by using the function to turn each a into a b, then using the Ord b to figure out how the bs would be sorted, and then sorting the as based on the bs.

var nameGetter = prop("name") // function(obj) { return obj.name }
sortBy(nameGetter, [{ name: "Alice" }, { name: "Charlie" }, { name: "Bob" }])
// [{ name: "Alice" }, { name: "Bob" }, { name: "Charlie" }]
HTNW
  • 27,182
  • 1
  • 32
  • 60