wiesław kałkus: c# functional programming

38
WIESŁAW KAŁKUS Holte Software Poland Functional Programming Is it possible in C#?

Upload: analyticsconf

Post on 15-Apr-2017

279 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Wiesław Kałkus: C# functional programming

WIESŁAW KAŁKUS

Holte Software Poland

Functional ProgrammingIs it possible in C#?

Page 2: Wiesław Kałkus: C# functional programming

Today’s Agenda

C# Functional Programming

Is It Worth An Effort? Maybe It Is

Controlling ComplexityComposition Function Composition

Programming ExampleCopying Photo Files Implementation Variants

What Is Functional Programming?Theoretical Perspective C# Programmer Perspective

Page 3: Wiesław Kałkus: C# functional programming

What is Functional Programming?

Page 4: Wiesław Kałkus: C# functional programming

What Is Functional Programming?

Wikipedia

Functional Programming is a programming paradigm, a style of building the structure and elements of computer programs, that treats computations as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions.

Page 5: Wiesław Kałkus: C# functional programming

Programming Paradigms

• Focus on describing how a program operates• Programs described in terms of statements that change a program

state• Structural (procedures as main actors) and object-oriented (objects as

main actors) programming styles fall into imperative paradigm

Imperative

• Focus on describing what a program should accomplish without prescribing how to do it in terms of sequences of actions to be taken

• Programs expressed in terms of computation logic without describing its control flow

• Functional (mathematical function as main actor) programming style falls into declarative paradigm

Declarative

Page 6: Wiesław Kałkus: C# functional programming

Multi-paradigm programming languages

Type inference (C# & F#)Anonymous functions (C# & F#)Language Integrated Query (C# & F#)Currying (F#)Pattern matching (F#)Lazy evaluation (F#)Tail recursion (F#)Immutability (F#)

Control statements (C#)Classes (C#).NET framework (C# & F#)Do notation (F#)

Declarative

Imperative

Page 7: Wiesław Kałkus: C# functional programming

Object-Oriented vs. Functional Programming Style

Characteristic Object-oriented (imperative) approach Functional (declarative) approach

Programmer focus How to perform tasks (algorithms) and how to track changes in state.

What information is desired and what transformations are required.

State changes Important. Localized and tightly controlled.

Order of execution Important. Low importance.

Primary flow control Loops, conditionals, and function (method) calls. Function calls, including recursion.

Primary manipulation unit Instances of structures or classes. Functions as first-class objects and data collections.

Page 8: Wiesław Kałkus: C# functional programming

Programming Example

Page 9: Wiesław Kałkus: C# functional programming

Programming Example – problem statement

Copy photo files from the source folder (include subfolders) to the destination folder (create subfolders as necessary). While copying rename each file using pattern <base-name><sequence-number>. Sequence numbers should preserve the order of date taken for each photo. Additionally allow for each source photo file to create multiple copies of the file in the target folder (useful for extending time-lapse photography sequences).

Page 10: Wiesław Kałkus: C# functional programming

Programming Example – object-oriented approach

foreach (sourceFile in sourceFilesSortedByPhotoDateTaken){ try { CreateTargetFolderPathIfNecessary fileCopyCount = 0 while (fileCopyCount < PhotoFileCopyMultiplicationFactor) { MakeNewTargetFileName CopySourceFileToTheTargetFolderUnderTheNewName fileCopyCount += 1 } } catch { } }

Programmer focus on how to perform tasks (algorithms) and how to track changes in state.

Page 11: Wiesław Kałkus: C# functional programming

Programming Example – object-oriented approach

foreach (sourceFile in sourceFilesSortedByPhotoDateTaken){ try { CreateTargetFolderPathIfNecessary fileCopyCount = 0 while (fileCopyCount < PhotoFileCopyMultiplicationFactor) { MakeNewTargetFileName CopySourceFileToTheTargetFolderUnderTheNewName fileCopyCount += 1 } } catch { } }

var sourceFiles = FolderVisitor.Visit (sourceFolder);sourceFiles.Sort(Photo.ByDateTaken);foreach (var sourceFile in sourceFiles) { try { CreateTargetFolderPathIfNecessary fileCopyCount = 0 while (fileCopyCount < PhotoFileCopyMultiplicationFactor) { MakeNewTargetFileName CopySourceFileToTheTargetFolderUnderTheNewName fileCopyCount += 1 } } catch{ } }

public static class FolderVisitor { public static List<FileInfo> Visit(string rootFolder) { return DoVisitFolder(rootFolder); } private static List<FileInfo> DoVisitFolder(string folder) { var files = new List<FileInfo>(); var thisFolder = new DirectoryInfo(folder); files.Add(thisFolder.EnumerateFiles()); var subFolders = thisFolder.EnumerateFolders(); foreach (var folder in subFolders) { files.Add(DoVisitFolder(subfolder.FullName)); } return files; }}

public static class Photo { public static int ByDateTaken (FileInfo f1, FileInfo f2) { if (f1 == null && f2 == null) { return 0; } else if (f1 == null) { return -1; } else if (f2 == null) { return 1; } return Photo.DateTaken(f1).CompareTo(Photo.DateTaken(f1)); } private static DateTime DateTaken(FileInfo f) { DateTime dateTaken = DateTime.Now; try { using (var reader = ExifReader (f.FullName)) { reader.GetTagValue<DateTime>( ExifTags.DateTimeDigitized, our dateTaken); } } catch (IOException) {} return dateTaken; }}

Add files from the current folder.Recursively add files from subfolders.

Open source Exif Library – read EXIF tag from the photo file.

Page 12: Wiesław Kałkus: C# functional programming

Programming Example – object-oriented approach

var sourceFiles = FolderVisitor.Visit (sourceFolder);sourceFiles.Sort(Photo.ByDateTaken);foreach (var sourceFile in sourceFiles) { try { CreateTargetFolderPathIfNecessary fileCopyCount = 0 while (fileCopyCount < PhotoFileCopyMultiplicationFactor) { MakeNewTargetFileName CopySourceFileToTheTargetFolderUnderTheNewName fileCopyCount += 1 } } catch{ } }

public static class FolderHelper { public static void MaterializeFolder(string root string path) { var realtivePath = path.SubString(root.Length); if (string.IsNullOrWhitespace(relativePath)) { return; } var segments = relativePath.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); var workingPath = root; foreach (var segment in segments) { workingPath = Path.Combine(workingPath, segment); if (!Directory.Exists(workingPath)) { Directory.CreateDirectory(workingPath); } } }}

Create missing folders.

Page 13: Wiesław Kałkus: C# functional programming

Programming Example – object-oriented approach

var sourceFiles = FolderVisitor.Visit (sourceFolder);sourceFiles.Sort(Photo.ByDateTaken);foreach (var sourceFile in sourceFiles) { try { FolderHelper.MaterializeFolder(targetFolder, sourceFile.Directory.FullName); fileCopyCount = 0 while (fileCopyCount < PhotoFileCopyMultiplicationFactor) { MakeNewTargetFileName CopySourceFileToTheTargetFolderUnderTheNewName fileCopyCount += 1 } } catch{ } }

public static class FolderHelper { public static void CopyFile(string targetRoot, FileInfo sourceFile, string fileNameBase, int sequenceNo) { var newFileName = string.Format(„{0}{1}{2}”, fileNameBase, sequenceNo, Path.GetExtension(sourceFile.Name)); var realtivePath = sourceFile .Directory.FullName .SubString(targetRoot.Length); var targetPath = Path.Combine(targetRoot, relativePath, newFileName); sourceFile.CopyTo(targetPath); }}

Make target file nameExtract target relative path.Create target absolute path.Copy source file to the target folder.

Page 14: Wiesław Kałkus: C# functional programming

Programming Example – object-oriented approach

var sourceFiles = FolderVisitor.Visit (sourceFolder);sourceFiles.Sort(Photo.ByDateTaken);foreach (var sourceFile in sourceFiles) { try { FolderHelper.MaterializeFolder(targetFolder, sourceFile.Directory.FullName); fileCopyCount = 0 while (fileCopyCount < PhotoFileCopyMultiplicationFactor) { FolderHelper.CopyFile(targetFolder, sourceFile, newFileNameBase, sequenceNo); fileCopyCount += 1; sequenceNo += 1; } } catch{ } }

Make a list of source files.Sort source files by the photo date taken.

Ensure target folder.

Make required file copies.

Update copying process state.

Page 15: Wiesław Kałkus: C# functional programming

Programming Example – functional approach

• Make the list of source files• Sort the source file list by picture date taken• Make the list of pairs - source file and target

folder path• Make the list of tuples – boolean flag

indicating sucessful file copy, source file, copy operation status (destination path or error message)

Programmer focus on what information is desired and what transformations are required.

Page 16: Wiesław Kałkus: C# functional programming

Programming Example – functional approach

• Sort the source file list by picture date taken• Make the list of pairs - source file and target

folder path• Make the list of tuples – boolean flag

indicating sucessful file copy, source file, copy operation status (destination path or error message)

• Make the list of source filesvar sourceFiles = FolderVisitor.Visit (sourceFolder);

type public FileVisitor() = static member private folderFiles folderPath filter = let emptyDirectory = List.Empty.AsEnumerable() let dirInfo = new DirectoryInfo(folderPath) try dirInfo.EnumerateFiles(filter) with | :? ArgumentException -> emptyDirectory | :? DirectoryNotFoundException -> emptyDirectory | :? SecurityException -> emptyDirectory | :? UnauthorizedAccessException -> emptyDirectory static member private subFolders folderPath = let dirInfo = new DirectoryInfo(folderPath) let noSubFolders = List.Empty.AsEnumerable() try dirInfo.EnumerateDirectories() with | :? DirectoryNotFoundException -> noSubFolders | :? SecurityException -> noSubFolders | :? UnauthorizedAccessException -> noSubFolders static member public Visit rootFolder fileNameFilter = seq { yield! FileVisitor.folderFiles rootFolder fileNameFilter for subFolder in FileVisitor.subFolders rootFolder do yield! FileVisitor.Visit subFolder.FullName fileNameFilter }

A sequence is a logical series of elements all of one type. Sequences are particularly useful when you have a large, ordered collection of data but do not necessarily expect to use all the elements. Individual sequence elements are computed only as required, so a sequence can provide better performance than a list in situations in which not all the elements are used.

var sourceFiles = FolderVisitor.Visit (sourceFolder);

IEnumerable<FileInfo>

Page 17: Wiesław Kałkus: C# functional programming

Programming Example – functional approach

• Make the list of pairs - source file and target folder path

• Make the list of tuples – boolean flag indicating sucessful file copy, source file, copy operation status (destination path or error message)

var sourceFiles = FolderVisitor.Visit (sourceFolder);• Sort the source file list by picture date takenvar sortedFiles = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken);

IEnumerable<FileInfo>

public static class Photo { public static DateTime DateTaken(FileInfo f) { DateTime dateTaken = DateTime.Now; try { using (var reader = ExifReader (f.FullName)) { reader.GetTagValue<DateTime>( ExifTags.DateTimeDigitized, our dateTaken); } } catch (IOException) {} return dateTaken; }}

Page 18: Wiesław Kałkus: C# functional programming

Programming Example – functional approach

• Make the list of pairs - source file and target folder path

var sortedFiles = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken);

• Make the list of tuples – boolean flag indicating sucessful file copy, source file, copy operation status (destination path or error message)

var copier =new FileCopier(targetFolder, baseName, seqStart, noCopies);var toBeCopiedFiles = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken) .SelectMany(copier.MakeTargetPath);

public class FileCopier { private readonly string _targetFolder; private readonly string _baseName; private readonly int _noOfCopies; private int _sequenceNo; public FileCopier(string targetFolder, string baseName, int seqStart, int noOfCopies) { _targetFolder = targetFolder; _baseName = baseName; _noOfCopies = noOfCopies; _sequenceNo = seqStart; } public IEnumerable<Tuple<FileInfo, string>> MakeTargetPath(FileInfo file) { var result = new List<Tuple<FileInfo, string>(); for (var i = 0; i < _noOfCopies; ++i) { var newFileName = string.Format(„{0}{1}{2}”, _baseName, _sequenceNo, Path.GetExtension(file.Name)); var realtivePath = file.Directory.FullName.SubString(_targetFolder.Length); var targetPath = Path.Combine(_targetFolder, relativePath, newFileName); result.Add(new Tuple<FileInfo, string>(file, targetPath)); _sequenceNo += 1; } return result; }}

var copier =new FileCopier(targetFolder, baseName, seqStart, noCopies);var toBeCopiedFiles = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken) .SelectMany(copier.MakeTargetPath);

IEnumerable<Tuple<FileInfo, string>>

Projects each element of a sequence to an IEnumerable<T> and flattens the resulting sequences into one sequence.

public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)

Page 19: Wiesław Kałkus: C# functional programming

Programming Example – functional approach

• Make the list of tuples – boolean flag indicating sucessful file copy, source file, copy operation status (destination path or error message)

var copier =new FileCopier(targetFolder, baseName, seqStart, noCopies);var toBeCopiedFiles = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken) .SelectMany(copier.MakeTargetPath);

var copier =new FileCopier(targetFolder, baseName, seqStart, noCopies);var fileCopyStatus = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken) .SelectMany(copier.MakeTargetPath) .SelectMany(copier.CopyFile);

public class FileCopier { private readonly string _targetFolder; private readonly string _baseName; private readonly int _noOfCopies; private int _sequenceNo; public FileCopier(string targetFolder, string baseName, int seqStart, int noOfCopies) { _targetFolder = targetFolder; _baseName = baseName; _noOfCopies = noOfCopies; _sequenceNo = seqStart; } public IEnumerable<Tuple<bool, FileInfo, string>> CopyFile(Tuple<FileInfo, string> file) { var result = new List<Tuple<bool, FileInfo, string>(); try { MaterializeFolder(_targetFolder, file.Item2); file.Item1.CopyTo(file.Item2); result.Add(new Tuple<bool, FileInfo, string>(true, file.Item1, file.Item2); } catch (Exception ex) { result.Add(new Tuple<bool, FileInfo, string>(false, file.Item1, ex.Message); } return result; }}

IEnumerable<Tuple<bool, FileInfo, string>>

Page 20: Wiesław Kałkus: C# functional programming

Programming Example – functional approach

var copier =new FileCopier(targetFolder, baseName, seqStart, noCopies);var fileCopyStatus = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken) .SelectMany(copier.MakeTargetPath) .SelectMany(copier.CopyFile);var fileCopyObserver = new FileCopyObserver();fileCopyStatus.Subscribe (fileCopyObserver);Console.WriteLine(fileCopyObserver.Summary());

public class FileCopyObserver : IObserver<Tuple<bool, FileInfo, string> { private int _copiedFileCount; private int _errorCount;

public string Summary() { return string.Format(„Attempt to copy {0} files; {1} successful; {2} errors.”, _copiedFileCount + _errorCount, _copiedFileCount, _errorCount); } public void OnCompleted() { } public void OnError (Exception error) {} public void OnNext(Tuple<bool, FileInfo, string> copyStatus) { if (copyStatus.Item1) { _copiedFileCoput += 1; } else { _errorCount += 1; } }}

var fileCopyObserver = new FileCopyObserver();fileCopyStatus.Subscribe (fileCopyObserver);Console.WriteLine(fileCopyObserver.Summary());

System.Reactive.Linq

Page 21: Wiesław Kałkus: C# functional programming

Programming Example – Object-Oriented vs Functional approach

var sourceFiles = FolderVisitor.Visit (sourceFolder);sourceFiles.Sort(Photo.ByDateTaken);foreach (var sourceFile in sourceFiles) { try { FolderHelper.MaterializeFolder(targetFolder, sourceFile.Directory.FullName); fileCopyCount = 0 while (fileCopyCount < noOfCopies) { FolderHelper.CopyFile(targetFolder, sourceFile, newFileNameBase, sequenceNo); fileCopyCount += 1; sequenceNo += 1; } } catch{ } }

var copier = new FileCopier(targetFolder, baseName, seqStart, noCopies);var fileCopyStatus = FolderVisitor.Visit (sourceFolder) .OrderBy(Photo.DateTaken) .SelectMany(copier.MakeTargetPath) .SelectMany(copier.CopyFile);var fileCopyObserver = new FileCopyObserver();fileCopyStatus.Subscribe (fileCopyObserver);Console.WriteLine(fileCopyObserver.Summary());Loops

State

Exceptions

Sequences

Functions

Observations

Control statements, state changesFunction call compositionControlling Complexity By

Page 22: Wiesław Kałkus: C# functional programming

Controlling Complexity

Page 23: Wiesław Kałkus: C# functional programming

Composition

Composition is the key to controlling complexity in software.

Expert programmers control the complexity of their designs by combining primitive elements to form compound objects, they abstract compound objects to form higher-level building blocks.

One form of composition, function composition, describes the dependencies between function calls.

Page 24: Wiesław Kałkus: C# functional programming

Function Composition

Function composition takes two functions and passes the result of the second function as the argument of the first function – effectively creating one function taking argument of the second function and returning result of the first function.

public static Func<T, V> Compose<T, U, V>(this Func<U, V> f, Func<T, U> g) {

return x => f(g(x));

}

Anonymous function (lambda) calling second function (g) with argument x and

passing its result as first function (f) argument, and returning the result of the first function.

Page 25: Wiesław Kałkus: C# functional programming

Function Composition

var r = f(g(x))

•Explicit dependency between f and g

var r = f.Compose(g)(x)

•Abstracted dependency between f and g

Abstract compound objects to form higher-level building blocks

Page 26: Wiesław Kałkus: C# functional programming

Function Composition Laws

•Identity.Compose(f) = fLeft identity

•f.Compose(Identity) = fRight identity

•f.Compose(g.Compose(h) = (f.Compose(g)).Compose(h)

Associative

public static T Identity<T>(this T value){ return value;}

Page 27: Wiesław Kałkus: C# functional programming

Function Composition – sometimes values are not enough

Generic (constructed) types package (amplify) values (integers, strings, objects of class T)

The type IEnumerable<T> represents a lazily computed list of values of type T

The type IEnumerable<T> packages (amplifies) the type T

Page 28: Wiesław Kałkus: C# functional programming

Function Composition with IEnumerable<T>

public static Func<T, IEnumerable<V>> Compose<T, U, V>(

this Func<U, IEnumerable<V>> f,

Func<T, IEnumerable<U>> g)

{

return x => f(g(x));

}

Error – g(x) returns IEnumerable<U> while f takes U

We need to bind the result of g(x) with the function f. Binding will

iterate over a list returned by g(x) and pass individual list items to

the function f. The result of binding g(x) to the function f is a list of

results returned by the function f.

public static Func<T, IEnumerable<V>> Compose<T, U, V>(

this Func<U, IEnumerable<V>> f,

Func<T, IEnumerable<U>> g)

{

return x => g(x).Bind(f);

}

public static IEnumerable<V> Bind ( this IEnumerable<U> m, Func<U, IEnumerable<V>> f)

public static IEnumerable<V> Bind (this IEnumerable<U> items, Func<U, IEnumerable<V>> f){ var result = new List<V>(); foreach (var item in items) { result.Add(f(item)); } return result;}

Page 29: Wiesław Kałkus: C# functional programming

Function Composition with IEnumerable<T>

• Our Bind function allows the function f to use IEnumerable returned by the function g• We need some function converting a type T to IEnumverable<T>

public static IEnumerable<T> Unit (this T value)

• Together the amplified type IEnumerable<T>, the function Bind, and the function Unit enable function composition with amplified values

A type

A Unit functi

on

A Bind functi

on

Monad

Monads enable function composition with amplified values

Page 30: Wiesław Kałkus: C# functional programming

Monads Satisfy Function Composition Laws

•Unit(v).Bind(f) = f(v)Left identity

•m.Bind(Unit) = mRight identity

• m.Bind(x => k(x).Bind(y => h(y)) = m.Bind(x =>k(x)).Bind(y => h(y))Associative

Page 31: Wiesław Kałkus: C# functional programming

Creating a Monad

To define a particular monad, the writer supplies the triple (a Type, a Unit function, and a Bind function), thereby specyfying the mechanics of the aplified values

Page 32: Wiesław Kałkus: C# functional programming

IEnumerable<T> Monad

IEnumerable<T> Monad

SelectMany function

AsEnumerable function

Type T

Page 33: Wiesław Kałkus: C# functional programming

C# Functional Programming

Page 34: Wiesław Kałkus: C# functional programming

C# Functional Programming

• Is it worth an effort?• Maybe it is• The Maybe monad is an example of a monadic

container type wrapping a value. The container can either have a value or have missing value. The Maybe monad is a better nullable type.

Page 35: Wiesław Kałkus: C# functional programming

C# Functional Programming – Maybe monad

public class Maybe<T> { public readonly static Maybe<T> Nothing = new Maybe<T>();

public bool IsNothing { get; private set; }

public T Just { get; private set; }

public Maybe() { IsNothing = true; Just = default(T); }

public Maybe(T value) { IsNothing = false; Just = value; }

public new string ToString() { if (IsNothing) { return "Nothing"; } return Just.ToString(); } }

Nothing represents all instances lacking a value.

Just the value if we have one.

We have the type. Now we need the Unit and Bind functions to have the Maybe monad.The Unit function which we call AsMaybe, wraps a value.The Bind function which we call SelectMany, takes a Maybe instance and if there is a value then it applies the delegate to the contained value. Otherwise, it returns Nothing.

Page 36: Wiesław Kałkus: C# functional programming

Maybe monad – Unit and Bind functions

public static class MaybeExtensions {

public static Maybe<T> AsMaybe<T> (this T value){ return new Maybe<T>(value); }

public static Maybe<U> SelectMany<T, U>(this Maybe<T> m, Func<T, Maybe<U>> doSemthingWith) { if (m == null) { return Maybe<U>.Nothing; } if (m.IsNothing) { return Maybe<U>.Nothing; } try { return doSomethingWith(m.Just); } catch (Exception) { return Maybe<U>.Nothing; } }}

The Unit function which we call AsMaybe, wraps a value.The Bind function which we call SelectMany, takes a Maybe instance and if there is a value then it applies the delegate to the contained value. Otherwise, it returns Nothing.

The delegate takes the un-wrapped value of the type T and returns wrapped (amplified) value of the type U – we can bind another delegate to ther result Maybe<U>.

Page 37: Wiesław Kałkus: C# functional programming

Maybe monad – usage examples

var maybeStringArray = (new string[] {„Ala”, „ma”, „kota”}).AsMaybe();

var maybeResult = maybeStringArray .SelectMany(x => x.FirstOrDefault(s => s.Equals(„Ala”)).AsMaybe()) .SelectMany(s => s.ToUpperInvariant().AsMaybe());Console.WriteLine(maybeResult.ToString());

maybeResult = maybeStringArray .SelectMany(x => x.FirstOrDefault(s => s.Equals(„Ola”)).AsMaybe()) .SelectMany(s => s.ToUpperInvariant().AsMaybe());Console.WriteLine(maybeResult.ToString());

Writes ALA.

Writes Nothing.

Maybe<string>

Page 38: Wiesław Kałkus: C# functional programming

Questions