Ra supports Pointfree style in Elm by providing various combinators to work with Predicates, Relations, Math, Functions, and Flow Control.
Pointfree can be abused, but when it is appropriate pointfree style is
Predicate combinators provide a huge readability win when you have named predicates.
Takes a list of predicates and returns a predicate that returns true for a given list of arguments if at least one of the provided predicates is satisfied by those arguments.
isOkay : Person -> Bool
isOkay = anyPass [isKind, isCreative, isHardWorking]Return a new predicate that is true if both of the two predicates return true. Note that this is short-circuited, meaning that the second predicate will not be invoked if the first returns false.
Return a new predicate that is true if either of the two predicates return true. Note that this is short-circuited, meaning that the second predicate will not be invoked if the first returns true.
Invert (negate, complement) a predicate i.e if the predicate would have return True for some a it will now return False and vice versa.
A predicate that always returns false.
A predicate that always returns true.
Designed for use in pipelines or currying. Returns a predicate that tests if the value is less than the first value given.
[5, 10, 11, 20] |> List.map (lessThan 10)
-- [True, False, False, False]Designed for currying. Returns a predicate that tests if the value is less than or equal to the first value given.
[5, 10, 11, 20] |> List.map (lessThanEqualto 10)
-- [True, True, False, False]Designed for currying. Returns a predicate that tests if the value is greater than the first value given.
[5, 10, 11, 20] |> List.map (greaterThan 10)
-- [False, False, True, True]Designed for currying. Returns a predicate that tests if the value is greater than or equal to the first value given.
[5, 10, 11, 20]
|> List.map (greaterThanEqualTo 10) -- [False, True, True, True]A natural language alias for == that also helps to avoid parenthesis when writing point-free flows
employees
|> List.filter (.employmentType >> equals Contractor)A literate alias for addition that is nice for building functional pipelines by avoiding parenthesis.
[5, 10, 15, 20] |> List.map (adding 5)
-- [10, 15, 20, 25]Subtraction alias designed for pipelines i.e. the order is swapped such that the subtrahend is the first argument.
[20, 30, 40]
|> List.map (subtracting 6)
-- [14, 24, 34]Division designed for pipelines i.e. the divisor is the first argument.
[20, 32, 40] |> List (divideByInt 4)
-- [5, 8, 10]Division designed for pipelines i.e. the divisor is the first argument.
[20, 25, 26.25] |> List (divideByFloat 2.5)
-- [8.0, 10.0, 10.5]A natural language alias for *.
Negate the given number.
Creates a function that will process either the whenTrue or the whenFalse function depending upon the result of the condition predicate.
ifElse
(.weightInLbs >> R.Relation.greaterThan 500)
(always "Sorry. You cannot ride the coaster.")
(.name >> String.append "Enjoy your ride ")Returns a function which encapsulates if/else, if/else, ... logic given a list of (predicate, transformer) tuples.
Same as cond but also provides a default option in the case that none of the conditions pass.
Tests the final argument by passing it to the given predicate function.
Tests the final argument by passing it to the given predicate function.
Takes a predicate, a transformation function, and an initial value, and returns a value of the same type as the initial value. It does so by applying the transformation until the predicate is satisfied, at which point it returns the satisfactory value.
This function can best be explained with examples as it is unusually useful when building data pipelines but has a tricky type signature.
Please do NOT be afraid of the type signature!
If you would like a function that transforms a Person to a String of their first and last name.
converge
String.append
( .firstName, .lastName >> String.append " " )Or perhaps you are building a data pipeline
List WidgetextraWidgets: List Widget -> List WidgetThen you might write
converge
List.append
(identity, extraWidgets)Converge for the case of a 3 argument function. See converge for examples.
As with converge, convergeList is best explained with an example
convergeList
(String.join " ")
[ .firstName, .middleInitial, .lastName ]Or to be more complete
nonEmptyStrings : List String -> List String
nonEmptyStrings = List.filter (complement String.isEmpty)
fullName : Person -> String
fullName =
convergeList
(nonEmptyStrings >> String.join " ")
[ .firstName, .middleInitial, .lastName ]Converts a function from 2-tuple to normal curried function. This is mostly useful for reverting an uncurry.
Converts a function from 3-tuple to normal curried function. This is mostly useful for reverting an uncurry.
Flip the first and second arguments of a function.
For example, the first argument to Dict.get is the key and the second is the dictionary. What if we want to map over multiple keys? We would want to apply the dictionary first and get a function that accepts the key.
valuesForKeys : Dict comparable value -> List comparable -> List value
valuesForKeys =
flip Dict.get >> List.filterMapMore simply, consider the case of having a (Dictionary k v) of values but wanting to pass along a function k -> Maybe v to some provider
so that the provider doesn't couple to the Dictionary? This would be a good idea to Reduce Coupling and honor the Principle of Least Privilege.
someInterestingProvider : (Int -> Widget) -> String -> Maybe Widget
someInterestingProvider = Debug.todo "doesn't matter for the demonstration"
someConsumer : List Widget -> String -> Maybe Widget
someConsumer widgets =
someInterestingProvider (flip Dict.get (indexBy .id widgets))This is like map for functions but going the opposite direction (contra functor).
OKAY. So that it probably a little or a lot confusing. How would you use this? Any time yout have a parent/child relation and a function that will transform the child and you want to get back a function that transforms the parent.
addressesCount : Person -> Int
addressesCount = fnContraMap .addresses List.lengthBest described with an example. Let's say you have a parent/child relation, a function of 2 arguments that applies to the child type and you would like to get back a function that works on the parent.
sameFavoriteColor : Person -> Person -> Bool
sameFavoriteColor = fnContraMap2 (.favorites >> .color) (==)Same as fnContraMap2 but for 3 argument functions.
Converts a normal Elm function to a function that accepts a 2-tuple. This can be useful if the output of some function happens to be a tuple or a functor (List, Maybe, Result, etc.) of a tuple.
sums : List (number, number) -> List number
sums = List.map (uncurry adding)
sum : Maybe (number, number) -> Maybe number
sum = Maybe.map (uncurry adding)Converts a standard Elm function to a function that accepts a 3-tuple.
Dict.member with the arguments flipped in order to test the member of many keys against a single dictionary.
let
validPersonIds =
personIds
|> List.filter (isMemberOf idToPerson)
in
-- ...Deduplicate consecutive items in a list using a function to extract the element of interest.
deduplicateConsecutiveItemsBy identity [ 1, 1, 1, 2, 3, 3 ] -- [1, 2, 3]
deduplicateConsecutiveItemsBy
Tuple.second
[ ("Betty", 1), ("Tom", 1), ("Jane", 2)] -- [("Betty", 1), ("Jane", 2)]
Takes a list of predicates and returns a predicate that returns true for a given list of arguments if every one of the provided predicates is satisfied by those arguments.