{"id":13418024,"url":"https://github.com/beark/ftl","last_synced_at":"2025-03-15T02:32:25.670Z","repository":{"id":7878686,"uuid":"9253071","full_name":"beark/ftl","owner":"beark","description":"C++ template library for fans of functional programming","archived":false,"fork":false,"pushed_at":"2019-01-07T20:37:22.000Z","size":2509,"stargazers_count":994,"open_issues_count":4,"forks_count":69,"subscribers_count":73,"default_branch":"master","last_synced_at":"2024-07-31T22:40:42.659Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"zlib","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/beark.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-04-06T00:12:01.000Z","updated_at":"2024-07-16T12:15:41.000Z","dependencies_parsed_at":"2022-09-10T21:50:28.642Z","dependency_job_id":null,"html_url":"https://github.com/beark/ftl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beark%2Fftl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beark%2Fftl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beark%2Fftl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beark%2Fftl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beark","download_url":"https://codeload.github.com/beark/ftl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221532186,"owners_count":16838913,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T22:00:57.349Z","updated_at":"2025-03-15T02:32:25.660Z","avatar_url":"https://github.com/beark.png","language":"C++","readme":"FTL - The Functional Template Library\n=====================================\n\n[![Build Status](https://travis-ci.org/beark/ftl.png?branch=master)](https://travis-ci.org/beark/ftl)\n\nC++ template library for fans of functional programming.\n\nThis project is currently on hiatus. Not sure when/if I'll resume work on it.\n\nIf you want to play around with it anyway, you need to compile in C++11 mode with gcc-4.8 or later or clang-3.2 or later. Not sure if newer MSVC versions would work or not.\n\nTutorials\n---------\n* [Parser Combinator Part I: Simple Parser](docs/Parsec-I.md)\n* [Parser Combinator Part II: Parser Generator Library](docs/Parsec-II.md)\n\nShowcases\n---------\nA couple of quick showcases of some rather neat things the FTL gives you.\n\n### Curried Function Calling\nOne of the typically functional conventions brought to C++ by FTL is support for curried functions and function objects. A curried function is an n-ary function that can be invoked one argument at a time; each step returning a _new_ function object of arity _n-1_. Once enough parameters have been applied (when _n_ reaches _0_), the actual computation is performed and the result is returned. One of the uses of this is to achieve very convenient partial function application. For example:\n```cpp\nauto plus = ftl::curry(std::plus\u003cint\u003e);\nauto addOne = plus(1);\n\nauto x = addOne(2); // x = 3\nauto y = addOne(x); // y = 4\n```\nAs mentioned, all of the function objects provided by FTL are curried by default and do not require an `ftl::curry` call first. Partial application is thus in many cases extremely concise and clean. Note, however, that without, for instance, a wrapping lambda function, it is not possible to partially apply parameters other than in the exact order they appear. For example, the following are all valid:\n```cpp\nauto f = curriedTernaryFn(1);\nauto g = curriedTernaryFn(1,2);\n\nf(2)(3) == f(2,3) \u0026\u0026 f(2,3) == g(3); // true\n```\nBut it is not possible to \"skip\" a parameter or leave a placeholder, as in:\n```cpp\nusing std::placeholders::_1;\nauto f = std::bind(ternaryFn, _1, 2, 3);\n```\nCurrying by itself is a very nice thing to have, but what _truly_ makes it shine is when used in combination with e.g. higher order functions. See for instance applicative style coding, which is not nearly as nice without currying.\n\n### Expanding The Standard Library\nOne of the nice things about FTL is that it does not try to replace or supercede the standard library, it tries to _expand_ it when possible. These expansions include giving existing types concept instances for e.g. Functor, Monad, Monoid, and others. For example, in FTL, `std::shared_ptr` is a monad. This means we can sequence a series of operations working on shared pointers without ever having to explicitly check for validity\u0026mdash;while still being assured there are no attempts to access an invalid pointer.\n\nFor example, given\n```cpp\nshared_ptr\u003ca\u003e foo();\nshared_ptr\u003cb\u003e bar(a);\n```\n\nWe can simply write\n```cpp\nshared_ptr\u003cb\u003e ptr = foo() \u003e\u003e= bar;\n```\n\nInstead of\n```cpp\nshared_ptr\u003cb\u003e ptr(nullptr);\nauto ptra = foo();\nif(ptra) {\n    ptr = bar(*ptra);\n}\n```\n\nWhich would be the equivalent FTL-less version of the above.\n\nMonadic code may perhaps often look strange if you're not used to all the operators, but once you've got that, reading it becomes amazingly easy and clear. `operator\u003e\u003e=` above is used to sequence two monadic computations, where the second is dependant on the result of the first. Exactly what it does varies with monad instance, but in the case of `shared_ptr`, it essentially performs `nullptr` checks and either aborts the expression (returning a `nullptr` initialised `shared_ptr`), or simply passes the valid result forward.\n\nOther types that have been similarly endowed with new powers include: `std::future`, `std::list`, `std::vector`, `std::forward_list`, `std::map`, `std::unordered_map`, `std::set`, and more.\n\n### Sum/Union Types With Pattern Matching\nFor those not familiar with sum types, they can be viewed as tagged unions. In other words, they are defined at compile time by a set of possible types, and at run-time their value will be of exactly one of those types at any particular instance. A quick example:\n```cpp\nftl::sum_type\u003cstd::string,int\u003e x{ftl::constructor\u003cint\u003e(), 3};\n```\nHere, `x`, can take on any value that is either of type `std::string` or `int` and we've initialised it to be the integer value `3`. It is necessary to use the `constructor\u003cT\u003e` tag to specify what type to initialise to, because some types may have constructors accepting similar or even the same arguments.\n\nOnce we have our sum type, we can now pattern match\u0026mdash;or `switch` on its type, if you prefer\u0026mdash;to get a guaranteed safe way of accessing its value:\n```cpp\nint y = x.match(\n    [](int x){ return x+1; },           // This function will be invoked, with\n                                        // 3 as parameter\n\n    [](const std::string\u0026){ return 0; } // This function would have been invoked\n                                        // if x was a string\n);\n```\nUnlike in a language with first class support for pattern matching, in FTL we are unfortunately forced to use functions as match clauses, as you can see above. This makes it slightly less compelling, but we do get the same static guarantees at least: FTL checks at compile time that you've covered every possible type the matched sum type could take on.\n\nNote that `match` must return a value by default, to encourage the functional way where everything is an expression and therefore returns a value. This means, of course, that all match clauses must return values such that `std::common_type` can find a common type they are all implicitly convertible to.\n\nIf the above expression-semantic of `match` is not actually desirable, there is also `matchE`, which does \"effectful\" matches:\n```cpp\nx.matchE(\n    [](int\u0026 x){ ++x; },\n    [](const std::string\u0026 x){ std::cout \u003c\u003c x; }\n);\n```\n\nAnother possible point of inconvenience is if we have a complicated sum type of many sub-types. It can be quite a bother to write out every match case explicitly then, especially if we're only interested in one or a couple. Enter `otherwise`:\n```cpp\nftl::sum_type\u003cA,B,C,D\u003e x{...};\nauto r = x.match(\n    [](const B\u0026 b){ return f(b); }, // This function will be invoked only if\n                                    // x's value is of type B\n\n    [](otherwise){ return g(); }    // This function will be invoked if x's\n                                    // value is of any type except B\n);\n```\nIt is of course possible to have several specific match clauses before the otherwise-clause, but make sure to always put `otherwise` last\u0026mdash;or else any clause appearing below it will never actually be executed. Simply put, matching is done in the order the expressions appear.\n\nThe true usefulness of `sum_type` isn't so much the case where you use it directly. It's when you use it to quickly and cleanly create new data types. For example, did you know both `ftl::maybe` and `ftl::either` are really just simple type aliases of `sum_type`? It's true, `maybe` is defined simply as:\n```cpp\ntemplate\u003ctypename T\u003e\nusing maybe = sum_type\u003cT,Nothing\u003e;\n```\nThat takes care of the vast majority of the logic required for `maybe`, though there is a number of convenience functions and definitions in addition.\n\n### Applying Applicatives\nAdding a bit of the Applicative concept to the mix, we can do some quite concise calculations. Now, if we are given:\n```cpp\nint algorithm(int, int, int);\nftl::maybe\u003cint\u003e maybeGetAValue();\nftl::maybe\u003cint\u003e maybeGetAnother();\nftl::maybe\u003cint\u003e maybeGetAThird();\n```\nwhere `ftl::maybe` is a type similar to e.g. `boost::optional`, provided by FTL (mostly to make sure there are no external dependencies save the standard library).\n\nThen we can compute:\n```cpp\n/* ftl::operator% is short for ftl::fmap, basically the classic \"map\" function\n * of functional programming.\n * Similarly, ftl::operator* is short for ftl::aapply, the work horse function of\n * applicative programming style. It basically applies a function (the left hand\n * side) to one argument at a time (the right hand side).\n */\nusing ftl::operator%;\nusing ftl::operator*;\nauto result =\n\tftl::curry(algorithm) % maybeGetAValue() * maybeGetAnother() * maybeGetAThird();\n```\nwhich would compute the result of algorithm, but only if every one of the `maybe` functions returned a value.\n\nIn other words, without Functor's `fmap` and Applicative's `aapply`, it would have looked something like:\n```cpp\nftl::maybe\u003cint\u003e result;\nauto x = maybeGetAValue(), y = maybeGetAnother(), z = maybeGetAThird();\nif(x.is\u003cint\u003e() \u0026\u0026 y.is\u003cint\u003e() \u0026\u0026 z.is\u003cint\u003e()) {\n    result = ftl::value(algorithm(ftl::get\u003cint\u003e(x), ftl::get\u003cint\u003e(y), ftl::get\u003cint\u003e(z)));\n}\n```\nIf `algorithm` had happened to be wrapped in an `ftl::function`, or else be one of the built-in, curried-by-default function objects of FTL, then the `curry` call could have been elided for even cleaner code.\n\nExactly what operation is done by `apply` varies from type to type. For example, with containers, it generally implies combining their elements in every possible combination. Thus\n```cpp\ncurry(std::plus\u003cint\u003e) % std::list\u003cint\u003e{1,2} * std::list\u003cint\u003e{5,10};\n```\ncan be read as \"for each element in `{1,2}`, combine it using `std::plus\u003cint\u003e` with each element in `{5,10}`\", resulting in the list `{6, 11, 7, 12}`. Of course, what happens on a more technical level is that `std::plus\u003cint\u003e` is partially applied to each element in the first list, resulting in a list of unary functions, that in turn gets applied to each element in the second list.\n\nA much less technical way of thinking\u0026mdash;to gain some intuition for how applicative expressions behave\u0026mdash;could be that `operator%` is an alias for function application, and `operator*` separates parameters. Except the parameters happen to be wrapped in some \"context\" or \"container\".\n\nThis type of function application scales to arbitrary arity.\n\n### Transformers\nNo, not as in Optimus Prime! As in a monad transformer: a type transformer that takes one monad as parameter and \"magically\" adds functionality to it in the form of one of many other monads. For example, let's say you want to add the functionality of the `maybe` monad to the list monad. You'd have to create a new type that combines the powers, then write all of those crazy monad instances and whatnot, right? Wrong!\n\n```cpp\ntemplate\u003ctypename T\u003e\nusing listM = ftl::maybeT\u003cstd::list\u003cT\u003e\u003e;\n```\nBam! All the powers of lists and `maybe`s in one! What exactly does that mean though? Well, let's see if we can get an intuition for it.\n\n```cpp\n// With the inplace tag, we can call list's constructor directly\nlistM\u003cint\u003e ms{ftl::inplace_tag(), ftl::value(1), ftl::maybe\u003cint\u003e{}, ftl::value(2)};\n\n// Kind of useless, but demonstrates what's going on\nfor(auto m : *ms) {\n    m.matchE(\n        [](int x){ std::cout \u003c\u003c x \u003c\u003c \", \"; },\n        [](ftl::Nothing){ std::cout \u003c\u003c \"nothing, \"; }\n    );\n}\nstd::cout \u003c\u003c std::endl;\n```\nIf run, the above would produce:\n```\n1, nothing, 2, \n```\nSo, pretty much a list of `maybe`s then, what's the point? The point is, the new type `listM` is a monad, in pretty much the same way as `std::list` is, _except_ we can apply, bind, and map functions that work with `T`s. That is, given the above list, `ms`, we can do:\n\n```cpp\nauto ns = [](int x){ return x-1; } % ms;\n\n// Let's say this invokes the same print loop as before\nprint(ns);\n``` \nSame deal, but if `ms` was a regular, untransformed list of `maybe`:\n```cpp\nauto ns = [](maybe\u003cint\u003e x){\n    return x.match(\n        [](int x){ return ftl::value(x-1); },\n        [](otherwise){ return ftl::nothing\u003cint\u003e(); }\n    );\n} % ms;\n\nprint(ns);\n``` \nOutput (in both cases):\n```\n0, nothing, 1, \n```\nSo, basically, this saves us the trouble of having to check for nothingness in the elements of `ns` (coincidentally\u0026mdash;or not\u0026mdash;exactly what the maybe monad does: allow us to elide lots of ifs or pattern matches). \n\nRight, this is kinda neat, but not really all that exciting yet. The excitement comes when we stop to think a bit before we just arbitrarily throw together a couple of monads. For instance, check out the magic of the `either`-transformer on top of the `function` monad in the parser generator tutorial [part 2](docs/Parsec-II.md).\n\n### Yet More Concepts\nIn addition to the above concepts, FTL contains several more of the classic Haskell type classes: Monoids, Foldables, Zippables, and Orderables. These all express nice and abstracted interfaces that apply to many types, both built-in, standard library ones, and user defined ones (assuming one adds a concept instance). For instance, the code below uses the fact that an ordering is a Monoid to sort a vector of `MyType` by first one of its properties, then another.\n```cpp\nusing namespace ftl;\nstd::sort(vec.begin(), vec.end(),\n    asc(comparing(\u0026MyType::someProperty) ^ comparing(\u0026MyType::anotherProperty))\n);\n```\nThe equivalent version without FTL might look a bit like this:\n```cpp\nstd::sort(vec.begin(), vec.end(), [](const MyType\u0026 lhs, const MyType\u0026 rhs){\n    return lhs.someProperty() == rhs.someProperty()\n        ? lhs.anotherProperty() \u003c rhs.anotherProperty()\n        : lhs.someProperty() \u003c rhs.someProperty();\n});\n```\nPersonally, I find the former a lot cleaner and easier to read. The sort order is explicitly stated (`asc`), and then it just straight up says that we sort by comparing first `someProperty` and then `anotherProperty`. Fewer chances of subtle typos, less chance of screwing up the comparisons, much easier to extend, what's not to like?\n\n","funding_links":[],"categories":["TODO scan for Android support in followings","C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeark%2Fftl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeark%2Fftl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeark%2Fftl/lists"}