Functional Programming without Lambda - Part 2 Lifting, Functor, Monad

Lifting

Now, let‘s review map from another perspective. map :: (T -> R) -> [T] -> [R] accepts 2 parameters, a function f :: T -> R and a list list :: [T]. [T] is a generic type paramterized by T, it‘s not the same as T, but definitely shares some properties of T. So, an interesting interpretation of map(f) :: [T] -> [R] is that map turns a function of type T -> R into a function of [T] -> [R], this is called lifting.

Let‘s get a flavor of lifting with the example of x -> x * x. map(x -> x * x) turns the square on Int into the square on [Int]. Therefore, it makes sense to name map(x -> x * x) as squareForList. You can even simply name it as square, which is the overloaded version of square for [Int].

The concept of lifting is the key to understand the advanced abstractions in functional programming. Lifting allows you to reuse a function of type T -> R (or T1 -> T2 -> R ...) in the context of List, Maybe, Lazy, Promise, etc. That saves you the work to implement similar functions from scratch just for the context.

Let me explain why lifting matters by changing the string conversion problem in the previous chapter a bit. In the original problem, we got a function convert :: String -> String, what if the input string is not directly available, but asynchronously fetched from a web service? Do you want to chain the convert to the callback for the asynchronous HTTP response? You can use callback, but that makes you lose functional composition.

Just like map lifts a function on T into a function on [T], we just wanted to lift it to Promise<T>. Here Promise<T> stands for an asynchronously available value of type T. So, we‘ll introduce a function fmap :: (T -> R) -> Promise<T> -> Promise<R>, meaning fmap turns a function of type T -> R into a function of type Promise<T> -> Promise<R>. See the following example:

// Java 6
F1<String, String> convert = _(split(" "), reverse, map(toUpperCase), join("_"));
// fmap turns a function of type "T -> R" into a function of type "Promise<T> -> Promise<R>"
F1<Promise<String>, Promise<String>> convertForPromise = fmap(convert);
// await() blocks until the async result available
String result = convertForPromise.apply(promise(URL)).await();

More details here.

promise(URL) :: Promise<String> stands for a string value which will be available in the future. Calling await on the promise object will block until the string is available. fmap turns convert :: String -> String into convertForPromise :: Promise<String> -> Promise<String> which can work on a promise. By the way, if you like we can omit the convert function by inlining it as:

fmap(_(split(" "), reverse, map(toUpperCase), join("_")))

Functor

As I mentioned in the previous section, Promise, Maybe, List, Lazy, and so on are all contexts. The idea behind them is the functional abstraction named Functor. In Java, a functor can be defined as follows:

interface class Functor<T> {
  <R> Functor<R> fmap(F1<T, R> f);
}

then, Promise<T> will implement the fmap:

class Promise<T> implements Functor<T> {
  <R> Promise<R> fmap(F1<T, R> f) {
    ...
  }
}

But as I have said before, we are not in favor of the OO-style API design. A better way to define functor in Java is as follows:

public class Promises {
  public static <T, R> F1<Promise<T>, Promise<R>> fmap(F1<T, R> f) {
    return Promises.<T, R>fmap().apply(f);
  }
}

It essentially means if we can define a function fmap to lift a function of type T -> R into a function of type Functor<T> -> Functor<R>, then Functor<T> is a functor. In addition, there‘re 2 properties named Functor Laws as the semantics constraints to ensure the type makes sense:

fmap id      = id
fmap (p . q) = (fmap p) . (fmap q)

Don‘t be scared, it‘s actually very simple. Just like we put the FILO constraint on the push and pop of the Stack type to make sure it behaves as what we want.

If you feel too abstract, take a look at the example of List<T> or Promise<T>. More often than not, your functor class satisfies the laws automatically. However, keep in mind that you may always want to test the functor laws for your functor class, just like you want to test FILO for a Stack implementation. See unit tests of Promise<T> for the functor laws here.

Monad

Lifting a function of type T -> R into a function of type Functor<T> -> Functor<R> allows us to reuse the existing functions in a different context, but sometimes the basic function we have is not as plain as toUpperCase :: String -> String. Let‘s look at the following problem:

Given 1) a function Promise<String> asyncGet(String url) which accepts an URL and returns a promise of the web page; 2) n hyperlinked web pages, the contents of one page is the URL of the next page, url1 -> page1 (url2) -> page2 (url3) -> page3 (url4) ... page_n (url1), please write a function Promise<String> asyncGetK(String url, int k) which starts from the url, goes forward by k steps, returns the promise of the page.

If what we have is a sync function String get(String url), that would be a simple loop like:

// Java 6
String getK(String url, int k) {
  String page = url;
  for (int i = 0; i < k; i++) {
    page = get(page);
  }
  return page;
}

The point here is that the result of the previous get can be directly passed to the next get, because the type matches. In other words, we can compose multiple get functions together.

But since we only have asyncGet of type String -> Promise<String>, the result type Promise<String> of a previous asyncGet doesn‘t match the parameter type url :: String of the next asyncGet, we are unable to compose them together directly. So, we‘d really like to lift asyncGet :: String -> Promise<String> into asyncGetPromise :: Promise<String> -> Promise<String> then it‘s composable.

The idea is great, but what would happen if we apply fmap to asyncGet. Since the type of fmap is (T -> R) -> Promise<T> -> Promise<R>, then the type of fmap(asyncGet) would be Promise<String> -> Promise<Promise<String>>. Ooops, that‘s too much! But if we have a join :: Promise<Promise<T>> -> Promise<T> to flatten the nested promise, then we will get compose(fmap(asyncGet), join) :: Promise<String> -> Promise<String>. Combining fmap and join together, we get a function named flatMap :: (T -> R) -> Promise<T> -> Promise<R>, which is exactly what we want.

Being able to define a function fmap makes the type a functor, likewise being able to define a function flatMap makes the type a monad. Then the code would be like:

// Java 6
String getK(String url, int k) {
  F1<Promise<String>, Promise<String>> liftedAsyncGet = flatMap(asyncGet);
  Promise<String> page = unit(url);
  for (int i = 0; i < k; i++) {
    page = liftedAsyncGet(page);
  }
  return page.await();
}

It‘s really share the same structure with the sync code. That is isomorphic!

时间: 2024-09-28 15:56:08

Functional Programming without Lambda - Part 2 Lifting, Functor, Monad的相关文章

PROFESSIONAL FUNCTIONAL PROGRAMMING IN C# 学习

CHAPTER1 A Look at Functional Programming History CHAPTER2 Putting Functional Programming into a Modern Context CHAPTER3 Functions,Delegates,and Lambda Expressions CHAPTER4 Flexible Typing with Generics CHAPTER5 Lazy Listing with Iterators CHAPTER6 E

Functional Programming.

I have been seeing Redux for sometime, but recently I come to realize that , it's part of so called functional programming. The basic idea for functional programming is so simple, Declare all dependency as function input, no hidden input ( we should

Functional Programming in Javascript 中文翻译 —— 目录和介绍

原著:[美] Dan Mantyla 目录 前言1 Javascript函数式编程的力量--举个例子2 函数式编程基础3 建立函数式编程环境4 在Javascript中实现函数式编程的技术5 类型理论6 高级主题以及Javascript的缺陷7 Javascript中的函数式和面型对象编程8 Javascript中的函数式和面型对象编程 关于翻译的这本书 现在市面上有两本专注于javascript函数式编程的书,一本是<Functional Javascript>(下文简称FJS), 另一本就

Apply Functional Programming Principles

Apply Functional Programming Principles Edward Garson FUNCTiONAL PROGRAMMiNG has recently enjoyed renewed interest from the mainstream programming community. Part of the reason is because emergent properties of the functional paradigm are well positi

Functional Programming Principles in ScalaScala函式编程原理 第一章笔记

所有non-trival编程语言都提供了 基本表达式(expression)去表示最简单的表达式 组合表达式的方法 抽象表达式的方法,也就是为表达式引入一个名字去引用它 substitional model 替代模型 sumOfSquares(3,2+2) sumOfSquares(3,4) square(3)+square(4) 9+square(4) 9+16 25 这种模型的核心概念是所有的表达式都规约成值,替代模型在lamada表达式中被形式化,构成了函数式编程的基石 substitio

Coursera公开课Functional Programming Principles in Scala习题解答:Week 2

引言 OK.时间很快又过去了一周,第一周有五一假期所以感觉时间绰绰有余,这周中间没有假期只能靠晚上加周末的时间来消化,其实还是有点紧张呢!后来发现每堂课的视频还有对应的课件(Slide).字幕(subtitles)可以下载,这样下载视频学习和在线学习就只差课程中间的Exercise了 Week 2主要讲函数,函数在Scala里是first-class citizen,可以在任意域内出现,这门课其实也是在借Scala来讲函数式编程原理.好了,不多说,进入习题解析. 这周的作业主要是使用函数来表示一

Beginning Scala study note(4) Functional Programming in Scala

1. Functional programming treats computation as the evaluation of mathematical and avoids state and mutable data. Scala encourages an expression-oriented programming(EOP) 1) In expression-oriented programming every statement is an expression. A state

Coursera公开课Functional Programming Principles in Scala习题解答:Week 3

引言 这周的作业其实有点复杂,需要完成的代码有点多,有点绕.本周的课程主要讲了Scala中的类.继承和多态,作业也很好的从各个方面考察了课程的内容.作业题目工程主要需要完成的部分是TweetSet.scala这个文件中的内容,比较新潮,都是和推特相关.其中定义了一个抽象类TweetSet,以及其的两个子类Empty.NonEmpty,表示空集和非空集.非空集使用二叉树来表示,二叉树的根是一个Tweet类对象,表示一条推特(用天朝的话来说叫做"微博"),一条微博哦不,一条推特包含user

Functional Programming 资料收集

书籍: Functional Programming for Java Developers papers: Why Functional Programming Matters 在线课程: CSE341: Programming Languages (washington university) 课程主页: https://courses.cs.washington.edu/courses/cse341/13wi/ cousera主页: https://class.coursera.org/p