12

Just ran across this term here:

http://www.codemesh.io/codemesh2014/viktor-klang

"We'll demonstrate the Flow API—a lifted representation—as well as a pluggable way of transforming the lifted representation into the execution representation—Flow Materialization."

Googling did not help much.

Den
  • 4,827
  • 2
  • 32
  • 48
  • recommended reading: **[Discuss this ${blog}](http://meta.programmers.stackexchange.com/questions/6417/discuss-this-blog)** – gnat Sep 08 '14 at 13:20
  • 11
    @gnat it seems that he did not invent that term, it doesn't look like an opinion, it's not likely to provoke a discussion and my gut feeling is that it's not going to be too broad (although feels like math). – Den Sep 08 '14 at 13:31
  • 2
    I discuss the meaning of "lifted" in the context of C# here: http://blogs.msdn.com/b/ericlippert/archive/2007/06/27/what-exactly-does-lifted-mean.aspx -- likely the Scala developers are using the term in an analogous, but more general fashion. – Eric Lippert Sep 08 '14 at 17:22

3 Answers3

22

I am not familiar with the Flow API.

The term “lifting” comes from category theory. In programming languages such as Haskell or Scala, a lift function takes a function A => B, and somehow performs magic so that the lifted function F[A] => F[B] can be applied to a functor or monad F[A].

A concrete example using Scala's Seq container: Assume we have a function def double(x: Int): Int = 2 * x, and a sequence val xs = Seq(1, 2, 3). We cannot double(xs) due to incompatible types. But if we obtain a val doubleSeq = liftToSeq(double), we can do doubleSeq(xs), which evaluates to Seq(2, 4, 6). Here, liftToSeq can be implemented as

def liftToSeq[A, B](f: A => B): (Seq[A] => Seq[B]) =
  (seq: Seq[A]) => seq.map(f)

The Seq(…) constructor can also be seen as a lifting operation, which lifts the values 1, 2, 3 into a Seq instance, thus allowing us to use list abstractions for these values.

Monads allow us to encapsulate the inner workings of some type by offering a watertight but composable interface. Using a lifted representation can make it easier to reason about a computation. Using such abstractions also means that we lose knowledge of the abstracted-away specifics, but those are needed for providing an efficient implementation under the hood (finding a suitable execution representation).

Scott Whitlock
  • 21,874
  • 5
  • 60
  • 88
amon
  • 132,749
  • 27
  • 279
  • 375
  • 4
    That's a good description of mathematical "lifting". We should also include a reference to the [more formal description of lifting from Wikipedia](https://en.wikipedia.org/wiki/Lift_(mathematics)). – Scott Whitlock Sep 08 '14 at 14:48
  • 3
    A perhaps more clear example of "lifting" is lifting to nullable (or "optional" or "maybe") types. For example, suppose you have an operator `+` defined such that `int + int --> int`. The lifted-to-nullable operator `int? + int? --> int?` has the semantics of "if either operand is null then the answer is null, otherwise use the non-lifted operator on the values". – Eric Lippert Sep 08 '14 at 17:21
  • @ScottWhitlock Do you even lift? – helrich Sep 08 '14 at 19:29
  • I thought I understood lifting before I read the wikipedia article, but in the linked article, only three objects `X,Y,Z` are involved. Could you maybe explain why in Haskell/Scala the lifting actually involves four types instead? (`A,B,F[A],F[B]`) – Frank Sep 09 '14 at 05:09
  • 1
    @Frank I read the Wikipedia article before writing my answer, and didn't understand it either. Instead, I found [the *Haskell Wiki* on *Lifting*](http://www.haskell.org/haskellwiki/Lifting) to be more accessible. Note that we don't really have four types. We have four concrete types, but only three type variables: two types `A` and `B`, and a functor `F` which is a type constructor. – amon Sep 09 '14 at 08:49
  • 1
    I'm not too deep into all of this, but if `F` is a type constructor, then `F[A]` is one of its constructed types. So why is it wrong to speak of these four types? (two types and one type constructor would be equally fine though of course) – Frank Sep 09 '14 at 09:08
  • @Frank As I understand it, `F[A]` and `F[B]` are the same constructed type, just with different values. That's why 3 types and not 4 types – Izkata Sep 09 '14 at 13:57
7

The term to lift can of course have different meanings depending on the context.

In generic programming it describes the process of abstracting to the next higher level. For example, you could have two pieces of code, one type with int, and the other with float. Lifting this code would mean something like templating the method with a generic type T that works for both, int and float.

I found this usage of the term to be a good intuitive guideline for what lifting means. The only difference that seems to exist between the different contexts is what this higher abstraction really is.

In particular, Viktor is known in the context of functional programming, and in this context, you can find sightly different interpretations of lifting there. One example, is to lift values into a functor, or to lift functions to work on monadic values (i.e. Haskell's liftM2).

A very concrete example of a "lifted representation" could then f.ex. be a List(1), or a Some(1).

Frank
  • 14,407
  • 3
  • 41
  • 66
4

These sorts of concepts are usually easiest to understand with a concrete example. Consider the following excerpt from this Flow API example:

Flow(text.split("\\s").toVector).
      // transform
      map(line => line.toUpperCase).
      // print to console (can also use ``foreach(println)``)
      foreach(transformedLine => println(transformedLine)).
      onComplete(FlowMaterializer(MaterializerSettings())) {
        case Success(_) => system.shutdown()
        case Failure(e) =>
          println("Failure: " + e.getMessage)
          system.shutdown()
      }

This takes the following code:

text.split("\\s").toVector.
      map(line => line.toUpperCase).
      foreach(println)

and "lifts" it into a Flow context. That allows you to use the same syntax you are familiar with for specifying your algorithm, but behind the scenes the map is done in parallel on several processors or even machines, then the foreach(println) seamlessly collects that output back to one processor for printing.

This is a generic term that can refer to wrapping any context around any type. Another more familiar example is map takes a function that works on a single element and "lifts" it into the new context of working on a collection of those elements. Lifting is ubiquitous in functional programming and one of the main reasons it is so much easier to reuse functional code.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479