© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
K. EasonStylish F# 6https://doi.org/10.1007/978-1-4842-7205-3_9

9. Programming with Functions

Kit Eason1  
(1)
Farnham, Surrey, UK
 

“Form follows function” – that has been misunderstood. Form and function should be one, joined in a spiritual union.

—Frank Lloyd Wright, Architect

Functions First

One of the things that makes F# a functional-first language is that its functions are “first-class values.”1 But what does that really mean, and how genuinely useful is it? In this chapter, you’ll get the answers to these questions and learn how you can use (and sometimes abuse) this feature to build simple, refactorable code. This is one of those topics where we move quite a way from the familiar ground of Object-Oriented code. So buckle up and enjoy the ride!

Functions as Values

What does it mean to describe a function as a value? Consider a simple function that adds two numbers (Listing 9-1).
// int -> int -> int
let add a b = a + b
// int -> int -> int
let addUp = add
// 5
printfn "%i" (add 2 3)
// 5
printfn "%i" (addUp 2 3)
Listing 9-1

Binding a function to another label

Listing 9-1 shows that we can not only define a function and use it: we can also bind it to another label (let addUp = add) and call that as if it were the original function. We can also pass it as an argument to some other function and have that other function call the supplied function (Listing 9-2).
let add a b = a + b
let applyAndPrint f a b =
    let r = f a b
    printfn "%i" r
// "5"
applyAndPrint add 2 3
Listing 9-2

A function as a parameter for another function

In Listing 9-2, the function applyAndPrint has a parameter called f, whose signature is a function that takes two values and returns an integer. In its body, applyAndPrint calls the provided function – whatever it is – and prints the result.

All this is achieved without any additional ceremony, for example, having to make the function be a “delegate” or a “func.” It’s just a value that happens to be a function. The fact that it is a function (and not a single integer value or a string or whatever) is deduced by the compiler entirely by the way it is used. In this case, the key line is
let r = f a b.

Furthermore, the compiler knows the function must return an integer, because we use its result in a printfn statement that uses a %i format string.

Treating functions as values unlocks a rich store of possibilities for expressing yourself in code. But it comes with the need to understand a few distinctly non-ObjectOriented concepts, which I’ll cover in the next few sections.

Currying and Partial Application

To get a bit more out of functions as values, we need to go into the twin concepts of curried arguments and partial application. You can think of curried arguments as being arguments expressed as separate values like this:
let add a b = a + b
…as opposed to the (for many people) more familiar style of tupled arguments, which are bracketed together like this:
let add (a, b) = a + b
Note

I’m using the terms “curried arguments” and “tupled arguments” here even though, strictly speaking, these are function parameters (in the definition), not arguments (actual values at call time). It just happens that “curried arguments” and “tupled arguments” are the more commonly used, if less, precise phrases.

Partial application is the act of binding a function while providing values for some, but not all, of the expected arguments (Listing 9-3).
// int -> int -> int
let add a b = a + b
// int -> int
let addTwo = add 2
// 5
printfn "%i" (add 2 3)
// 5
printfn "%i" (addTwo 3)
Listing 9-3

Partial application

In Listing 9-3, we bind a function called addTwo , which takes one argument and adds it to the constant 2. This is achieved by using (applying) add and providing one argument value: 2. The one “left-over” argument of add is now required by addTwo, and when we supply a value for it (last line of Listing 9-3), an actual calculation is done.

Incidentally, we can only use partial application because the add function’s arguments are curried, not tupled. With tupled arguments, the caller always has to provide the complete tuple.

Note

Another way to think of currying is that every function in F# takes only one parameter. If a function is bound with, say, two (nontupled) parameters, it’s really a function that takes one parameter and returns a function that itself takes the other parameter.

You may well wonder why you’d ever want to curry and partially apply! The short answer is code reuse. Imagine you want a simple function that surrounds a string with two other strings. The surrounding strings might be brackets, quote marks, or even comment delimiters. Listing 9-4 shows how you can do this in a concise way using partial application.
let surround prefix suffix s =
    sprintf "%s%s%s" prefix s suffix
let roundParen = surround "(" ")"
let squareParen = surround "[" "]"
let xmlComment = surround "<!--" "-->"
let quote q = surround q q
let doubleQuote = quote """
// ~~Markdown strikethrough~~
printfn "%s" (surround "~~" "~~" "Markdown strikethrough")
// (Round parentheses)
printfn "%s" (roundParen "Round parentheses")
// [Square parentheses]
printfn "%s" (squareParen "Square parentheses")
// <!--XML comment-->
printfn "%s" (xmlComment "XML comment")
// "To be or not to be"
printfn "%s" (doubleQuote "To be or not to be")
Listing 9-4

Parenthesizing strings using partial application

In Listing 9-4, we start with a simple function called surround, which does the fundamental work of surrounding a string with two other strings. The surround function has curried arguments, which means we can use partial application to specialize the function in various different ways: roundParen and squareParen parenthesize a string with round and square brackets, respectively; and just to prove that we can use longer surrounding strings, xmlComment surrounds the string with <!-- and -->. We also define a quote function, which uses the same string before and after the input string, again by calling surround. Then we specialize quote further, as doubleQuote, to surround the input string with double quotation marks.

Mixing Tupled and Curried Styles

There’s nothing in the rule book that says you can’t mix tupled and curried styles. Let’s say you decide that it’s invalid to allow the enclosing strings to be applied separately in the surround function. You could enforce that by tupling together the prefix and suffix parameters (Listing 9-5).
let surround (prefix, suffix) s =
    sprintf "%s%s%s" prefix s suffix
let roundParen = surround ("(", ")")
let squareParen = surround ("[", "]")
let xmlComment = surround ("<!--", "-->")
let quote q = surround(q, q)
let doubleQuote = quote """
// ~~Markdown strikethrough~~
printfn "%s" (surround ("~~", "~~") "Markdown strikethrough")
// (Round parentheses)
printfn "%s" (roundParen "Round parentheses")
// [Square parentheses]
printfn "%s" (squareParen "Square parentheses")
// <!--XML comment-->
printfn "%s" (xmlComment "XML comment")
// "To be or not to be"
printfn "%s" (doubleQuote "To be or not to be")
Listing 9-5

Mixed tupled and curried styles

Note how all the code in Listing 9-5 has been amended so that prefix and suffix are provided as a single tuple. But there is still partial application going on, for instance, when we define specialized versions like roundParen and quote, where we provide the whole prefix/suffix tuple but no value for the s parameter.

Stylistically, mixing tupled and curried styles is relatively rare, though oddly enough we have encountered one example earlier in this book. It happened in Chapter 8, when we had to provide a tuple for the two indices of a two-dimensional array property in the property’s setter, and yet the value to be set was curried. (The relevant code is repeated in Listing 9-6).
type RingBuffer2D<'T>(items : 'T[,]) =
    let leni = items.GetLength(0)
    let lenj = items.GetLength(1)
    let _items = Array2D.copy items
    member _.Item
        with get(i, j) =
            _items.[i % leni, j % lenj]
        and set (i, j) value =
            _items.[i % leni, j % lenj] <- value
Listing 9-6

Mixed tupled and curried styles in the wild

It’s also worth saying that many F# developers prefer the curried style even when they don’t intend to use partial application, simply because it means there are fewer brackets to type and match up.

When writing a function, it’s important to think through the order of the curried arguments; which arguments will you want to provide “first,” and which will you want to leave open for specialization?

Function Signatures Revisited

We discussed function signatures a bit in Chapter 2, but I want to revisit the topic here because it’s very important to start thinking in function signatures when designing code. Like me, you might initially have been a bit irritated to find that in F#, the type signature of a function like add is int -> int -> int. “Why,” I thought, “can’t they use a different symbol to separate the parameter list (int and another int) from what the function returns? (int). Why is it all just arrows?” The answer is because when we use curried style, there truly is no distinction. Every time we provide an argument value for a parameter, one item gets knocked off that undifferentiated list of parameters, until we finally bind an actual value with a nonfunction type like int (Listing 9-7).
// int -> int -> int
let add a b = a + b
// int -> int
let addTwo = add 2
// int
let result = addTwo 3
Listing 9-7

Function signatures and function application

Note how the type signature of the add function differs if we tuple its parameters (Listing 9-8).
// int * int -> int
let add(a, b) = a + b
// int
let result = add(2, 3)
Listing 9-8

Function signature for tupled arguments

The asterisk in the construct int * int shows that these values are part of a tuple, and the function is expecting a whole tuple, not just two integers that might be provided separately.

It’s worth getting familiar with F#’s way of expressing type signatures for two reasons: they let you verify that a function has the “shape” you expect, and they let you pin down that shape, if you want to, using type hints.

Type Hints for Functions

When a function takes another function as a parameter, the “outer” function obviously needs to apply the provided function appropriately. Think about the code in Listing 9-9, where we provide a function to another function.
let add a b = a + b
let applyAndPrint f a b =
    let r = f a b
    printfn "%i" r
// "5"
applyAndPrint add 2 3
Listing 9-9

A function as a parameter for another function

Type inference deduces that the signature of the function f is ‘a ->b -> int; in other words, “function f takes a parameter of unknown type ‘a and another parameter of unknown type ‘b and returns an integer.” The actual add function that we send in fits this signature (where ‘a and ‘b also turn out to be integers). But sometimes you will want to think about things in a different way: choosing to specify the type of f up front, by giving a type hint. You write the type hint using the same notation as shown in the type signatures we’ve just been discussing: that is, a list of parameter types, separated by-> if the parameters are to be curried, or * if they are to be tupled together. Listing 9-10 shows this in action. First (in applyAndPrint1), we allow the incoming curried arguments to be unknown types ‘a and ‘b, expressed as (f : 'a -> 'b -> int). Second (in applyAndPrint2), we pin them down to be integers, expressed as (f : int -> int-> int). And finally (in applyAndPrint3), we require a tuple of two integers, expressed as (f : int * int -> int).
// Takes curried arguments:
let add a b = a + b
// Takes tupled argument:
let addTupled(a, b) = a + b
// f must take curried arguments and return an int:
let applyAndPrint1 (f : 'a -> 'b -> int) a b =
    let r = f a b
    printfn "%i" r
// f must take curried integer arguments and return an int:
let applyAndPrint2 (f : int -> int -> int) a b =
    let r = f a b
    printfn "%i" r
// f must take tupled integer arguments and return an int:
let applyAndPrint3 (f : int * int -> int) a b =
    let r = f(a, b)
    printfn "%i" r
// Must use the curried version of add here:
applyAndPrint1 add 2 3
applyAndPrint2 add 2 3
// Must use the tupled version of add here:
applyAndPrint3 addTupled 2 3
Listing 9-10

Using type hints to specify function types

This means that when writing a function that takes another function, you have two options (we’ll call the functions newFunction and paramFunction):
  • Work on the body of newFunction first, and let the compiler work out the type of paramFunction itself based on how it is used (as Listing 9-9).

  • Specify the signature of the paramFunction in newFunction’s parameter list using a type hint so that the compiler can check that you call paramFunction correctly in newFunction’s body (as Listing 9-10).

The final outcome can be just the same, because usually you can remove the type hint when everything is compiling successfully.

For me, there is no hard and fast rule for which approach to take. I usually start by relying entirely on type inference at first, but if either the compiler or I get confused, I try adding a type hint in case that clarifies matters. I normally try removing the type hint at the end of the process, but I don’t let it ruin my day if type inference can’t work out the signature, which sometimes does happen in otherwise valid code. In those cases, I leave the type hint in and move on. Even then, I often find later that my code elsewhere was imperfect, and when I sort that out, I try again to remove type hints I left in earlier.

All that being said, it can be useful to provide type hints for functions you expect other people to have to call or refer to a lot, for example, the public API of a library you are publishing. This can help readability for people who might not be interested in the nuts and bolts of your code, or who are viewing it outside an IDE.

Functions That Return Functions

Not only can functions take functions; functions can return functions. Unlike with parameters, explicitly returning functions requires you to pay a tiny syntax overhead, the keyword fun followed by an argument list and a forward arrow ->. We can rejig the add function from Listing 9-1 so that it behaves in exactly the same way but works by explicitly returning a function (Listing 9-11).
// int -> int -> int
let add a =
    fun b -> a + b
// 5
printfn "%i" (add 2 3)
Listing 9-11

Explicitly returning a function

See how in Listing 9-11 we use fun b -> to specify that we want to create a function that takes one argument, which we call b. Since this is the last expression in the definition of add, it is this newly minted function that is returned. Notice also how the type signature of the new add is the same as it was in Listing 9-1. This bears out what I was saying earlier: that you can think of a function with two arguments as only really taking one argument and returning a function that itself requires the remaining argument.

Why on earth would you want to make a function definition more complicated by explicitly returning another function? The answer is: you can do useful work, and/or hide data, by placing it inside the outer function but before the returned function. In Listing 9-12, we define a simple counter that takes a starting point, and each time it is invoked, it returns the next integer.
let counter start =
    let mutable current = start
    fun () ->
        let this = current
        current <- current + 1
        this
let c1 = counter 0
let c2 = counter 100
// c1: 0
// c2: 100
// c1: 1
// c2: 101
// c1: 2
// c2: 102
// c1: 3
// c2: 103
// c1: 4
// c2: 104
for _ in 0..4 do
    printfn "c1: %i" (c1())
    printfn "c2: %i" (c2())
Listing 9-12

A simple counter using explicit returning of a function

The counter function works by initializing a little bit of mutable state (current) and then returning a function that returns the current value and increments the state. This is a nice way of using, but concealing, some mutable state. (As implemented here, though, I wouldn’t want to warrant that it’s thread safe.)

Another situation where you might like to create and use, but not expose, a bit of state is random number generation. One way of generating random numbers is to create a new instance of the System.Random class and then call one of its methods to produce values. It’s always a little annoying to have to worry about the scope of the System.Random instance. But you can get around this by binding a value that creates the System.Random and then returns a function that gets the next value from it (Listing 9-13).
let randomByte =
    let r = System.Random()
    fun () ->
        r.Next(0, 255) |> byte
// E.g. A3-52-31-D2-90-E6-6F-45-1C-3F-F2-9B-7F-58-34-44-
for _ in 0..15 do
    printf "%X-" (randomByte())
printfn ""
Listing 9-13

Hiding a System.Random instance by returning a function

In Listing 9-13, the function we return takes unit (expressed as two round brackets) and uses – but does not expose – a System.Random instance to return a random byte. Although we call randomByte() multiple times, only one System.Random() instance is created. In addition to the data-hiding aspect, this pattern is also useful where it takes significant time to initialize the state within the outer function.

Function Composition

Once we realize that functions are simply values, it’s logical to ask if we can in some way add them together, as we can number values (by adding) or string values (by concatenating). The answer, you won’t be surprised to learn, is “yes.” Let’s imagine you have the task of taking some text and replacing all the directional or typographic quote marks with nondirectional or neutral ones. For example, this text:

“Bob said ‘Hello’,” said Alice.

… would be translated to this:

"Bob said 'Hello'," said Alice. (Note the nondirectional quote marks.)

The actual replacement is simply a matter of calling .NET’s String.Replace method a couple of times in functions called fixSingleQuotes and fixDoubleQuotes (Listing 9-14). Then we bind a function called fixTypographicQuotes. which calls fixSingleQuotes and fixDoubleQuotes to do its work.
module Quotes =
    module Typographic =
        let openSingle = 'u2018' // ‘
        let closeSingle = 'u2019' // ’
        let openDouble = 'u201C' // “
        let closeDouble = 'u201D' // ”
    module Neutral =
        let single = 'u0027' // '
        let double = 'u0022' // "
    /// Translate any typographic single quotes to neutral ones .
    let fixSingle (s : string) =
        s
            .Replace(Typographic.openSingle, Neutral.single)
            .Replace(Typographic.closeSingle, Neutral.single)
    /// Translate any typographic double quotes to neutral ones.
    let fixDouble (s : string) =
        s
            .Replace(Typographic.openDouble, Neutral.double)
            .Replace(Typographic.closeDouble, Neutral.double)
    /// Translate any typographic quotes to neutral ones.
    let fixTypographic (s : string) =
        s
        |> fixSingle
        |> fixDouble
"This had "typographical 'quotes'"" |> Quotes.fixTypographic
Listing 9-14

First cut of removing typographic quotes

There’s nothing inherently wrong with the way Quotes.fixTypographic is defined in Listing 9-14. Indeed, I would often be tempted to leave the code in that state. But there are several alternative ways of expressing the same logic, any of which you may encounter in the wild, and some of which you might even prefer.

Firstly, we note that Quotes.fixSingle returns a string and Quotes.fixDouble takes a string. Whenever some function takes the same type that another function returns, you can compose them together into a new function using the function composition operator >> (Listing 9-15).
    /// Translate any typographic quotes to neutral ones using
    /// function composition.
    let fixTypographic (s : string) =
        let fixQuotes = fixSingle >> fixDouble
        s |> fixQuotes
Listing 9-15

Basic function composition

In Listing 9-15, we define a function called fix, which is a combination of fixSingle and fixDouble . When fix is called, fixSingle will be called first (using the input to fix), and its output will be passed to fixDouble. Whatever fixDouble returns will be returned as the result of fixTypographic. Having defined fixTypographic, we then call it by passing the input s into it.

We can eliminate still more code by not explicitly binding fixQuotes, instead doing the composition “on the fly” in brackets and passing s into that (Listing 9-16).
    /// Translate any typographic quotes to neutral ones using
    /// function composition.
    let fixTypographic (s : string) =
        s |> (fixSingle >> fixDouble)
Listing 9-16

Using a composed function without binding it to a name

This does exactly the same thing as Listing 9-15, but without binding the composed function to an arguably unnecessary token.

Finally, we note that the explicit parameter s isn’t really needed because its sole purpose is to be passed into the composition of fixSingle and fixDouble . If we simply delete it, we still end up with a function fixTypographic that takes a string and returns a string (Listing 9-17).
    /// Translate any typographic quotes to neutral ones using
    /// function composition.
    let fixTypographic =
        fixSingle >> fixDouble
Listing 9-17

Eliminating an unnecessary parameter

It takes a while before one starts automatically recognizing where functions can be composed with >>, rather than pipelined together with |>. But once you start “feeling the force,” there is a temptation to go crazy with function composition. You may even find yourself bending other parts of your code just so that you can use composition. This is often a good thing: functions that are easily composable are often well-designed functions. But also remember: composition isn’t a goal in itself. The principles of motivational transparency and semantic focus trump everything else.

For example, if you use function composition extensively, the reader of your code will have fewer named bindings, like fixTypographic, to give them clues as to what is going on. And in the worst case, if the code has to be debugged, they won’t have a bound value to look at because the composed functions have effectively been put into a black box. Sometimes, code with a few explicitly bound intermediate values is simply more readable and more maintainable. Use function composition with restraint!

Recommendations

Here are some thoughts I’d like you to take away from this chapter:
  • Remember the twin concepts of currying (defining parameters as separate, untupled items) and partial application (binding a function value by applying another function giving some, but not all, its curried parameters).

  • Consider defining the parameters of your function in curried style. It can reduce noise (brackets) and make your functions more flexible to use.

  • Define curried parameters (more commonly known as curried arguments) in an order that is likely to make partial application by a consumer make the most sense.

  • Use currying and partial application judiciously to clarify and simplify your code and to eliminate code repetition.

  • Functions can take other functions as arguments. Exploit this to create beautiful, decoupled code. Remember that you have a choice about whether to specify the signature of the incoming function using a type hint or to allow type inference to infer its signature based on how it is used.

  • Functions can explicitly return other functions. This can be a great way to get data hiding without classes.

  • Whenever a function’s input is the same type as another function’s output, they can be composed together using the >> operator. The fact that the functions you have written are composable is a good sign, but that doesn’t mean you have to compose them with >>. You may be sacrificing readability and ease of debugging.

Summary

Coding gurus love to talk about “decoupled code.” But in Object-Oriented languages, functions are still coupled to classes in the form of methods, and parameters are still coupled to one another in the form of tuples. F# sets functions free by making them first-class values, able to be declared independently, called, passed as arguments, returned as results, and composed together; all vastly increasing the expressiveness of the language. In part, this is achieved by using the concept of curried arguments, which can be applied one at a time, with each supplied argument taking us one step closer to an actual computation.

One of the keys to writing stylish F# code is to make good but cautious use of these powers. Above all, don’t always use partial application and composition to reduce your code down to the most concise expression humanly possible. It won’t be readable or maintainable.

In the next chapter, we’ll leave the rarefied world of F# functions and start our journey into performant F# code by looking at asynchronous and parallel programming .

Exercises

Exercise 9-1 – Functions as Arguments
Think back to the code from Listing 9-2, where we supplied an add function to applyAndPrint, which calls add and prints the results:
let add a b = a + b
let applyAndPrint f a b =
    let r = f a b
    printfn "%i" r
// "5"
applyAndPrint add 2 3

Define another function called multiply that multiplies its arguments. Can it be used by applyAndPrint?

What if you want to send in a function to subtract its second input from its first? Is it possible to do this without defining a named function called something like subtract?

Exercise 9-2 – Functions Returning Functions
In Listing 9-12, we defined a counter that returned a function to count up from a defined starting point:
let counter start =
    let mutable current = start
    fun () ->
        let this = current
        current <- current + 1
        this
let c1 = counter 0
let c2 = counter 100
for _ in 0..4 do
    printfn "c1: %i" (c1())
    printfn "c2: %i" (c2())

Define another function called rangeCounter that returns a function that generates numbers in a circular pattern between a specified range, for example, 3, 4, 5, 6, 3, 4, 5, 6, 3….

Exercise 9-3 – Partial Application
The following code shows a function featureScale that “rescales” a dataset so that all the values fall into a specified range. The scale function calls featureScale to normalize a dataset into the range 0..1.
let featureScale a b xMin xMax x =
    a + ((x - xMin) * (b - a)) / (xMax - xMin)
let scale (data : seq<float>) =
    let minX = data |> Seq.min
    let maxX = data |> Seq.max
    // let zeroOneScale = ...
    data
    |> Seq.map (fun x -> featureScale 0. 1. minX maxX x)
    // |> Seq.map zeroOneScale
// seq [0.0; 0.5; 1.0]
[100.; 150.; 200.]
|> scale
How would you amend the code so that the mapping operation at the end of the scale function did not use a lambda function? That is, so that it reads something like this:
|> Seq.map zeroOneScale

You can assume that the provided dataset is nonempty.

Exercise 9-4 – Function Composition
You have a list of functions, each of which takes a float argument and returns another float, like this:
let pipeline =
    [ fun x -> x * 2.
      fun x -> x * x
      fun x -> x - 99.9 ]

The list is nonempty but otherwise can have any length.

How would you write a function applyAll that can take such a list of functions and apply them all, taking the result of the first function and feeding it into the second, taking the result of that and feeding it into the third function, and so forth, until a final result is produced? Your function should be callable like this:
let applyAll (p : (float -> float) list) =
    // Replace this:
    raise <| System.NotImplementedException()
let r = 100. |> applyAll pipeline
// 39900.1
printfn "%f" r
Hints:
  • You can combine values in a nonempty list using List.reduce.

  • Remember that there is an F# operator that can combine (compose) two functions into one, providing that the output of the first is compatible with the input of the second.

Exercise Solutions

Exercise 9-1 – Functions as Arguments
It’s straightforward to define a multiply function and pass it into applyAndPrint :
let add a b = a + b
let multiply a b = a * b
let applyAndPrint f a b =
    let r = f a b
    printfn "%i" r
// "5"
applyAndPrint add 2 3
// "6"
applyAndPrint multiply 2 3
To define a subtract function without naming it, you can use the fun keyword in the call to applyAndPrint:
// "-1"
applyAndPrint (fun x y -> x - y) 2 3
Or you could just pass an operator straight in:
// "-1"
applyAndPrint (-) 2 3
Exercise 9-2 – Functions Returning Functions
You can achieve this using a similar pattern to Listing 9-12 but with a little if/then logic to calculate the next value and wrap it round when it passes the upper bound.
let rangeCounter first last =
    let mutable current = first
    fun () ->
        let this = current
        let next = current + 1
        current <-
            if next <= last then
                next
            else
                first
        this
// r1: 3 r2: 6
// r1: 4 r2: 7
// r1: 5 r2: 8
// r1: 6 r2: 9
// r1: 3 r2: 10
// r1: 4 r2: 11
// ...
// r1: 3 r2: 8
let r1 = rangeCounter 3 6
let r2 = rangeCounter 6 11
for _ in 0..20 do
    printfn "r1: %i r2: %i" (r1()) (r2())
Exercise 9-3 – Partial Application
You need to bind a value called something like zeroOneScale, which is a partial application of featureScale providing values for the a, b, xMin, and xMax parameters. The resulting function only has one parameter, x, and so can be used directly in a Seq.map operation .
let featureScale a b xMin xMax x =
    a + ((x - xMin) * (b - a)) / (xMax - xMin)
let scale (data : seq<float>) =
    let minX = data |> Seq.min
    let maxX = data |> Seq.max
    let zeroOneScale = featureScale 0. 1. minX maxX
    data
    |> Seq.map zeroOneScale
// seq [0.0; 0.5; 1.0]
[100.; 150.; 200.]
|> scale
Exercise 9-4 – Function Composition
This can be achieved using List.reduce (or Seq.reduce) and the >> (function composition) operator.
let pipeline =
    [ fun x -> x * 2.
      fun x -> x * x
      fun x -> x - 99.9 ]
let applyAll (p : (float -> float) list) =
    p |> List.reduce (>>)
let r = 100. |> applyAll pipeline
// 39900.1
printfn "%f" r

Since List.reduce is a partial function and raises an exception if the list is empty, the function pipeline list must contain at least one function.

If you want, you can omit the explicit parameter for applyAll, as the reduce operation will return a composed function, which itself expects a parameter.
        let applyAll =
            List.reduce (>>)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.34.105