3

I am trying to better understand functional Programming. The example below was provided, this example creates a predicate lambda. Then creates a static method to Count the number of items that pass the predicate conditions.

public Predicate<string> longWords = (x) => x.Length > 10;

public static int Count<T>(T[] array, Predicate<T> predicate, int counter)
    {
        for (int i = 0; i < array.Length; i++)
        {
            if (predicate(array[i]))
            {
                counter++;
            }
        }

        return counter;
    }

I wanted to make this solution follow the Referential Transparency principle which states that you can determine the result of applying that function only by looking at the values of its arguments.

Unfortunately longWords doesn't tell me what a long word really means, I need to pass in an argument to longWords telling it the length that makes a function a long word, ie not hardcoding '10'.

Predicates only take in one parameter, and making longWords require Predicate<Tuple<string,int>>, comes with its challenges.

Below is my attempt at coming to a solution, and the two error messages that resulted.

public class BaseFunctions
{
    public Predicate<Tuple<string, int>> longWords = (x) => x.Item1.Length > x.Item2;

    public static int Count<T>(T[] array, Predicate<T> predicate, int counter)
    {
        for (int i = 0; i < array.Length; i++)
        {
            /// error below: Cannot convert from 'T' to 'string'
            /// in the example provided predicate(array[i]) worked fine when the predicate had only string type
            if (predicate(new Tuple<string, int>(array[i], 10)))
            {
                counter++;
            }
        }
        return counter;
    }
}

and it's usage

 BaseFunctions b = new BaseFunctions();
 string[] text = ["This is a really long string", "Not 10"];
 /// also error below on BaseFunctions.Count 'The type arguments for method BaseFunctions.Count<T>(T[], Predicate<T>, int) cannot be inferred from the usage. Try specifying the arguments explicitly
 Console.WriteLine(BaseFunctions.Count(text, b.longWords, 0).ToString());
 Console.ReadLine();
christopher clark
  • 2,026
  • 5
  • 28
  • 47
  • instead of using `Predicate>`, it would be better to use `Func` then change `Predicate predicate` to `Func predicate` – Ousmane D. Mar 11 '18 at 17:34
  • See https://stackoverflow.com/questions/665494/why-funct-bool-instead-of-predicatet for why `Func` is preferred over `Predicate` – Ian Mercer Mar 11 '18 at 17:37
  • As an aside, in the ideal world, you wouldn't want to reinvent the wheel as we can achieve what you want with: `string[] text = { "This is a really long string", "Not 10" }; Console.WriteLine(text.Count(x => x.Length > 10));` Using the `Count` extension method. Although, in this specific case it's fine as you've mentioned that this is only for practise purposes. – Ousmane D. Mar 11 '18 at 18:10

1 Answers1

4

I'd do:

public Func<string, int, bool> longWords = (str, number) => str.Length > number;

instead of:

public Predicate<Tuple<string, int>> longWords = (x) => x.Item1.Length > x.Item2;

This is simply because I believe it's easier to work with a function that takes a string and an int then returns a bool as opposed to a predicate that takes a tuple of string and int and returns a bool. plus this version of the tuple type is cumbersome to work with. consider the new tuple types if you're using C#7 and above.

Then change the method signature from:

public static int Count<T>(T[] array, Predicate<T> predicate, int counter)

to:

public static int Count<T>(T[] array, Func<T, int, bool> predicate, int counter)

To accommodate the change of delegate types mentioned above.

Then the if condition needs to be changed from:

if (predicate(new Tuple<string, int>(array[i], 10)))

to:

if (predicate(array[i], 10))

To accommodate the change of delegate types mentioned above.

Also, note that in C# you'll need to define the array like this:

string[] text = { "This is a really long string", "Not 10" };

and not with the [ ] braces.

Now your usage code becomes:

BaseFunctions b = new BaseFunctions();
string[] text = { "This is a really long string", "Not 10" };

Console.WriteLine(BaseFunctions.Count(text, b.longWords, 0).ToString());
Console.ReadLine();
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • Beautiful answer. Yes I had a silly mistake with the string array declaration! – christopher clark Mar 11 '18 at 18:59
  • 1
    While this answer is a direct answer to your question I think it may be worthwhile to point out that the original solution *is* referentially transparent. The `longwords` function is one of the inputs to the Count function and, therefore, can be used to reason about the results of the function call. In fact, this is how all higher-ordered functions (HOFs) work. They take a function as a parameter and that function *specializes* the HOF. Here, `Count` is the HOF and `longwords` is used to determine what `Count` counts. – melston Mar 12 '18 at 16:50