0
# Function Lifting
1
2
The Lift module in shapeless provides utilities for lifting ordinary functions of arbitrary arity into various contexts, such as Option. This enables functional programming patterns where operations can safely handle absent values.
3
4
## Core Functionality
5
6
### LiftO - Lifting into Option
7
8
```scala { .api }
9
object Lift {
10
/**
11
* Lifts a function of arbitrary arity into Option context
12
* All arguments must be Some for function to be applied
13
*/
14
def liftO[InF, InL <: HList, R, OInL <: HList, OutF](f: InF)
15
(implicit
16
hlister: FnHListerAux[InF, InL => R],
17
mapper: MapperAux[get.type, OInL, InL],
18
folder: MapFolder[OInL, Boolean, isDefined.type],
19
unhlister: FnUnHListerAux[OInL => Option[R], OutF]
20
): OutF
21
}
22
```
23
24
The `liftO` function transforms regular functions to work with Option types, automatically handling the case where any argument is None.
25
26
**Usage Examples:**
27
28
```scala
29
import shapeless._
30
31
// Lift binary function
32
val add: (Int, Int) => Int = _ + _
33
val addOption = Lift.liftO(add) // (Option[Int], Option[Int]) => Option[Int]
34
35
val result1 = addOption(Some(5), Some(3)) // Some(8)
36
val result2 = addOption(Some(5), None) // None
37
val result3 = addOption(None, Some(3)) // None
38
39
// Lift ternary function
40
val combine: (String, Int, Boolean) => String =
41
(s, i, b) => s"$s-$i-$b"
42
43
val combineOption = Lift.liftO(combine)
44
// Type: (Option[String], Option[Int], Option[Boolean]) => Option[String]
45
46
val combined1 = combineOption(Some("hello"), Some(42), Some(true))
47
// Some("hello-42-true")
48
49
val combined2 = combineOption(Some("hello"), None, Some(true))
50
// None
51
```
52
53
## Arity Support
54
55
The `liftO` function works with functions of any arity from 0 to 22:
56
57
### Nullary Functions
58
59
```scala
60
import shapeless._
61
62
val constant: () => String = () => "fixed"
63
val constantOption = Lift.liftO(constant) // () => Option[String]
64
65
val result = constantOption() // Some("fixed")
66
```
67
68
### Unary Functions
69
70
```scala
71
import shapeless._
72
73
val double: Int => Int = _ * 2
74
val doubleOption = Lift.liftO(double) // Option[Int] => Option[Int]
75
76
val doubled1 = doubleOption(Some(21)) // Some(42)
77
val doubled2 = doubleOption(None) // None
78
```
79
80
### Higher Arity Functions
81
82
```scala
83
import shapeless._
84
85
// Quaternary function
86
val process4: (String, Int, Double, Boolean) => String =
87
(s, i, d, b) => s"$s:$i:$d:$b"
88
89
val process4Option = Lift.liftO(process4)
90
// Type: (Option[String], Option[Int], Option[Double], Option[Boolean]) => Option[String]
91
92
val result = process4Option(Some("test"), Some(1), Some(2.5), Some(false))
93
// Some("test:1:2.5:false")
94
95
// Missing any argument results in None
96
val resultNone = process4Option(Some("test"), None, Some(2.5), Some(false))
97
// None
98
```
99
100
## Advanced Usage Patterns
101
102
### Lifting Complex Operations
103
104
```scala
105
import shapeless._
106
107
// Complex business logic function
108
case class User(name: String, age: Int)
109
case class Product(name: String, price: Double)
110
case class Order(user: User, product: Product, quantity: Int)
111
112
val createOrder: (User, Product, Int) => Order = Order.apply
113
val createOrderOption = Lift.liftO(createOrder)
114
// Type: (Option[User], Option[Product], Option[Int]) => Option[Order]
115
116
val user = Some(User("Alice", 30))
117
val product = Some(Product("Widget", 19.99))
118
val quantity = Some(2)
119
120
val order = createOrderOption(user, product, quantity)
121
// Some(Order(User("Alice", 30), Product("Widget", 19.99), 2))
122
123
// Missing user information
124
val failedOrder = createOrderOption(None, product, quantity)
125
// None
126
```
127
128
### Pipeline Operations
129
130
```scala
131
import shapeless._
132
133
// Chain lifted functions
134
val parseInput: String => Int = Integer.parseInt
135
val validatePositive: Int => Int = i => if (i > 0) i else throw new Exception("negative")
136
val doubleValue: Int => Int = _ * 2
137
val formatOutput: Int => String = i => s"Result: $i"
138
139
// Lift each function
140
val parseInputOption = Lift.liftO(parseInput) // Option[String] => Option[Int]
141
val validatePositiveOption = Lift.liftO(validatePositive) // Option[Int] => Option[Int]
142
val doubleValueOption = Lift.liftO(doubleValue) // Option[Int] => Option[Int]
143
val formatOutputOption = Lift.liftO(formatOutput) // Option[Int] => Option[String]
144
145
// Create pipeline (in practice you'd handle exceptions properly)
146
def safePipeline(input: Option[String]): Option[String] = {
147
val parsed = parseInputOption(input)
148
val validated = parsed.flatMap(i => try { Some(validatePositive(i)) } catch { case _ => None })
149
val doubled = doubleValueOption(validated)
150
formatOutputOption(doubled)
151
}
152
153
val result1 = safePipeline(Some("21")) // Some("Result: 42")
154
val result2 = safePipeline(Some("-5")) // None (validation fails)
155
val result3 = safePipeline(Some("abc")) // None (parsing fails)
156
val result4 = safePipeline(None) // None (no input)
157
```
158
159
### Working with Case Classes
160
161
```scala
162
import shapeless._
163
164
case class Point3D(x: Double, y: Double, z: Double)
165
case class Vector3D(x: Double, y: Double, z: Double)
166
167
val createPoint: (Double, Double, Double) => Point3D = Point3D.apply
168
val createVector: (Double, Double, Double) => Vector3D = Vector3D.apply
169
170
val createPointOption = Lift.liftO(createPoint)
171
val createVectorOption = Lift.liftO(createVector)
172
173
// Safe construction from potentially missing coordinates
174
def safePoint(x: Option[Double], y: Option[Double], z: Option[Double]): Option[Point3D] =
175
createPointOption(x, y, z)
176
177
def safeVector(x: Option[Double], y: Option[Double], z: Option[Double]): Option[Vector3D] =
178
createVectorOption(x, y, z)
179
180
val point = safePoint(Some(1.0), Some(2.0), Some(3.0)) // Some(Point3D(1.0, 2.0, 3.0))
181
val invalidPoint = safePoint(Some(1.0), None, Some(3.0)) // None
182
183
// Vector operations
184
val addVectors: (Vector3D, Vector3D) => Vector3D =
185
(v1, v2) => Vector3D(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z)
186
187
val addVectorsOption = Lift.liftO(addVectors)
188
189
val v1 = Some(Vector3D(1, 2, 3))
190
val v2 = Some(Vector3D(4, 5, 6))
191
val sum = addVectorsOption(v1, v2) // Some(Vector3D(5.0, 7.0, 9.0))
192
```
193
194
### Integration with Validation
195
196
```scala
197
import shapeless._
198
199
// Validation functions
200
def validateEmail(email: String): Option[String] =
201
if (email.contains("@")) Some(email) else None
202
203
def validateAge(age: Int): Option[Int] =
204
if (age >= 0 && age <= 120) Some(age) else None
205
206
def validateName(name: String): Option[String] =
207
if (name.nonEmpty) Some(name) else None
208
209
// User creation function
210
case class ValidUser(name: String, email: String, age: Int)
211
val createUser: (String, String, Int) => ValidUser = ValidUser.apply
212
val createUserOption = Lift.liftO(createUser)
213
214
// Safe user creation with validation
215
def createValidUser(name: String, email: String, age: Int): Option[ValidUser] = {
216
val validName = validateName(name)
217
val validEmail = validateEmail(email)
218
val validAge = validateAge(age)
219
220
createUserOption(validName, validEmail, validAge)
221
}
222
223
val user1 = createValidUser("Alice", "alice@example.com", 30) // Some(ValidUser(...))
224
val user2 = createValidUser("", "alice@example.com", 30) // None (empty name)
225
val user3 = createValidUser("Alice", "invalid-email", 30) // None (bad email)
226
val user4 = createValidUser("Alice", "alice@example.com", -5) // None (invalid age)
227
```
228
229
## Implementation Details
230
231
The `liftO` function uses several type classes internally:
232
233
- **FnHLister**: Converts function to HList function
234
- **Mapper**: Maps `get` over Option HList to extract values
235
- **MapFolder**: Folds over Option HList with `isDefined` to check all are present
236
- **FnUnHLister**: Converts HList function back to regular function
237
238
This enables the lifting to work generically across functions of any arity while maintaining type safety.
239
240
Function lifting provides a powerful way to work with potentially absent values in a compositional manner, enabling robust and safe functional programming patterns.