Skip to content

Commit 423827a

Browse files
committed
happy with
0 parents  commit 423827a

File tree

7 files changed

+193
-0
lines changed

7 files changed

+193
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/_build
2+
/cover
3+
/deps
4+
erl_crash.dump
5+
*.ez

README.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# HappyWith
2+
3+
Tiny syntax sugar around Elixir's `with` special form.
4+
5+
#### Why ?
6+
7+
Because I'm happy on how Elixir's `with` special form works,
8+
and can get used to the `<-` arrow but I still dont like to
9+
place commas between all the with expressions.
10+
11+
Back then before elixir 1.2 release, I implemented [happy](http://github.com/vic/happy) using case
12+
expressions, it leaked variables as normal case expressions would do, and _overriding_ the standard
13+
`=` operator turned out to have some unexpected cases. Also, after using standard `with` a bit, I
14+
really got to like it, except for the fact of having to place commas after expressions, so I wrote
15+
`happy_with` which is a *very* tiny macro that just rewrites to Elixir's standard `with` special form.
16+
17+
## Installation
18+
19+
[Available in Hex](https://hex.pm/packages/happy_with), the package can be installed as:
20+
21+
1. Add happy_with to your list of dependencies in `mix.exs`:
22+
23+
```elixir
24+
def deps do
25+
[{:happy_with, "~> 0.0.1"}]
26+
end
27+
```
28+
29+
## Usage
30+
31+
```elixir
32+
import HappyWith
33+
```
34+
35+
#### `happy_with` macro
36+
37+
```
38+
happy_with do
39+
{:ok, friend} <- retrieve_friend
40+
name = String.downcase(friend)
41+
name
42+
else
43+
_ -> "nobody"
44+
end
45+
```
46+
47+
rewrites into standard `with` form.
48+
Only the last expression in the happy_with block is given to with's do.
49+
50+
```elixir
51+
with({:ok, friend} <- retrieve_friend,
52+
name = String.downcase(friend))
53+
do
54+
name
55+
else
56+
_ -> "nobody"
57+
end
58+
```
59+
60+
IMHO the first one reads better.

config/config.exs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file is responsible for configuring your application
2+
# and its dependencies with the aid of the Mix.Config module.
3+
use Mix.Config
4+
5+
# This configuration is loaded before any dependency and is restricted
6+
# to this project. If another project depends on this project, this
7+
# file won't be loaded nor affect the parent project. For this reason,
8+
# if you want to provide default values for your application for
9+
# 3rd-party users, it should be done in your "mix.exs" file.
10+
11+
# You can configure for your application as:
12+
#
13+
# config :happy_with, key: :value
14+
#
15+
# And access this configuration in your application as:
16+
#
17+
# Application.get_env(:happy_with, :key)
18+
#
19+
# Or configure a 3rd-party app:
20+
#
21+
# config :logger, level: :info
22+
#
23+
24+
# It is also possible to import configuration files, relative to this
25+
# directory. For example, you can emulate configuration per environment
26+
# by uncommenting the line below and defining dev.exs, test.exs and such.
27+
# Configuration from the imported file will override the ones defined
28+
# here (which is why it is important to import them last).
29+
#
30+
# import_config "#{Mix.env}.exs"

lib/happy_with.ex

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
defmodule HappyWith do
2+
3+
@doc ~S"""
4+
Rewrites the given block and else clauses into Elixir's standard `with` form.
5+
6+
iex> import HappyWith
7+
iex> happy_with do
8+
...> {:ok, name} <- {:ok, "joSE"}
9+
...> lower = String.downcase(name)
10+
...> lower
11+
...> end
12+
"jose"
13+
14+
15+
You can also provide else clauses to the `with` form.
16+
17+
18+
iex> import HappyWith
19+
iex> happy_with do
20+
...> {:ok, name} <- {:error, :nobody}
21+
...> _never_reached = String.downcase(name)
22+
...> else
23+
...> {:error, _} -> "luis"
24+
...> end
25+
"luis"
26+
27+
28+
"""
29+
defmacro happy_with([do: {:__block__, _, body}, else: elses]), do: rewrite(body, elses)
30+
defmacro happy_with([do: {:__block__, _, body}]), do: rewrite(body, nil)
31+
32+
defp rewrite(body, elses) when length(body) > 1 do
33+
exprs = Enum.slice(body, 0..-2)
34+
do_last = [do: Enum.at(body, -1)]
35+
else_clauses = elses && [else: elses] || []
36+
{:with, [], exprs ++ [do_last ++ else_clauses]}
37+
end
38+
39+
defp rewrite(body, _elses), do: body
40+
41+
end

mix.exs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
defmodule HappyWith.Mixfile do
2+
use Mix.Project
3+
4+
def project do
5+
[app: :happy_with,
6+
version: "0.0.1",
7+
elixir: "~> 1.2",
8+
description: description,
9+
package: package,
10+
build_embedded: Mix.env == :prod,
11+
start_permanent: Mix.env == :prod,
12+
deps: deps]
13+
end
14+
15+
def package do
16+
[files: ~w(lib mix.exs README* LICENSE),
17+
maintainers: ["Victor Hugo Borja <[email protected]>"],
18+
licenses: ["Apache 2.0"],
19+
links: %{
20+
"GitHub" => "https://github.com/vic/happy_with"
21+
}]
22+
end
23+
24+
def description do
25+
"""
26+
Tiny syntax sugar around Elixir's `with` special form.
27+
28+
Dont use commas around with expressions.
29+
"""
30+
end
31+
32+
# Configuration for the OTP application
33+
#
34+
# Type "mix help compile.app" for more information
35+
def application do
36+
[applications: [:logger]]
37+
end
38+
39+
# Dependencies can be Hex packages:
40+
#
41+
# {:mydep, "~> 0.3.0"}
42+
#
43+
# Or git/path repositories:
44+
#
45+
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
46+
#
47+
# Type "mix help deps" for more examples and options
48+
defp deps do
49+
[]
50+
end
51+
end

test/happy_with_test.exs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule HappyWithTest do
2+
use ExUnit.Case
3+
4+
doctest HappyWith
5+
end

test/test_helper.exs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

0 commit comments

Comments
 (0)