{"id":18676240,"url":"https://github.com/cgarciae/phi","last_synced_at":"2026-03-17T15:31:35.849Z","repository":{"id":57452255,"uuid":"73657412","full_name":"cgarciae/phi","owner":"cgarciae","description":"Functional Programming + Python - Pain","archived":false,"fork":false,"pushed_at":"2018-08-13T14:14:07.000Z","size":1420,"stargazers_count":136,"open_issues_count":1,"forks_count":7,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-07-31T21:16:37.904Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cgarciae.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2016-11-14T02:00:21.000Z","updated_at":"2025-01-21T19:33:12.000Z","dependencies_parsed_at":"2022-09-02T10:12:00.937Z","dependency_job_id":null,"html_url":"https://github.com/cgarciae/phi","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/cgarciae/phi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgarciae%2Fphi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgarciae%2Fphi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgarciae%2Fphi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgarciae%2Fphi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cgarciae","download_url":"https://codeload.github.com/cgarciae/phi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgarciae%2Fphi/sbom","scorecard":{"id":272302,"data":{"date":"2025-08-11","repo":{"name":"github.com/cgarciae/phi","commit":"87fd7100a76f823232f4fd8360498b4b80675265"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 2/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: docker/python27.dockerfile:1: pin your Docker image by updating python:2.7 to python:2.7@sha256:cfa62318c459b1fde9e0841c619906d15ada5910d625176e24bf692cf8a2601d","Warn: containerImage not pinned by hash: docker/python35.dockerfile:1: pin your Docker image by updating python:3.5 to python:3.5@sha256:42a37d6b8c00b186bdfb2b620fa8023eb775b3eb3a768fd3c2e421964eee9665","Warn: pipCommand not pinned by hash: docker/python27.dockerfile:6","Warn: pipCommand not pinned by hash: docker/python27.dockerfile:7","Warn: pipCommand not pinned by hash: docker/python27.dockerfile:8","Warn: pipCommand not pinned by hash: docker/python27.dockerfile:9","Warn: pipCommand not pinned by hash: docker/python27.dockerfile:10","Warn: pipCommand not pinned by hash: docker/python27.dockerfile:11","Warn: pipCommand not pinned by hash: docker/python27.dockerfile:12","Warn: pipCommand not pinned by hash: docker/python35.dockerfile:6","Warn: pipCommand not pinned by hash: docker/python35.dockerfile:7","Warn: pipCommand not pinned by hash: docker/python35.dockerfile:8","Warn: pipCommand not pinned by hash: docker/python35.dockerfile:9","Warn: pipCommand not pinned by hash: docker/python35.dockerfile:10","Warn: pipCommand not pinned by hash: docker/python35.dockerfile:11","Warn: pipCommand not pinned by hash: docker/python35.dockerfile:12","Info:   0 out of   2 containerImage dependencies pinned","Info:   0 out of  14 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T13:41:20.564Z","repository_id":57452255,"created_at":"2025-08-17T13:41:20.564Z","updated_at":"2025-08-17T13:41:20.564Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273746786,"owners_count":25160646,"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","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-11-07T09:28:25.042Z","updated_at":"2026-03-17T15:31:30.811Z","avatar_url":"https://github.com/cgarciae.png","language":"Python","readme":"# Phi\nPhi library for functional programming in Python that intends to remove as much of the pain as possible from your functional programming experience in Python.\n\n## Import\nFor demonstration purposes we will import right now everything we will need for the rest of the exercises like this\n\n```python\nfrom phi.api import *\n```\nbut you can also import just what you need from the `phi` module.\n\n## Math-like Lambdas\n\n#### Operators\n\nUsing the `P` object you can create quick lambdas using any operator. You can write things like\n\n```python\nf = (P * 6) / (P + 2)  # lambda x: (x * 6) / (x + 2)\n\nassert f(2) == 3  # (2 * 6) / (2 + 2) == 12 / 4 == 3\n```\n\nwhere the expression for `f` is equivalent to\n\n```python\nf = lambda x: (x * 6) / (x + 2)\n```\n\n#### getitem\nYou can also use the `P` object to create lambdas that access the items of a collection\n\n```python\nf = P[0] + P[-1]  # lambda x: x[0] + x[-1]\n\nassert f([1,2,3,4]) == 5   #1 + 4 == 5\n```\n\n#### field access\nIf you want create lambdas that access the field of some entity you can use the `Rec` (for Record) object an call that field on it\n```python\nfrom collections import namedtuple\nPoint = namedtuple('Point', ['x', 'y'])\n\nf = Rec.x + Rec.y  # lambda p: p.x + p.y\n\nassert f(Point(3, 4)) == 7   # point.x + point.y == 3 + 4 == 7\n```\n#### method calling\nIf you want to create a lambda that calls the method of an object you use the `Obj` object and call that method on it with the parameters\n\n```python\nf = Obj.upper() + \", \" + Obj.lower()  # lambda s: s.upper() + \", \" + s.lower()\n\nassert f(\"HEllo\") == \"HELLO, hello\"   # \"HEllo\".upper() + \", \" + \"HEllo\".lower() == \"HELLO\" + \", \" + \"hello\" == \"HELLO, hello\"\n```\n\nHere no parameters were needed but in general\n\n```python\nf = Obj.some_method(arg1, arg2, ...) #lambda obj: obj.some_method(arg1, arg2, ...)\n```\n\nis equivalent to\n\n```python\nf = lambda obj: obj.some_method(arg1, arg2, ...)\n```\n\n## Composition\n#### \u003e\u003e and \u003c\u003c\nYou can use the `\u003e\u003e` operator to *forward* compose expressions\n\n```python\nf = P + 7 \u003e\u003e math.sqrt  #executes left to right\n\nassert f(2) == 3  # math.sqrt(2 + 7) == math.sqrt(9) == 3\n```\n\nThis is preferred because it is more readable, but you can use the `\u003c\u003c` to compose them *backwards* just like the mathematical definition of function composition\n\n```python\nf =  math.sqrt \u003c\u003c P + 7 #executes right to left\n\nassert f(2) == 3  # math.sqrt(2 + 7) == math.sqrt(9) == 3\n```\n\n#### Seq and Pipe\nIf you need to do a long or complex composition you can use `Seq` (for 'Sequence') instead of many chained `\u003e\u003e`\n\n```python\nf = Seq(\n  str,\n  P + \"00\",\n  int,\n  math.sqrt\n)\n\nassert f(1) == 10  # sqrt(int(\"1\" + \"00\")) == sqrt(100) == 10\n```\n\nIf you want to create a composition and directly apply it to an initial value you can use `Pipe`\n\n```python\nassert 10 == Pipe(\n  1,  #input\n  str,  # \"1\"\n  P + \"00\",  # \"1\" + \"00\" == \"100\"\n  int,  # 100\n  math.sqrt  #sqrt(100) == 10\n)\n```\n\n## Combinators\n#### List, Tuple, Set, Dict\nThere are a couple of combinators like `List`, `Tuple`, `Set`, `Dict` that help you create compound functions that return the container types `list`, `tuple`, `set` and `dict` respectively. For example, you can pass `List` a couple of expressions to get a function that returns a list with the values of these functions\n\n```python\nf = List( P + 1, P * 10 )  #lambda x: [ x +1, x * 10 ]\n\nassert f(3) == [ 4, 30 ]  # [ 3 + 1, 3 * 10 ] == [ 4, 30 ]\n```\n\nThe same logic applies for `Tuple` and `Set`. With `Dict` you have to use keyword arguments\n\n```python\nf = Dict( x = P + 1, y = P * 10 )  #lambda x: [ x +1, x * 10 ]\n\nd = f(3)\n\nassert d == { 'x': 4, 'y': 30 }  # { 'x': 3 + 1, 'y': 3 * 10 } == { 'x': 4, 'y': 30 }\nassert d.x == 4   #access d['x'] via field access as d.x\nassert d.y == 30  #access d['y'] via field access as d.y\n```\n\nAs you see, `Dict` returns a custom `dict` that also allows *field access*, this is useful because you can use it in combination with `Rec`.\n\n#### State: Read and Write\nInternally all these expressions are implemented in such a way that they not only pass their computed values but also pass a **state** dictionary between them in a functional manner. By reading from and writing to this state dictionary the `Read` and `Write` combinators can help you \"save\" the state of intermediate computations to read them later\n\n```python\nassert [70, 30] == Pipe(\n  3,\n  Write(s = P * 10),  #s = 3 * 10 == 30\n  P + 5,  #30 + 5 == 35\n  List(\n    P * 2  # 35 * 2 == 70\n  ,\n    Read('s')  #s == 30\n  )\n)\n```\n\nIf you need to perform many reads inside a list -usually for output- you can use `ReadList` instead\n\n```python\nassert [2, 4, 22] == Pipe(\n    1,\n    Write(a = P + 1),  #a = 1 + 1 == 2\n    Write(b = P * 2),  #b = 2 * 2 == 4\n    P * 5,   # 4 * 5 == 20\n    ReadList('a', 'b', P + 2)  # [a, b, 20 + 2] == [2, 4, 22]\n)\n```\n`ReadList` interprets string elements as `Read`s, so the previous is translated to\n```python\nList(Read('a'), Read('b'), P + 2)\n```\n\n#### Then, Then2, ..., Then5, ThenAt\nTo create a partial expression from a function e.g.\n\n```python\ndef repeat_word(word, times, upper=False):\n  if upper:\n    word = word.upper()\n\n  return [ word ] * times\n```\n\nuse the `Then` combinator which accepts a function plus all but the *1st* of its `*args` + `**kwargs`\n\n```python\nf = P[::-1] \u003e\u003e Then(repeat_word, 3)\ng = P[::-1] \u003e\u003e Then(repeat_word, 3, upper=True)\n\nassert f(\"ward\") == [\"draw\", \"draw\", \"draw\"]\nassert g(\"ward\") == [\"DRAW\", \"DRAW\", \"DRAW\"]\n```\n\nand assumes that the *1st* argument of the function will be applied last, e.g. `word` in the case of `repeat_word`. If you need the *2nd* argument to be applied last use `Then2`, and so on. In general you can use `ThenAt(n, f, *args, **kwargs)` where `n` is the position of the argument that will be applied last. For example\n\n```python\n# since map and filter receive the iterable on their second argument, you have to use `Then2`\nf = Then2(filter, P % 2 == 0) \u003e\u003e Then2(map, P**2) \u003e\u003e list  #lambda x: map(lambda z: z**2, filter(lambda z: z % 2 == 0, x))\n\nassert f([1,2,3,4,5]) == [4, 16]  #[2**2, 4**2] == [4, 16]\n```\n\nBe aware that `P` already has the `map` and `filter` methods so you can write the previous more easily as\n\n```python\nf = P.filter(P % 2 == 0) \u003e\u003e P.map(P**2) \u003e\u003e list  #lambda x: map(lambda z: z**2, filter(lambda z: z % 2 == 0, x))\n\nassert f([1,2,3,4,5]) == [4, 16]  #[2**2, 4**2] == [4, 16]\n```\n\n#### Val\nIf you need to create a constant function with a given value use `Val`\n\n```python\nf = Val(42)  #lambda x: 42\n\nassert f(\"whatever\") == 42\n```\n\n#### Others\nCheck out the `With`, `If` and more, combinators on the documentation. The `P` object also offers some useful combinators as methods such as `Not`, `First`, `Last` plus **almost all** python built in functions as methods:\n\n```python\nf = Obj.split(' ') \u003e\u003e P.map(len) \u003e\u003e sum \u003e\u003e If( (P \u003c 15).Not(), \"Great! Got {0} letters!\".format).Else(\"Too short, need at-least 15 letters\")\n\nassert f(\"short frase\") == \"Too short, need at-least 15 letters\"\nassert f(\"some longer frase\") == \"Great! Got 15 letters!\"\n```\n\n## The DSL\nPhi has a small omnipresent DSL that has these simple rules:\n\n1. Any element of the class `Expression` is an element of the DSL. `P` and all the combinators are of the `Expression` class.\n2. Any callable of arity 1 is an element of the DSL.\n3. The container types `list`, `tuple`, `set`, and `dict` are elements of the DSL. They are translated to their counterparts `List`, `Tuple`, `Set` and `Dict`, their internal elements are forwarded.\n4. Any value `x` that does not comply with any of the previous rules is also an element of the DSL and is translated to `Val(x)`.\n\nUsing the DSL, the expression\n\n```python\nf = P**2 \u003e\u003e List( P, Val(3), Val(4) )  #lambda x: [ x**2]\n\nassert f(10) == [ 100, 3, 4 ]  # [ 10**2, 3, 4 ]  == [ 100, 3, 4 ]\n```\n\ncan be rewritten as\n\n```python\nf = P**2 \u003e\u003e [ P, 3, 4 ]\n\nassert f(10) == [ 100, 3, 4 ]  # [ 10 ** 2, 3, 4 ]  == [ 100, 3, 4 ]\n```\n\nHere the values `3` and `4` are translated to `Val(3)` and `Val(4)` thanks to the *4th* rule, and `[...]` is translated to `List(...)` thanks to the *3rd* rule. Since the DSL is omnipresent you can use it inside any core function, so the previous can be rewritten using `Pipe` as\n\n```python\nassert [ 100, 3, 4 ] == Pipe(\n  10,\n  P**2,  # 10**2 == 100\n  [ P, 3, 4 ]  #[ 100, 3, 4 ]\n)\n```\n\n#### F\nYou can *compile* any element to an `Expression` using `F`\n\n```python\nf = F((P + \"!!!\", 42, Obj.upper()))  #Tuple(P + \"!!!\", Val(42), Obj.upper())\n\nassert f(\"some tuple\") == (\"some tuple!!!\", 42, \"SOME TUPLE\")\n```\n\nOther example\n\n```python\nf = F([ P + n for n in range(5) ])  \u003e\u003e [ len, sum ]  # lambda x: [ len([ x, x+1, x+2, x+3, x+4]), sum([ x, x+1, x+2, x+3, x+4]) ]\n\nassert f(10) == [ 5, 60 ]  # [ len([10, 11, 12, 13, 14]), sum([10, 11, 12, 13, 14])] == [ 5, (50 + 0 + 1 + 2 + 3 + 4) ] == [ 5, 60 ]\n```\n\n## Fluent Programming\nAll the functions you've seen are ultimately methods of the `PythonBuilder` class which inherits from the `Expression`, therefore you can also [fluently](https://en.wikipedia.org/wiki/Fluent_interface) chain methods instead of using the `\u003e\u003e` operator. For example\n\n```python\nf = Dict(\n  x = 2 * P,\n  y = P + 1\n).Tuple(\n  Rec.x + Rec.y,\n  Rec.y / Rec.x\n)\n\nassert f(1) == (4, 1)  # ( x + y, y / x) == ( 2 + 2, 2 / 2) == ( 4, 1 )\n```\n\nThis more complicated previous example\n\n```python\nf = Obj.split(' ') \u003e\u003e P.map(len) \u003e\u003e sum \u003e\u003e If( (P \u003c 15).Not(), \"Great! Got {0} letters!\".format).Else(\"Too short, need at-least 15 letters\")\n\nassert f(\"short frase\") == \"Too short, need at-least 15 letters\"\nassert f(\"some longer frase\") == \"Great! Got 15 letters!\"\n```\n\ncan be be rewritten as\n\n```python\nf = (\n  Obj.split(' ')\n  .map(len)\n  .sum()\n  .If( (P \u003c 15).Not(),\n    \"Great! Got {0} letters!\".format\n  ).Else(\n    \"Too short, need at-least 15 letters\"\n  )\n)\n\nassert f(\"short frase\") == \"Too short, need at-least 15 letters\"\nassert f(\"some longer frase\") == \"Great! Got 15 letters!\"\n```\n\n## Integrability\n#### Register, Register2, ..., Register5, RegistarAt\nIf you want to have custom expressions to deal with certain data types, you can create a custom class that inherits from `Builder` or `PythonBuilder`\n\n```python\nfrom phi import PythonBuilder\n\nclass MyBuilder(PythonBuilder):\n  pass\n\nM = MyBuilder()\n```\n\nand register your function in it using the `Register` class method\n\n```python\ndef remove_longer_than(some_list, n):\n  return [ elem from elem in some_list if len(elem) \u003c= n ]\n\nMyBuilder.Register(remove_longer_than, \"my.lib.\")\n```\nOr better even use `Register` as a decorator\n```python\n@MyBuilder.Register(\"my.lib.\")\ndef remove_longer_than(some_list, n):\n  return [ elem for elem in some_list if len(elem) \u003c= n ]\n```\n\nNow the method `MyBuilder.remove_longer_than` exists on this class. You can then use it like this\n\n```python\nf = Obj.lower() \u003e\u003e Obj.split(' ') \u003e\u003e M.remove_longer_than(6)\n\nassert f(\"SoMe aRe LONGGGGGGGGG\") == [\"some\", \"are\"]\n```\n\nAs you see the argument `n = 6` was partially applied to `remove_longer_than`, an expression which waits for the `some_list` argument to be returned. Internally the `Registar*` method family uses the `Then*` method family.\n\n#### PatchAt\nIf you want to register a batch of functions from a module or class automatically you can use the `PatchAt` class method. It's an easy way to integrate an entire module to Phi's DSL. See `PatchAt`.\n\n#### Libraries\nPhi currently powers the following libraries that integrate with its DSL:\n\n* [PythonBuilder](https://cgarciae.github.io/phi/python_builder.m.html) : helps you integrate Python's built-in functions and keywords into the phi DSL and it also includes a bunch of useful helpers for common stuff. `phi`'s global `P` object is an instance of this class. [Shipped with Phi]\n* [TensorBuilder](https://github.com/cgarciae/tensorbuilder): a TensorFlow library enables you to easily create complex deep neural networks by leveraging the phi DSL to help define their structure.\n* NumpyBuilder: Comming soon!\n\n## Documentation\nCheck out the [complete documentation](https://cgarciae.github.io/phi/).\n\n## More Examples\nThe global `phi.P` object exposes most of the API and preferably should be imported directly. The most simple thing the DSL does is function composition:\n\n```python\nfrom phi.api import *\n\ndef add1(x): return x + 1\ndef mul3(x): return x * 3\n\nx = Pipe(\n    1.0,     #input 1\n    add1,  #1 + 1 == 2\n    mul3   #2 * 3 == 6\n)\n\nassert x == 6\n```\n\nUse phi [lambdas](https://cgarciae.github.io/phi/lambdas.m.html) to create the functions\n\n```python\nfrom phi.api import *\n\nx = Pipe(\n    1.0,      #input 1\n    P + 1,  #1 + 1 == 2\n    P * 3   #2 * 3 == 6\n)\n\nassert x == 6\n```\n\nCreate a branched computation instead\n\n```python\nfrom phi.api import *\n\n[x, y] = Pipe(\n    1.0,  #input 1\n    [\n        P + 1  #1 + 1 == 2\n    ,\n        P * 3  #1 * 3 == 3\n    ]\n)\n\nassert x == 2\nassert y == 3\n```\n\nCompose it with a function equivalent to `f(x) = (x + 3) / (x + 1)`\n\n```python\nfrom phi.api import *\n\n[x, y] = Pipe(\n    1.0,  #input 1\n    (P + 3) / (P + 1),  #(1 + 3) / (1 + 1) == 4 / 2 == 2\n    [\n        P + 1  #2 + 1 == 3\n    ,\n        P * 3  #2 * 3 == 6\n    ]\n)\n\nassert x == 3\nassert y == 6\n```\n\nGive names to the branches\n\n```python\nfrom phi.api import *\n\nresult = Pipe(\n    1.0,  #input 1\n    (P + 3) / (P + 1),  #(1 + 3) / (1 + 1) == 4 / 2 == 2\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    )\n)\n\nassert result.x == 3\nassert result.y == 6\n```\n\nDivide `x` by `y`.\n\n```python\nfrom phi.api import *\n\nresult = Pipe(\n    1.0,  #input 1\n    (P + 3) / (P + 1),  #(1 + 3) / (1 + 1) == 4 / 2 == 2\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    ),\n    Rec.x / Rec.y  #3 / 6 == 0.5\n)\n\nassert result == 0.5\n```\n\nSave the value from the `(P + 3) / (P + 1)` computation as `s` and load it at the end in a branch\n\n```python\nfrom phi.api import *\n\n[result, s] = Pipe(\n    1.0,  #input 1\n    Write(s = (P + 3) / (P + 1)), #s = 4 / 2 == 2\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    ),\n    [\n        Rec.x / Rec.y  #3 / 6 == 0.5\n    ,\n        Read('s')  #s == 2\n    ]\n)\n\nassert result == 0.5\nassert s == 2\n```\n\nAdd 3 to the loaded `s` for fun and profit\n\n```python\nfrom phi.api import *\n\n[result, s] = Pipe(\n    1.0,  #input 1\n    Write(s = (P + 3) / (P + 1)), #s = 4 / 2 == 2\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    ),\n    [\n        Rec.x / Rec.y  #3 / 6 == 0.5\n    ,\n        Read('s') + 3  # 2 + 3 == 5\n    ]\n)\n\nassert result == 0.5\nassert s == 5\n```\n\nUse the `Read` and `Write` field access lambda style just because\n\n```python\nfrom phi.api import *\n\n[result, s] = Pipe(\n    1.0,  #input 1\n    (P + 3) / (P + 1), #4 / 2 == 2\n    Write.s,  #s = 2\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    ),\n    [\n        Rec.x / Rec.y  #3 / 6 == 0.5\n    ,\n        Read.s + 3  # 2 + 3 == 5\n    ]\n)\n\nassert result == 0.5\nassert s == 5\n```\n\nAdd an input `Val` of 9 on a branch and add to it 1 just for the sake of it\n\n```python\nfrom phi.api import *\n\n[result, s, val] = Pipe(\n    1.0,  #input 1\n    (P + 3) / (P + 1), Write.s,  #4 / 2 == 2, saved as 's'\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    ),\n    [\n        Rec.x / Rec.y  #3 / 6 == 0.5\n    ,\n        Read.s + 3  # 2 + 3 == 5\n    ,\n        Val(9) + 1  #input 9 and add 1, gives 10\n    ]\n)\n\nassert result == 0.5\nassert s == 5\nassert val == 10\n```\n\nDo the previous only if `y \u003e 7` else return `\"Sorry, come back later.\"`\n\n```python\nfrom phi.api import *\n\n[result, s, val] = Pipe(\n    1.0,  #input 1\n    (P + 3) / (P + 1), Write.s,  #4 / 2 == 2, saved as 's'\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    ),\n    [\n        Rec.x / Rec.y  #3 / 6 == 0.5\n    ,\n        Read.s + 3  # 2 + 3 == 5\n    ,\n        If( Rec.y \u003e 7,\n            Val(9) + 1  #input 9 and add 1, gives 10    \n        ).Else(\n            \"Sorry, come back later.\"\n        )\n    ]\n)\n\nassert result == 0.5\nassert s == 5\nassert val == \"Sorry, come back later.\"\n```\n\nNow, what you have to understand that everything you've done with these expression is to create and apply a single function. Using `Seq` we can get the standalone function and then use it to get the same values as before\n\n```python\nfrom phi.api import *\n\nf = Seq(\n    (P + 3) / (P + 1), Write.s,  #4 / 2 == 2, saved as 's'\n    dict(\n        x = P + 1  #2 + 1 == 3\n    ,\n        y = P * 3  #2 * 3 == 6\n    ),\n    [\n        Rec.x / Rec.y  #3 / 6 == 0.5\n    ,\n        Read.s + 3  # 2 + 3 == 5\n    ,\n        If( Rec.y \u003e 7,\n            Val(9) + 1  #input 9 and add 1, gives 10    \n        ).Else(\n            \"Sorry, come back later.\"\n        )\n    ]\n)\n\n[result, s, val] = f(1.0)\n\nassert result == 0.5\nassert s == 5\nassert val == \"Sorry, come back later.\"\n```\n\n### Even More Examples\n\n```python\nfrom phi.api import *\n\navg_word_length = Pipe(\n    \"1 22 333\",\n    Obj.split(\" \"), # ['1', '22', '333']\n    P.map(len), # [1, 2, 3]\n    P.sum() / P.len() # sum([1,2,3]) / len([1,2,3]) == 6 / 3 == 2\n)\n\nassert 2 == avg_word_length\n```\n\n```python\nfrom phi.api import *\n\nassert False == Pipe(\n    [1,2,3,4], P\n    .filter(P % 2 != 0)   #[1, 3], keeps odds\n    .Contains(4)   #4 in [1, 3] == False\n)\n```\n\n```python\nfrom phi.api import *\n\nassert {'a': 97, 'b': 98, 'c': 99} == Pipe(\n    \"a b c\", Obj\n    .split(' ').Write.keys  # keys = ['a', 'b', 'c']\n    .map(ord),  # [ord('a'), ord('b'), ord('c')] == [97, 98, 99]\n    lambda it: zip(Ref.keys, it),  # [('a', 97), ('b', 98), ('c', 99)]\n    dict   # {'a': 97, 'b': 98, 'c': 99}\n)\n```\n\n## Installation\n\n```sh\npip install phi\n```\n\n#### Bleeding Edge Release\n\n```sh\npip install git+https://github.com/cgarciae/phi.git@develop\n```\n\n## Status\n* Version: ![Latest Tagged Release Number](https://img.shields.io/github/tag/cgarciae/phi.svg?%3FlongCache=true\u0026label=).\n* Documentation coverage: 100%. Please create an issue if documentation is unclear, it is a high priority of this library.\n* Milestone: reach 1.0.0 after feedback from the community.\n","funding_links":[],"categories":["Awesome Functional Python"],"sub_categories":["Libraries"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcgarciae%2Fphi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcgarciae%2Fphi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcgarciae%2Fphi/lists"}