Apache Mesos
lambda.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12 
13 #ifndef __STOUT_LAMBDA_HPP__
14 #define __STOUT_LAMBDA_HPP__
15 
16 #include <algorithm>
17 #include <functional>
18 #include <memory>
19 #include <type_traits>
20 #include <utility>
21 #include <vector>
22 
23 #include <glog/logging.h>
24 
25 #include <stout/cpp14.hpp>
26 #include <stout/cpp17.hpp>
27 #include <stout/hashmap.hpp>
28 #include <stout/result_of.hpp>
29 
30 namespace lambda {
31 
32 using std::bind;
33 using std::cref;
34 using std::function;
35 using std::ref;
36 
37 using namespace std::placeholders;
38 
39 
40 template <
41  template <typename...> class Iterable,
42  typename F,
43  typename U,
44  typename V = typename result_of<F(U)>::type,
45  typename... Us>
46 Iterable<V> map(F&& f, const Iterable<U, Us...>& input)
47 {
48  Iterable<V> output;
50  input.begin(),
51  input.end(),
52  std::inserter(output, output.begin()),
53  std::forward<F>(f));
54  return output;
55 }
56 
57 
58 template <
59  template <typename...> class OutputIterable,
60  template <typename...> class InputIterable,
61  typename F,
62  typename U,
63  typename V = typename result_of<F(U)>::type,
64  typename... Us>
65 OutputIterable<V> map(F&& f, const InputIterable<U, Us...>& input)
66 {
67  OutputIterable<V> output;
69  input.begin(),
70  input.end(),
71  std::inserter(output, output.begin()),
72  std::forward<F>(f));
73  return output;
74 }
75 
76 
77 template <
78  template <typename...> class Iterable,
79  typename F,
80  typename U,
81  typename V = typename result_of<F(U)>::type,
82  typename = typename std::enable_if<
83  !std::is_same<U, V>::value>::type,
84  typename... Us>
85 Iterable<V> map(F&& f, Iterable<U, Us...>&& input)
86 {
87  Iterable<V> output;
89  std::make_move_iterator(input.begin()),
90  std::make_move_iterator(input.end()),
91  std::inserter(output, output.begin()),
92  std::forward<F>(f));
93  return output;
94 }
95 
96 
97 template <
98  template <typename...> class Iterable,
99  typename F,
100  typename U,
101  typename = typename std::enable_if<
103  typename... Us>
104 Iterable<U, Us...>&& map(F&& f, Iterable<U, Us...>&& iterable)
105 {
107  std::make_move_iterator(iterable.begin()),
108  std::make_move_iterator(iterable.end()),
109  iterable.begin(),
110  std::forward<F>(f));
111  return std::move(iterable);
112 }
113 
114 
115 template <
116  template <typename...> class OutputIterable,
117  template <typename...> class InputIterable,
118  typename F,
119  typename U,
120  typename V = typename result_of<F(U)>::type,
121  typename... Us>
122 OutputIterable<V> map(F&& f, InputIterable<U, Us...>&& input)
123 {
124  OutputIterable<V> output;
126  std::make_move_iterator(input.begin()),
127  std::make_move_iterator(input.end()),
128  std::inserter(output, output.begin()),
129  std::forward<F>(f));
130  return output;
131 }
132 
133 
134 template <
135  template <typename...> class OutputIterable,
136  typename F,
137  typename U,
138  typename V = typename result_of<F(U)>::type>
139 OutputIterable<V> map(F&& f, std::initializer_list<U> input)
140 {
141  OutputIterable<V> output;
143  input.begin(),
144  input.end(),
145  std::inserter(output, output.begin()),
146  std::forward<F>(f));
147  return output;
148 }
149 
150 
151 template <
152  typename F,
153  typename U,
154  typename V = typename result_of<F(U)>::type>
155 std::vector<V> map(F&& f, std::initializer_list<U> input)
156 {
157  std::vector<V> output;
159  input.begin(),
160  input.end(),
161  std::inserter(output, output.begin()),
162  std::forward<F>(f));
163  return output;
164 }
165 
166 
167 // TODO(arojas): Make this generic enough such that an arbitrary
168 // number of inputs can be used.
169 // It would be nice to be able to choose between `std::pair` and
170 // `std::tuple` or other heterogeneous compilers.
171 template <
172  template <typename...> class OutputIterable,
173  template <typename...> class InputIterable1,
174  template <typename...> class InputIterable2,
175  typename U1,
176  typename U2,
177  typename... U1s,
178  typename... U2s>
179 OutputIterable<std::pair<U1, U2>> zipto(
180  const InputIterable1<U1, U1s...>& input1,
181  const InputIterable2<U2, U2s...>& input2)
182 {
183  OutputIterable<std::pair<U1, U2>> output;
184 
185  auto iterator1 = input1.begin();
186  auto iterator2 = input2.begin();
187 
188  auto inserter = std::inserter(output, output.begin());
189 
190  // We zip only as many elements as we can.
191  while (iterator1 != input1.end() && iterator2 != input2.end()) {
192  inserter = std::make_pair(*iterator1, *iterator2);
193  iterator1++;
194  iterator2++;
195  }
196 
197  return output;
198 }
199 
200 
201 // TODO(arojas): Make this generic enough such that the output
202 // container type can be parametrized, i.e. `hashmap`, `std::unordered_map`,
203 // `std::map`, `std::vector<std::pair<U1, U2>`.
204 // NOTE: by default we zip into a `hashmap`. See the `zip()` overload
205 // for zipping into another iterable as `std::pair`.
206 template <
207  template <typename...> class InputIterable1,
208  template <typename...> class InputIterable2,
209  typename U1,
210  typename U2,
211  typename... U1s,
212  typename... U2s>
214  const InputIterable1<U1, U1s...>& input1,
215  const InputIterable2<U2, U2s...>& input2)
216 {
217  // TODO(benh): Use the overload of `zip()`, something like:
218  // std::vector<std::pair<U1, U2>> vector = zip<std::vector>(input1, input2);
219  // return hashmap<U1, U2>(
220  // std::make_move_iterator(vector.begin()),
221  // std::make_move_iterator(vector.end()));
222 
223  hashmap<U1, U2> output;
224 
225  auto iterator1 = input1.begin();
226  auto iterator2 = input2.begin();
227 
228  // We zip only as many elements as we can.
229  while (iterator1 != input1.end() && iterator2 != input2.end()) {
230  output.put(*iterator1, *iterator2);
231  iterator1++;
232  iterator2++;
233  }
234 
235  return output;
236 }
237 
238 
239 #define RETURN(...) -> decltype(__VA_ARGS__) { return __VA_ARGS__; }
240 
241 
242 namespace internal {
243 
244 // The `int` specializations here for `is_placeholder<T>::value`.
245 // `is_placeholder<T>::value` returns a `0` for non-placeholders,
246 // and I > 0 for placeholders where I indicates the placeholder
247 // value. e.g., `is_placeholder<decltype(_1)>::value == 1`
248 
249 template <int I>
250 struct Expand
251 {
252  // Bound argument is a placeholder.
253  template <typename T, typename Args>
254  auto operator()(T&&, Args&& args) const
255  RETURN(std::get<I - 1>(std::forward<Args>(args)))
256 };
257 
258 
259 template <>
260 struct Expand<0>
261 {
262  // Bound argument is not a placeholder.
263  template <typename T, typename Args>
264  auto operator()(T&& t, Args&&) const
265  RETURN(std::forward<T>(t))
266 };
267 
268 
269 template <typename F, typename... BoundArgs>
270 class Partial
271 {
272  F f;
273  std::tuple<BoundArgs...> bound_args;
274 
275  template <typename T, typename Args>
276  static auto expand(T&& t, Args&& args)
277  RETURN(Expand<std::is_placeholder<typename std::decay<T>::type>::value>{}(
278  std::forward<T>(t), std::forward<Args>(args)))
279 
280  // Invoke the given function `f` with bound arguments expanded. If a bound
281  // argument is a placeholder, we use the index `I` of the placeholder to
282  // pass the `I`th argument out of `args` along. Otherwise, we pass the bound
283  // argument through preserving its value category. That is, passing the bound
284  // argument as an lvalue-ref or rvalue-ref depending correspondingly on
285  // whether the `Partial` itself is an lvalue or rvalue.
286  template <typename F_, typename BoundArgs_, typename Args, std::size_t... Is>
287  static auto invoke_expand(
288  F_&& f,
289  BoundArgs_&& bound_args,
291  Args&& args)
292  RETURN(cpp17::invoke(
293  std::forward<F_>(f),
294  expand(
295  std::get<Is>(std::forward<BoundArgs_>(bound_args)),
296  std::forward<Args>(args))...))
297 
298 public:
299  template <typename... BoundArgs_>
300  explicit Partial(const F& f, BoundArgs_&&... args)
301  : f(f), bound_args(std::forward<BoundArgs_>(args)...) {}
302 
303  template <typename... BoundArgs_>
304  explicit Partial(F&& f, BoundArgs_&&... args)
305  : f(std::move(f)), bound_args(std::forward<BoundArgs_>(args)...) {}
306 
307  Partial(const Partial&) = default;
308  Partial(Partial&&) = default;
309 
310  Partial& operator=(const Partial&) = default;
311  Partial& operator=(Partial&&) = default;
312 
313  template <typename... Args>
314  auto operator()(Args&&... args) &
315  RETURN(invoke_expand(
316  f,
317  bound_args,
318  cpp14::make_index_sequence<sizeof...(BoundArgs)>(),
319  std::forward_as_tuple(std::forward<Args>(args)...)))
320 
321  template <typename... Args>
322  auto operator()(Args&&... args) const &
323  RETURN(invoke_expand(
324  f,
325  bound_args,
326  cpp14::make_index_sequence<sizeof...(BoundArgs)>(),
327  std::forward_as_tuple(std::forward<Args>(args)...)))
328 
329  template <typename... Args>
330  auto operator()(Args&&... args) &&
331  RETURN(invoke_expand(
332  std::move(f),
333  std::move(bound_args),
334  cpp14::make_index_sequence<sizeof...(BoundArgs)>(),
335  std::forward_as_tuple(std::forward<Args>(args)...)))
336 
337  template <typename... Args>
338  auto operator()(Args&&... args) const &&
339  RETURN(invoke_expand(
340  std::move(f),
341  std::move(bound_args),
342  cpp14::make_index_sequence<sizeof...(BoundArgs)>(),
343  std::forward_as_tuple(std::forward<Args>(args)...)))
344 };
345 
346 } // namespace internal {
347 
348 
349 // Performs partial function application, similar to `std::bind`. However,
350 // it supports moving the bound arguments through, unlike `std::bind`.
351 // To do so, the `operator()` must be invoked on a rvalue `lambda::partial`.
352 //
353 // Unsupported `std::bind` features:
354 // - There is no special treatment for nested bind expressions. When calling
355 // `operator()` on partial, call parameters will not be passed to nested
356 // bind expression. Instead, bind expression will be passed as-is to the
357 // wrapped function object. This behavior is intentional, for simplicity
358 // reasons, and is in sync with C++20's `std::bind_front`.
359 // - Passing `std::reference_wrapper` is not implemented.
360 template <typename F, typename... Args>
362  typename std::decay<F>::type,
363  typename std::decay<Args>::type...>
364 partial(F&& f, Args&&... args)
365 {
366  using R = internal::Partial<
367  typename std::decay<F>::type,
368  typename std::decay<Args>::type...>;
369  return R(std::forward<F>(f), std::forward<Args>(args)...);
370 }
371 
372 
373 #undef RETURN
374 
375 
376 namespace internal {
377 
378 // Helper for invoking functional objects.
379 // It needs specialization for `void` return type to ignore potentialy
380 // non-`void` return value from `cpp17::invoke(f, args...)`.
381 template <typename R>
382 struct Invoke
383 {
384  template <typename F, typename... Args>
385  R operator()(F&& f, Args&&... args)
386  {
387  return cpp17::invoke(std::forward<F>(f), std::forward<Args>(args)...);
388  }
389 };
390 
391 
392 template <>
393 struct Invoke<void>
394 {
395  template <typename F, typename... Args>
396  void operator()(F&& f, Args&&... args)
397  {
398  cpp17::invoke(std::forward<F>(f), std::forward<Args>(args)...);
399  }
400 };
401 
402 } // namespace internal {
403 
404 
405 // This is similar to `std::function`, but it can only be called once.
406 // The "called once" semantics is enforced by having rvalue-ref qualifier
407 // on `operator()`, so instances of `CallableOnce` must be `std::move`'d
408 // in order to be invoked. Similar to `std::function`, this has heap
409 // allocation overhead due to type erasure.
410 //
411 // Note: Heap allocation can be avoided in some cases by implementing
412 // small buffer optimization. This is currently not implemented.
413 template <typename F>
415 
416 
417 template <typename R, typename... Args>
418 class CallableOnce<R(Args...)>
419 {
420 public:
421  template <
422  typename F,
423  typename std::enable_if<
424  !std::is_same<F, CallableOnce>::value &&
425  (std::is_same<R, void>::value ||
426  std::is_convertible<
427  decltype(
428  cpp17::invoke(std::declval<F>(), std::declval<Args>()...)),
429  R>::value),
430  int>::type = 0>
432  : f(new CallableFn<typename std::decay<F>::type>(std::forward<F>(f))) {}
433 
434  CallableOnce(CallableOnce&&) = default;
435  CallableOnce(const CallableOnce&) = delete;
436 
437  CallableOnce& operator=(CallableOnce&&) = default;
438  CallableOnce& operator=(const CallableOnce&) = delete;
439 
440  R operator()(Args... args) &&
441  {
442  CHECK(f != nullptr);
443  return std::move(*f)(std::forward<Args>(args)...);
444  }
445 
446 private:
447  struct Callable
448  {
449  virtual ~Callable() = default;
450  virtual R operator()(Args&&...) && = 0;
451  };
452 
453  template <typename F>
454  struct CallableFn : Callable
455  {
456  F f;
457 
458  CallableFn(const F& f) : f(f) {}
459  CallableFn(F&& f) : f(std::move(f)) {}
460 
461  R operator()(Args&&... args) && override
462  {
463  return internal::Invoke<R>{}(std::move(f), std::forward<Args>(args)...);
464  }
465  };
466 
467  std::unique_ptr<Callable> f;
468 };
469 
470 } // namespace lambda {
471 
472 
473 namespace std {
474 
475 template <typename F, typename... Args>
476 struct is_bind_expression<lambda::internal::Partial<F, Args...>>
477  : true_type {};
478 
479 } // namespace std {
480 
481 #endif // __STOUT_LAMBDA_HPP__
F && f
Definition: defer.hpp:270
CallableOnce(F &&f)
Definition: lambda.hpp:431
hashmap< U1, U2 > zip(const InputIterable1< U1, U1s... > &input1, const InputIterable2< U2, U2s... > &input2)
Definition: lambda.hpp:213
internal::Partial< typename std::decay< F >::type, typename std::decay< Args >::type... > partial(F &&f, Args &&...args)
Definition: lambda.hpp:364
Definition: cpp14.hpp:31
Definition: type_utils.hpp:619
Definition: lambda.hpp:30
void operator()(F &&f, Args &&...args)
Definition: lambda.hpp:396
R operator()(F &&f, Args &&...args)
Definition: lambda.hpp:385
Definition: hashmap.hpp:38
Definition: lambda.hpp:270
Definition: lambda.hpp:250
make_integer_sequence< std::size_t, N > make_index_sequence
Definition: cpp14.hpp:61
process::Future< Nothing > transform(process::Owned< Reader< T >> &&reader, const std::function< std::string(const T &)> &func, process::http::Pipe::Writer writer)
This is a helper function that reads records from a Reader, applies a transformation to the records a...
Definition: recordio.hpp:112
void put(const Key &key, Value &&value)
Definition: hashmap.hpp:104
Iterable< V > map(F &&f, const Iterable< U, Us... > &input)
Definition: lambda.hpp:46
Definition: attributes.hpp:24
R operator()(Args...args)&&
Definition: lambda.hpp:440
Try< uint32_t > type(const std::string &path)
OutputIterable< std::pair< U1, U2 > > zipto(const InputIterable1< U1, U1s... > &input1, const InputIterable2< U2, U2s... > &input2)
Definition: lambda.hpp:179
Definition: lambda.hpp:382
Try< Nothing > bind(int_fd s, const Address &address)
Definition: network.hpp:46
#define RETURN(...)
Definition: lambda.hpp:239
Definition: lambda.hpp:414