printf

.... printf originated from the C programming language and has been a headache since then because a proper call of printf requires the number and types of arguments to match the format string; otherwise, it may visit a wild memory or a wrong register. In recent versions of GCC this issue is addressed by type checks hard coded into the compiler itself, which is ugly because the compiler should not be caring about the safety of applications of a specific function....



The key issue here is that, considering the curried version, the type of the returned function of applying the format string to printf , actually depends on the value of that format string. That is to say, printf "%s" : String -> String , and printf "%d%s%d" : Int -> String -> Int -> String , and so on. This is where dependent type comes in: in dependently typed language, the type of returned values of functions can be dependent on value of the arguments; .... ---- Zesen Qian (ICFP'2106)

Currying

int multiply(int i, int j) { return i*j; } auto curry = [](auto binary) { return [=](int i) { return [=](int j) { return binary(i, j); }; }; }; auto mul = curry(multiply); auto a = mul(10); auto b = a(20); std:: cout << b << std::endl; // prints 200

multiple

mul

a

Currying printf--dependently

printf

operator ""

#include <utility> template <char... chars> using CharSeq = std::integer_sequence<char, chars...>; template <typename T, T... chars> constexpr CharSeq<chars...> operator""_lift() { return { }; }

CharSeq

std::integer_sequence<char, ...>

_lift

CharSeq

"cpptruths"_lift

std::integer_sequence<char,'c','p','p','t','r','u','t','h','s'>

#include <boost/core/demangle.hpp> auto cpptruths = "cpptruths"_lift; std::cout << boost::core::demangle(typeid(decltype(cpptruths)).name()) << "

";

CharSeq

"%d"

int

"%s"

const char *

StringToTuple

template <class Head, class Tuple> struct Append; template <class Head, class... Args> struct Append<Head, std::tuple<Args...>> { using type = std::tuple<Head, Args...>; }; template <class CharSeq> struct StringToTuple; template <> struct StringToTuple<CharSeq<>> { using type = std::tuple<>; }; template <char Any, char... chars> struct StringToTuple<CharSeq<Any, chars...>> { using type = typename StringToTuple<CharSeq<chars...>>::type; }; template <char... chars> struct StringToTuple<CharSeq<'%', 's', chars...>> { using tail = typename StringToTuple<CharSeq<chars...>>::type; using type = typename Append<const char *, tail>::type; }; template <char... chars> struct StringToTuple<CharSeq<'%', 'd', chars...>> { using tail = typename StringToTuple<CharSeq<chars...>>::type; using type = typename Append<int, tail>::type; }; template <char... chars> struct StringToTuple<CharSeq<'%', 'f', chars...>> { using tail = typename StringToTuple<CharSeq<chars...>>::type; using type = typename Append<double, tail>::type; }; template <char... chars> struct StringToTuple<CharSeq<'%', 'u', chars...>> { using tail = typename StringToTuple<CharSeq<chars...>>::type; using type = typename Append<unsigned int, tail>::type; }; auto format = "%s%d"_lift; StringToTuple<decltype(format)>::type FormatTuple; // std::tuple<const char *, int>

StringToTuple

CharSeq

Append

CharSeq

char Any

curried_printf_impl

CharSeq

template <class CharSeq> auto curried_printf_impl(const char * fmt, CharSeq) { using FormatType = typename StringToTuple<CharSeq>::type; std::cout << boost::core::demangle(typeid(FormatType).name()) << "

"; return curry<FormatType>::apply(fmt); } #define curried_printf(X) curried_printf_impl(X, X##_lift)

curry

FormatType

curried_printf

curry

template <class Tuple> struct curry; template <class Head, class... Tail> struct curry<std::tuple<Head, Tail...>> { template<class... Args> static auto apply(Args&&... args) { return [args...](Head h) { return curry<std::tuple<Tail...>>::apply(args..., h); }; } }; template <class Head> struct curry<std::tuple<Head>> { template <class... Args> static auto apply(Args&&... args) { return [args...](Head h) { return printf(args..., h); }; } }; template <> struct curry<std::tuple<>> { static auto apply(const char * fmt) { return printf(fmt); } };

curry

apply

Head

curry<Tail...>::apply

curry

args...

curried_printf_impl

int main(void) { curried_printf("C++ Rocks%s %d %f

")("!!")(10)(20.30); curried_printf("C++ Rocks!!

"); return 0; }

Avoiding Copying Arguments

std::move

std::tuple

template <class Head, class... Tail> struct curry<std::tuple<Head, Tail...>> { template<class... Args> static auto apply(Args&&... args) { return [t=std::make_tuple(std::move(args)...)](Head h) { // Move each element of t and h to curry<std::tuple<Tail...>>::apply somehow. }; } };

curry::apply

Head

std::apply

std::invoke

Head

Head

Currying Arbitrary Functions

// In C++17, std::experimental::apply can replace the following execute function. template <size_t... Indices, class Tuple, class Func> auto execute(std::integer_sequence<size_t, Indices...>, Tuple&& tuple, Func&& func) { return func(std::get<Indices>(std::forward<Tuple>(tuple))...); } template <int I, class AllArgs, class Tuple> struct dyn_curry; template <int I, class AllArgs, class Head, class... Tail> struct dyn_curry<I, AllArgs, std::tuple<Head, Tail...>> { enum { Index = std::tuple_size<AllArgs>::value - I }; template <class Func> static auto apply(std::shared_ptr<AllArgs> shptr, Func&& func) { return [shptr, func=std::move(func)](const Head &h) mutable { std::get<Index>(*shptr) = h; return dyn_curry<I-1, AllArgs, std::tuple<Tail...>>::apply(shptr, std::move(func)); }; } }; template <class AllArgs, class Head> struct dyn_curry<1, AllArgs, std::tuple<Head>> { enum { Index = std::tuple_size<AllArgs>::value - 1 }; using IntSeq = std::make_index_sequence<std::tuple_size<AllArgs>::value>; template <class Func> static auto apply(std::shared_ptr<AllArgs> shptr, Func&& func) { return [shptr, func=std::move(func)](const Head &h) mutable { std::get<Index>(*shptr) = h; return execute(IntSeq(), sd::move(*shptr), std::move(func)); }; } }; template <class Ret, class... Args> auto arb_curry(Ret (&func) (Args...)) { using AllArgs = std::tuple<std::decay_t<Args>...>; std::cout << boost::core::demangle(typeid(AllArgs).name()) << "

"; std::shared_ptr<AllArgs> shptr(new AllArgs); return dyn_curry<std::tuple_size<AllArgs>::value, AllArgs, AllArgs>::apply(shptr, func); } template <class Ret> Ret arb_curry(Ret (&func) ()) { return func(); } int print_add(std::string &msg, int &j, int k) { std::cout << msg; return j+k; } int identity(int i) { return i; } int foo() { return printf("foo

"); } int main(void) { arb_curry(foo); std::cout << arb_curry(identity)(99) << std::endl; auto a = arb_curry(print_add); auto b = a("Adding two integers: "); auto c = b(20); auto d = c(30); std::cout << d << std::endl; //prints 60. return 0; }

This implementation uses an explicit compile-time index to copy arguments in to the right slot in the tuple of arguments. There's more type related noise here because each call to apply passes the shared_ptr of the tuple type to the inner lambda. The final dispatch to the function is implemented in the execute function that expands all the arguments in the tuple as function arguments. In C++17, std::experimental::apply can replace the execute function.

Conclusion