© Kit Eason 2018
Kit EasonStylish F#https://doi.org/10.1007/978-1-4842-4000-7_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 definition, 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 is 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-Object-Oriented 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 (non-tupled) 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 """
        let demo() =
            // ~~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. (Incidentally sprintf sometimes isn’t the fastest way to do this, but I’m doing it that way here for simplicity.) 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 """
        let demo() =
            // ~~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 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 lines are repeated in Listing 9-6).
        member __.Item
            with get(i, j) =
                _items.[i % leni, j % lenj]
            // (i, j) are tupled, value is curried
            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.

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 non-function 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). Secondly (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 (we’ll call it newFunction) that takes another function (we’ll call it paramFunction), you have two options:
  • 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.

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, returns the next integer.
        let counter start =
            let mutable current = start
            fun () ->
                let this = current
                current <- current + 1
                this
        let demo() =
            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, then returning a function that returns the current value and increments the state. This is a nice way of using, but concealing, a 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, 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, 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
        // A3-52-31-D2-90-E6-6F-45-1C-3F-F2-9B-7F-58-34-44-
        let demo() =
            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 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 non-directional or neutral ones. For example this text:

“Bob said ‘Hello,’” said Alice.

… would be translated to this:

"Bob said 'Hello,'" said Alice.

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 Typographic =
            let openSingle = "'
            let openDouble = '"'
            let closeSingle = "'
            let closeDouble = '"'
        module Neutral =
            let single = '"
            let double = '"'
        /// Translate any typographic single quotes to neutral ones.
        let fixSingleQuotes (s : string) =
            s
             .Replace(Typographic.openSingle, Neutral.single)
             .Replace(Typographic.closeSingle, Neutral.single)
        /// Translate any typographic double quotes to neutral ones.
        let fixDoubleQuotes (s : string) =
            s
             .Replace(Typographic.openDouble, Neutral.double)
             .Replace(Typographic.closeDouble, Neutral.double)
        /// Translate any typographic quotes to neutral ones.
        let fixTypographicQuotes (s : string) =
            s
            |> fixSingleQuotes
            |> fixDoubleQuotes
Listing 9-14

First cut of removing typographic quotes

There’s nothing inherently wrong with the way fixTypographicQuotes 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 fixSingleQuotes returns a string, and fixDoubleQuotes 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).
        // Build a 'fixQuotes' function using composition:
        let fixTypographicQuotes (s : string) =
            let fixQuotes = fixSingleQuotes >> fixDoubleQuotes
            s |> fixQuotes
Listing 9-15

Basic function composition

In Listing 9-15 we define a function called fixQuotes, which is a combination of fixSingleQuotes and fixDoubleQuotes. When fixQuotes is called, fixSingleQuotes will be called first (using the input to fixQuotes), and its output will be passed to fixDoubleQuotes. Whatever fixDoubleQuotes returns will be returned as the result of fixQuotes. Having defined fixQuotes, 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).
        // Remove the explicit binding of 'fix':
        let fixTypographicQuotes (s : string) =
            s |> (fixSingleQuotes >> fixDoubleQuotes)
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 fixSingleQuotes and fixDoubleQuotes. If we simply delete it, we still end up with a function fixTypographicQuotes that takes a string and returns a string (Listing 9-17).
        // Remove the explicit parameter:
        let fixTypographicQuotes =
            fixSingleQuotes >> fixDoubleQuotes
Listing 9-17

Elminating 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 fixQuotes, 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 each other 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 not excessive 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 demo() =
            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 code below shows a function featureScale that “normalizes” 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]
        let demo() =
            [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 non-empty.

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 non-empty 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 demo() =
            let x = 100. |> applyAll pipeline
            // 39900.1
            printfn "%f" x
Hints:
  • Remember you can combine values in a non-empty list using List.reduce.

  • Remember that there is an F# operator, which 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:
        applyAndPrint (fun x y -> x - y) 2 3
Or you could just pass an operator straight in:
        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 demo() =
            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]
        let demo() =
            [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 demo() =
            let x = 100. |> applyAll pipeline
            // 39900.1
            printfn "%f" x

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
13.59.11.95