I don't understand this shortcut notation.
Two examples:
propEq (String → a → Object → Boolean)
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
I don't understand this shortcut notation.
Two examples:
propEq (String → a → Object → Boolean)
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
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
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)
?
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 a
s, by using the function to turn each a
into a b
, then using the Ord b
to figure out how the b
s would be sorted, and then sorting the a
s based on the b
s.
var nameGetter = prop("name") // function(obj) { return obj.name }
sortBy(nameGetter, [{ name: "Alice" }, { name: "Charlie" }, { name: "Bob" }])
// [{ name: "Alice" }, { name: "Bob" }, { name: "Charlie" }]