What Does Functional Programming Mean?
Wednesday, March 31st, 2010Here are some slides to a short talk I gave a few weeks ago.
Hoping to get less ridiculous (i.e. more insightful) blog comments…
Here are some slides to a short talk I gave a few weeks ago.
Hoping to get less ridiculous (i.e. more insightful) blog comments…
Scalaz now has an IRC channel.
irc://irc.freenode.net/#scalaz
A result of discussion in the #scala IRC channel.
A colleague has asked me to convert this to Haskell, which I have done below.
trait Monad answers the question “What does monad mean?”object Monad answers the question “What are some examples?”object MonadicFunctions answers the question “What are the practical implications?”If there are any questions or you get stuck, the #scala or #scalaz channel on Freenode could help out.
// 1. Start here. Observe this trait trait Monad[M[_]] { def flatMap[A, B](a: M[A], f: A => M[B]): M[B] def unital[A](a: A): M[A] } // A simple data type, which turns out to satisfy the above trait case class Inter[A](f: Int => A) // So does this. case class Identity[A](a: A) // Monad implementations object Monad { // 2. Replace error("todo") with an implementation def ListMonad: Monad[List] = error("todo") // 3. Replace error("todo") with an implementation def OptionMonad: Monad[Option] = error("todo") // 4. Replace error("todo") with an implementation def InterMonad: Monad[Inter] = error("todo") // 5. Replace error("todo") with an implementation def IdentityMonad: Monad[Identity] = error("todo") } object MonadicFunctions { // 6. Replace error("todo") with an implementation def sequence[M[_], A](as: List[M[A]], m: Monad[M]): M[List[A]] = error("todo") // 7. Replace error("todo") with an implementation def fmap[M[_], A, B](a: M[A], f: A => B, m: Monad[M]): M[B] = error("todo") // 8. Replace error("todo") with an implementation def flatten[M[_], A](a: M[M[A]], m: Monad[M]): M[A] = error("todo") // 9. Replace error("todo") with an implementation def apply[M[_], A, B](f: M[A => B], a: M[A], m: Monad[M]): M[B] = error("todo") // 10. Replace error("todo") with an implementation def filterM[M[_], A](f: A => M[Boolean], as: List[A] , m: Monad[M]): M[List[A]] = error("todo") // 11. Replace error("todo") with an implementation def replicateM[M[_], A](n: Int, a: M[A], m: Monad[M]): M[List[A]] = error("todo: flatMap n times to produce a list") // 12. Replace error("todo") with an implementation def lift2[M[_], A, B, C](f: (A, B) => C, a: M[A], b: M[B] , m: Monad[M]): M[C] = error("todo") // lift3, lift4, etc. Interesting question: Can we have liftN? }
Haskell
data Monad' answers the question “What does monad mean?”*** Monad implementations *** answers the question “What are some examples?”*** Monadic Functions *** answers the question “What are the practical implications?”{-# LANGUAGE RankNTypes #-} -- 1. Start here. Observe this data type data Monad' m = Monad' { unital :: forall a. a -> m a, flatMap :: forall a b. m a -> (a -> m b) -> m b } -- A simple data type, which turns out to satisfy the above trait newtype Inter a = Inter { f :: Int -> a } -- So does this. newtype Identity a = Identity { a :: a } deriving Show -- *** Monad implementations *** -- 2. Replace error "todo" with an implementation listMonad :: Monad' [] listMonad = error "todo" -- 3. Replace error "todo" with an implementation maybeMonad :: Monad' Maybe maybeMonad = error "todo" -- 4. Replace error "todo" with an implementation interMonad :: Monad' Inter interMonad = error "todo" -- 5. Replace error "todo" with an implementation identityMonad :: Monad' Identity identityMonad = error "todo" -- *** Monadic functions *** -- 6. Replace error "todo" with an implementation sequence' :: [m a] -> Monad' m -> m [a] sequence' = error "todo" -- 7. Replace error "todo" with an implementation fmap' :: m a -> (a -> b) -> Monad' m -> m b fmap' = error "todo" -- 8. Replace error "todo" with an implementation flatten :: m (m a) -> Monad' m -> m a flatten = error "todo" -- 9. Replace error "todo" with an implementation apply :: m (a -> b) -> m a -> Monad' m -> m b apply = error "todo" -- 10. Replace error "todo" with an implementation filterM' :: (a -> m Bool) -> [a] -> Monad' m -> m [a] filterM' = error "todo" -- 11. Replace error "todo" with an implementation replicateM' :: Int -> m a -> Monad' m -> m [a] replicateM' = error "todo: flatMap n times to produce a list" -- 12. Replace error "todo" with an implementation lift2 :: (a -> b -> c) -> m a -> m b -> Monad' m -> m c lift2 = error "todo" -- lift3, lift4, etc. Interesting question: Can we have liftN?
Because there is no such thing as a big application. There is only bigger or smaller. This is a central tenet of functional programming. “Big application” is a euphemism for “My idea of programming does not scale beyond this point.” You don’t realise how much information you give away when you ask this question.
So can we can stop with the piffle and get on with the interesting and important stuff? Ta.
I used to work for IBM on the Java implementation. I learned the language inside-out. I did all those Sun certifications and other spanky things. I wanted to understand what a lot of people alleged was so special. I didn’t ever find it.
I have found that few people can tell me the answer to this question. If you don’t know the answer, don’t fret; it’s not the important part.
method(s1.charAt(i), s2.charAt(j));
Assuming
s1ands2are both of the typejava.lang.String, then which call tocharAtwill occur first?
Many people would correctly guess at the left-most one. This is correct and is mandated by the specification.
However, on introspection, the reason nobody really knows is because it doesn’t matter. If the specification implementation had a bug and executed the right-most call first, then we, the programmer, would never even know. We gloss over this every day when we read Java code.
However, does this hold for all functions? What if it was something other than charAt? No, unfortunately, this only holds true for a specific set of functions. The charAt function is referentially transparent. For any given String and any given int then charAt will consistently return the same char. This is true for other referentially transparent functions too, but not any arbitrary function. Imagine if charAt did a database call or something like that!
Let us suppose now a new language feature that enforced the referential transparency of our functions. If it is referentially transparent, the compiler makes sure of it. Now imagine a new language where every function was referentially transparent.
All of a sudden, in the blink of an imagined hypothetical, the explicit order of invocation is no longer important. Just like that, poof, gone.
Welcome to the beginning of an understanding of the essence of functional programming.
Have fun! ![]()
I have found that many classes are created for the specific purpose of a
sequencefunction in the((->) t)monad, particularly in Java, C# and Python. Create a class, with a constructor that takes an argument (t) so that one may call many of its methods, each of which has access to t.
sequence :: [m a] -> m [a] sequence* :: [t -> a] -> t -> [a]
If we consider the type of t which we shall call Swizzle and the created class we shall call SwizzleManager then
// A constructor accepting a Swizzle SwizzleManager m = new SwizzleManager(t); // One of the following usually follow: // A list constructed by the results of executing several methods on 'm' List<R> = { m.a(x), m.b(y), m.c(z) } // A loop over a list of the results of executing several methods on 'm' for(R r : { m.a(x), m.b(y), m.c(z) }) { use(r); } // A list of effects to execute using 'm' m.a(x); m.b(y); m.c(z);
In a previous post, I mentioned a small library for validating using an applicative functor pattern with a semigroup for accumulating errors using Haskell, Scala and Java programming languages. In this post, I will give a set up for an example usage, but not necessarily a complete example. This is left as an exercise and may be elaborated on in a future post.
I will start with Java. First we must decide how we are going to accumulate errors. I have decided to store them in a LinkedList. In more practical languages with a better collections library, we would probably use something else. We will use something else for Scala and Haskell (later!), or you could use the Functional Java extension. In the meantime, we will use java.util.LinkedList. We start by writing its Semigroup implementation:
public static <A> Semigroup<LinkedList<A>> LinkedListSemigroup() { return new Semigroup<LinkedList<A>>() { public LinkedList<A> append(final LinkedList<A> a1, final LinkedList<A> a2) { final LinkedList<A> r = new LinkedList<A>(a1); r.addAll(a2); return r; } }; }
This is very straight-forward. In the previous example, I mentioned a class Person that is made of an age and a name. The age is an integer between 0 and 130 while the name is any string that starts with an upper-case character. Let’s write these data types:
// int wrapper between 0 and 130 class Age { private final int i; private Age(final int i) { this.i = i; } public int value() { return i; } public static Age age(final int i) { if(i <= 0 || i >= 130) throw new Error("out of range"); else return new Age(i); } }
And here is Name:
// String wrapper starts with upper-case class Name { private final String s; private Name(final String s) { this.s = s; } public String value() { return s; } public static Name name(final String s) { if(s.isEmpty() || !Character.isUpperCase(s.charAt(0))) throw new java.lang.Error(); else return new Name(s); } }
I won’t bother writing the Person data type, but it is sufficient to say it will have an Age field and a Name field.
Now for validation. Suppose we have two string values; one each for age and name. We would like to check these for validation and return either one or more error messages (String) or a Person. More succinctly, we want a function with the type:
String -> String -> Validation<LinkedList<String>, Person>>
How are we going to achieve this function? First we must say how to create an Age from a String or an error message if we cannot:
String -> Validation<LinkedList<String>, Age>>
and same for Name
String -> Validation<LinkedList<String, Name>>
We also need a function to create a Person from an Age and a Name
Age -> Name -> Person
This is simple. It’s the Person constructor.
So to sum up, we need to write a function with this type:
Arguments:
F<String, Validation<LinkedList<String>, Age>>>
F<String, Validation<LinkedList<String>, Name>>>
F<Age, F<Name, Person>>
Semigroup<LinkedList<String>>
Return type:
F<String, F<String, <Validation<LinkedList<String>, Person>>>>
Of course such a function is likely to be polymorphic, rather than specifying concrete types. I recommend starting by writing a function with this type:
Arguments:
F<T, F<U, V>>
F<A, Validation<E, T>>
F<B, Validation<E, U>>
Return type:
F<A, F<B, Validation<E, V>>
This function can be written by using the constructs mentioned in the previous post. I’m out of breath. Java is incredibly laborious.
Scala and Haskell for another time!
Sanjiv is an ex-colleague of mine who recently gave an excellent presentation at Brisbane Functional Programming Group. His presentation was on the use of the disjoint union algebraic data type for representing error handling or “validation”. This data type is often called Either. Sanjiv used the Scala version for his presentation.
Here I am going to write a data type that is just like (isomorphic to) Either called Validation. There will be two constructors for failure and success. I will be using Haskell, Scala and Java. I am going to present a function that is mentioned in an excellent paper called Applicative Programming with Effects.
There is a slight difference in my implementation to the function in this paper. The one mentioned in the paper uses a Monoid constraint, while I am using the more general Semigroup. This allows data types that are semigroups but not monoids (all monoids are semigroups). In particular, a non-empty list is a semigroup but not a monoid.
This constraint on the failing value allows the user to automatically accumulate errors as they chain through the Validation values, for example, by keeping them in a non-empty list. I am not going to give example usages of this code in this post because I think it is a great exercise for others and I do not want to spoil it. It is sufficient to say that any usage that type-checks is likely to be a useful example. I may give examples in the future if there is some confusion that needs clarification.
The function can accumulate Validation values using the applicative programming pattern. If all given are successful, you’ll be left with a function that takes all the successful values to a new value, which will be returned as a success. If at least one is not successful, the applicative pattern will begin accumulating with that error value and any future values.
I recommend starting with a simple example such as a Person type with an age :: Int and name :: String with imposed validation rules such as “age must be above 0 and less than 130″ and “name must start with a capital letter”. The applied function at the end should be ValidatedInt -> ValidatedString -> Person.
Like I said, if there is any confusion, I’ll expand later. Put the questions in the comments. Enjoy!
Here is a Haskell implementation of this:
data Validation e a = Fail e | Success a deriving (Eq, Show) -- Associative, binary operation (Monoid without identity) class Semigroup a where append :: a -> a -> a (<+>) :: Validation e a -> (a -> b) -> Validation e b Fail e <+> _ = Fail e Success a <+> f = Success (f a) (<*>) :: (Semigroup e) => Validation e a -> Validation e (a -> b) -> Validation e b Fail e1 <*> Fail e2 = Fail (e1 `append` e2) Fail e1 <*> Success _ = Fail e1 Success _ <*> Fail e2 = Fail e2 Success a <*> Success f = Success (f a)
And Scala:
sealed trait Validation[E, A] { def <+>[B](f: A => B): Validation[E, B] = this match { case Fail(e) => Fail(e) case Success(a) => Success(f(a)) } def <*>[B](f: Validation[E, A => B])(implicit s: Semigroup[E]) : Validation[E, B] = this match { case Fail(e1) => f match { case Fail(e2) => Fail(s append (e1, e2)) case Success(_) => Fail(e1) } case Success(a) => f match { case Fail(e2) => Fail(e2) case Success(f) => Success(f(a)) } } } case class Fail[E, A](e: E) extends Validation[E, A] case class Success[E, A](a: A) extends Validation[E, A] // Associative binary operation (Monoid without identity) case class Semigroup[A](append: (A, A) => A)
And now, for the allegedly practical Java. Hold your breath:
// Java pre-amble, sigh. interface F<X, Y> { Y apply(X x); } // Associative binary operation (Monoid without identity) interface Semigroup<A> { A append(A a1, A a2); } abstract class Validation<E, A> { // No algebraic data types in Java. Use a Church encoding (catamorphism). public abstract <X> X fold(F<E, X> fail, F<A, X> success); // Construction for fail public static <E, A> Validation<E, A> fail(final E e) { return new Validation<E, A>() { public <X> X fold(final F<E, X> fail, final F<A, X> success) { return fail.apply(e); } }; } // Construction for success public static <E, A> Validation<E, A> success(final A a) { return new Validation<E, A>() { public <X> X fold(final F<E, X> fail, final F<A, X> success) { return success.apply(a); } }; } // <+> public <B> Validation<E, B> map(final F<A, B> f) { return new Validation<E, B>() { public <X> X fold(final F<E, X> fail, final F<B, X> success) { return Validation.this.fold(fail, new F<A, X>() { public X apply(final A a) { return success.apply(f.apply(a)); } }); } }; } // <*> public <B> Validation<E, B> applicativate( final Validation<E, F<A, B>> f, final Semigroup<E> s /* no implicits or type-classes in Java */) { return Validation.this.fold(new F<E, Validation<E, B>>() { public Validation<E, B> apply(final E e1) { return f.fold(new F<E, Validation<E, B>>() { public Validation<E, B> apply(final E e2) { // case (Fail(e1), Fail(e2)) return Validation.fail(s.append(e1, e2)); } }, new F<F<A, B>, Validation<E, B>>() { public Validation<E, B> apply(final F<A, B> f) { // case (Fail(e1), Success(f)) return Validation.fail(e1); } }); } }, new F<A, Validation<E, B>>() { public Validation<E, B> apply(final A a) { return f.fold(new F<E, Validation<E, B>>() { public Validation<E, B> apply(final E e2) { // case (Success(a), Fail(e2)) return Validation.fail(e2); } }, new F<F<A, B>, Validation<E, B>>() { public Validation<E, B> apply(final F<A, B> f) { // case (Success(a), Success(f)) return Validation.success(f.apply(a)); } }); } }); } }