├── .gitignore ├── LICENSE ├── README.md ├── lib └── curry.ex ├── mix.exs ├── priv └── Example.ex └── test ├── curry_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 niahoo osef 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Curry 2 | ===== 3 | 4 | ### Howto 5 | 6 | Curry allows you to define curried functions : 7 | 8 | ```elixir 9 | defmodule Curry.Example do 10 | 11 | # 1. Use the Curry module 12 | use Curry 13 | 14 | # 2. use 'curry' instead of 'def' to define functions 15 | curry add(a,b), do: a + b 16 | 17 | # also, use 'curryp' instead of 'defp' 18 | curryp secret(replaced, by, sentence) do 19 | sentence |> String.replace(replaced, by) 20 | end 21 | 22 | def test do 23 | # 3. The first call is not 'dotted' 24 | increment = add(1) 25 | 26 | # 4. The following calls are 'dotted' 27 | 100 = increment.(99) 28 | 29 | encoder = secret("o").("_") 30 | "hell_" = encoder.("hello") 31 | end 32 | 33 | end 34 | ``` 35 | 36 | ### Limitations 37 | 38 | It is not currently possible to curry functions with multiple clauses, or 39 | functions that share their name with arity/1 functions. 40 | 41 | Look at the following definitions, we have a defaut loop repetition amount 42 | amount, and a match on the value `0` to stop the loop : 43 | 44 | ```elixir 45 | curry loop(value, 0), do: :ok 46 | curry loop(value, i) do 47 | do_something(value) 48 | loop(value, i - 1) 49 | end 50 | 51 | def loop(value), do: loop(value, @default_loop_repeat) 52 | ``` 53 | 54 | The currying macro will rewrite it as the following : 55 | 56 | ```elixir 57 | def loop(value), do: fn (0) -> :ok end 58 | def loop(value) do 59 | fn (i) -> 60 | do_something(value) 61 | loop(value, i - 1) 62 | end 63 | end 64 | 65 | def loop(value), do: loop(value, @default_loop_repeat) 66 | ``` 67 | 68 | So, the first `loop/1` definition will always match, and always return a `fn` that will only accept `0`. 69 | 70 | Plus, the original `loop/1` isn't available no more. 71 | 72 | The next version will define curried functions with 0-arity, which will be more convenient in most cases. 73 | 74 | ### Todo 75 | 76 | * Dotted first call (curried function with arity = 0) ? 77 | * Functions guards 78 | * Possibility to add more than one argument on first call 79 | * … on any call ? 80 | * More tests 81 | -------------------------------------------------------------------------------- /lib/curry.ex: -------------------------------------------------------------------------------- 1 | defmodule Curry do 2 | @moduledoc File.read!("README.md") 3 | 4 | defmacro __using__(_) do 5 | quote do 6 | import Curry, only: [curry: 2, curryp: 2] 7 | end 8 | end 9 | 10 | defmacro curry(head, do: body) do 11 | write_def(head, body, false) 12 | end 13 | 14 | defmacro curryp(head, do: body) do 15 | write_def(head, body, true) 16 | end 17 | 18 | defp write_def(head, body, private) do 19 | {_, _, fargs} = head 20 | case fargs do 21 | [] -> raise ArgumentError, "cannot curry a function when its arity is 0" 22 | nil -> raise ArgumentError, "cannot curry a function when its arity is 0" 23 | [_|_] -> :ok 24 | end 25 | {fname, ctx, [arg1|args_rest]} = head 26 | small_head = {fname,ctx,[arg1]} 27 | body = make_fns(args_rest, body) 28 | case private do 29 | true -> quote do: (defp unquote(small_head), do: unquote(body)) 30 | _ -> quote do: (def unquote(small_head), do: unquote(body)) 31 | end 32 | end 33 | 34 | defp make_fns([last_arg], body) do 35 | quote do 36 | fn unquote(last_arg) -> 37 | unquote(body) 38 | end 39 | end 40 | end 41 | 42 | defp make_fns([arg|args], body) do 43 | body = make_fns(args, body) 44 | quote do 45 | fn unquote(arg) -> 46 | unquote(body) 47 | end 48 | end 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Curry.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :curry, 6 | version: "0.0.1", 7 | elixir: "~> 1.0", 8 | description: "A simple currying macro allowing to define curried functions in Elixir modules.", 9 | package: [ 10 | contributors: ["Ludovic Demblans"], 11 | licenses: ["MIT"], 12 | links: %{"GitHub" => "https://github.com/niahoo/elixir-curry"} 13 | ], 14 | deps: deps] 15 | end 16 | 17 | def application do 18 | [] 19 | end 20 | 21 | defp deps do 22 | [] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /priv/Example.ex: -------------------------------------------------------------------------------- 1 | defmodule Curry.Example do 2 | 3 | # 1. Use the Curry module 4 | use Curry 5 | 6 | # 2. use 'curry' instead of 'def' to define functions 7 | curry add(a,b), do: a + b 8 | 9 | # also, use 'curryp' instead of 'defp' 10 | curryp secret(replaced, by, sentence) do 11 | sentence |> String.replace(replaced, by) 12 | end 13 | 14 | def test do 15 | # 3. The first call is not 'dotted' 16 | increment = add(1) 17 | 18 | # 4. The following calls are 'dotted' 19 | 100 = increment.(99) 20 | 21 | encoder = secret("o").("_") 22 | "hell_" = encoder.("hello") 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /test/curry_test.exs: -------------------------------------------------------------------------------- 1 | defmodule CurryTest do 2 | use ExUnit.Case 3 | use Curry 4 | 5 | curry add(a,b), do: a + b 6 | 7 | curryp three(s1, s2, s3) do 8 | s1 <> " " <> s2 <> " " <> s3 9 | end 10 | 11 | test "the truth" do 12 | increment = add(1) 13 | assert :erlang.is_function(increment) 14 | assert increment.(100) == 101 15 | assert three("I").("am").("you") == "I am you" 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------