fix config

fix config

use Mix.Config

# For production, we often load configuration from external
# sources, such as your system environment. For this reason,
# you won't find the :http configuration below, but set inside
# Glasnost.Web.Endpoint.load_from_system_env/1 dynamically.
# Any dynamic configuration should be moved to such function.
#
# Don't forget to configure the url host to something meaningful,
# Phoenix uses this information when generating URLs.
#
# Finally, we also include the path to a cache manifest
# containing the digested version of static files. This
# manifest is generated by the mix phoenix.digest task
# which you typically run after static files are built.
config :glasnost, Glasnost.Web.Endpoint,
  on_init: {Demo.Web.Endpoint, :load_from_system_env, []},
  url: [host: "example.com", port: 80],
  cache_static_manifest: "priv/static/cache_manifest.json"

# Do not print debug messages in production
config :logger, level: :info

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
#     config :glasnost, Glasnost.Web.Endpoint,
#       ...
#       url: [host: "example.com", port: 443],
#       https: [:inet6,
#               port: 443,
#               keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
#               certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
#
# Where those two env variables return an absolute path to
# the key and cert in disk or a relative path inside priv,
# for example "priv/ssl/server.key".
#
# We also recommend setting `force_ssl`, ensuring no data is
# ever sent via http, always redirecting to https:
#
#     config :glasnost, Glasnost.Web.Endpoint,
#       force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.

# ## Using releases
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start the server for all endpoints:
#
#     config :phoenix, :serve_endpoints, true
#
# Alternatively, you can configure exactly which server to
# start per endpoint:
#
#     config :glasnost, Glasnost.Web.Endpoint, server: true
#

# Finally import the config/prod.secret.exs
# which should be versioned separately.

# import_config "prod.secret.exs"

use Mix.Config

# For production, we often load configuration from external
# sources, such as your system environment. For this reason,
# you won't find the :http configuration below, but set inside
# Glasnost.Web.Endpoint.load_from_system_env/1 dynamically.
# Any dynamic configuration should be moved to such function.
#
# Don't forget to configure the url host to something meaningful,
# Phoenix uses this information when generating URLs.
#
# Finally, we also include the path to a cache manifest
# containing the digested version of static files. This
# manifest is generated by the mix phoenix.digest task
# which you typically run after static files are built.
config :glasnost, Glasnost.Web.Endpoint,
  on_init: {Demo.Web.Endpoint, :load_from_system_env, []},
  url: [host: "example.com", port: 80],
  cache_static_manifest: "priv/static/cache_manifest.json"

# Do not print debug messages in production
config :logger, level: :info

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
#     config :glasnost, Glasnost.Web.Endpoint,
#       ...
#       url: [host: "example.com", port: 443],
#       https: [:inet6,
#               port: 443,
#               keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
#               certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
#
# Where those two env variables return an absolute path to
# the key and cert in disk or a relative path inside priv,
# for example "priv/ssl/server.key".
#
# We also recommend setting `force_ssl`, ensuring no data is
# ever sent via http, always redirecting to https:
#
#     config :glasnost, Glasnost.Web.Endpoint,
#       force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.

# ## Using releases
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start the server for all endpoints:
#
#     config :phoenix, :serve_endpoints, true
#
# Alternatively, you can configure exactly which server to
# start per endpoint:
#
#     config :glasnost, Glasnost.Web.Endpoint, server: true
#

# Finally import the config/prod.secret.exs
# which should be versioned separately.
import_config "prod.secret.exs"


Periodic: Simplify the BigQuery query string

defmodule Krihelinator.Periodic.BigQuery do
  require Logger

  @moduledoc """
  Issue pre-configured queries against google BigQuery and (partly) process
  the results.
  """

  def query() do
    {start_date, end_date} = get_start_and_end_dates()
    {:ok, %{jobComplete: true, rows: rows}} = run_query(start_date, end_date)
    Logger.info "Got #{length(rows)} active repos between #{start_date} and #{end_date} from BigQuery"
    process_rows(rows)
  end

  defp get_start_and_end_dates() do
    end_date = Date.utc_today()
    start_date = Timex.shift(end_date, days: -7)
    {Date.to_string(start_date), Date.to_string(end_date)}
  end

  defp run_query(start_date, end_date) do
    query_string = "SELECT name, COUNT(DISTINCT author) AS authors FROM (SELECT type, repo.name AS name, actor.id AS author, FROM TABLE_DATE_RANGE([githubarchive:day.], TIMESTAMP('#{start_date}'), TIMESTAMP('#{end_date}')) WHERE type = 'PushEvent') GROUP BY name HAVING authors >= 2 ORDER BY authors DESC;"
    query_struct = %BigQuery.Types.Query{query: query_string}
    BigQuery.Job.query("krihelinator", query_struct)
  end

  defp process_rows(rows) do
    rows
    |> Stream.map(fn datum -> Map.fetch!(datum, :f) end)
    |> Stream.map(&hd/1)
    |> Enum.map(fn datum -> Map.fetch!(datum, :v) end)
  end
end

defmodule Krihelinator.Periodic.BigQuery do
  require Logger

  @moduledoc """
  Issue pre-configured queries against google BigQuery and (partly) process
  the results.
  """

  def query() do
    {start_date, end_date} = get_start_and_end_dates()
    {:ok, %{jobComplete: true, rows: rows}} = run_query(start_date, end_date)
    Logger.info "Got #{length(rows)} active repos between #{start_date} and #{end_date} from BigQuery"
    process_rows(rows)
  end

  defp get_start_and_end_dates() do
    end_date = Date.utc_today()
    start_date = Timex.shift(end_date, days: -7)
    {Date.to_string(start_date), Date.to_string(end_date)}
  end

  defp run_query(start_date, end_date) do
    query_string = "SELECT name, COUNT(DISTINCT author) AS authors, COUNT(*) AS pushes FROM (SELECT type, repo.name AS name, actor.id AS author, FROM TABLE_DATE_RANGE([githubarchive:day.], TIMESTAMP('#{start_date}'), TIMESTAMP('#{end_date}')) WHERE type = 'PushEvent') GROUP BY name HAVING (authors >= 2) AND (pushes >= 1) ORDER BY authors DESC;"
    query_struct = %BigQuery.Types.Query{query: query_string}
    BigQuery.Job.query("krihelinator", query_struct)
  end

  defp process_rows(rows) do
    rows
    |> Stream.map(fn datum -> Map.fetch!(datum, :f) end)
    |> Stream.map(&hd/1)
    |> Enum.map(fn datum -> Map.fetch!(datum, :v) end)
  end
end


format cryptocompare

format cryptocompare

defmodule Neoprice.Cryptocompare do
  @moduledoc "Cryptocompare interface"

  alias Neoprice.Cryptocompare.Api

  @limit 2000
  @minute 60
  @hour 3600
  @day 86_400

  @spec last_price(String.t, String.t) :: float
  def last_price(from_symbol, to_symbol) do
    Api.last_price(from_symbol, to_symbol)
  end
  @spec last_price_full(String.t, String.t) :: float
  def last_price_full(from_symbol, to_symbol) do
    Api.last_price_full(from_symbol, to_symbol)
  end

  @spec get_price(atom, non_neg_integer, non_neg_integer, String.t, String.t,
                  non_neg_integer) :: list

  def get_price(definition,  from, to, from_symbol, to_symbol, aggregate) do
    seconds = seconds(definition)
    :lists.seq(to, from, -seconds * @limit * aggregate)
    |> Enum.reverse()
    |> Enum.map(fn(time) ->
       limit = limit(from, time, seconds, aggregate)
       Api.get_historical_price(definition, from_symbol, to_symbol,
       limit, aggregate, time)
      end)
    |> List.flatten()
    |> Enum.filter(fn({k, _}) -> k > from and k < to end)
  end

  @spec limit(non_neg_integer, non_neg_integer, non_neg_integer,
              non_neg_integer) :: non_neg_integer
  def limit(from, time, definition, aggregate) do
    if from < time - aggregate * definition * @limit do
      @limit
    else
      (time - from) / (definition * aggregate)
      |> Float.ceil() |> round
    end
  end

  defp seconds(:day), do: @day
  defp seconds(:hour), do: @hour
  defp seconds(:minute), do: @minute
  defp seconds(definition), do: raise "Can't convert #{definition}"
end


Provide better defaults & env variable integration

@moduledoc """
A schema is a keyword list which represents how to map, transform, and validate
configuration values parsed from the .conf file. The following is an explanation of
each key in the schema definition in order of appearance, and how to use them.

## Import

A list of application names (as atoms), which represent apps to load modules from
which you can then reference in your schema definition. This is how you import your
own custom Validator/Transform modules, or general utility modules for use in
validator/transform functions in the schema. For example, if you have an application
`:foo` which contains a custom Transform module, you would add it to your schema like so:

`[ import: [:foo], ..., transforms: ["myapp.some.setting": MyApp.SomeTransform]]`

## Extends

A list of application names (as atoms), which contain schemas that you want to extend
with this schema. By extending a schema, you effectively re-use definitions in the
extended schema. You may also override definitions from the extended schema by redefining them
in the extending schema. You use `:extends` like so:

`[ extends: [:foo], ... ]`

## Mappings

Mappings define how to interpret settings in the .conf when they are translated to
runtime configuration. They also define how the .conf will be generated, things like
documention, @see references, example values, etc.

See the moduledoc for `Conform.Schema.Mapping` for more details.

## Transforms

Transforms are custom functions which are executed to build the value which will be
stored at the path defined by the key. Transforms have access to the current config
state via the `Conform.Conf` module, and can use that to build complex configuration
from a combination of other config values.

See the moduledoc for `Conform.Schema.Transform` for more details and examples.

## Validators

Validators are simple functions which take two arguments, the value to be validated,
and arguments provided to the validator (used only by custom validators). A validator
checks the value, and returns `:ok` if it is valid, `{:warn, message}` if it is valid,
but should be brought to the users attention, or `{:error, message}` if it is invalid.

See the moduledoc for `Conform.Schema.Validator` for more details and examples.
"""
[
  extends: [],
  import: [],
  mappings: [
    "gatekeeper.rfidreader.device": [
      commented: false,
      datatype: :binary,
      default: "/dev/ttyUSB0",
      doc: "Path to device file for RFID reader",
      hidden: false,
      to: "gatekeeper.rfidreader.device"
    ],
    "gatekeeper.doorlock.type": [
      commented: false,
      datatype: :atom,
      default: GPIO,
      doc: "Module name representing the driver for the Door Lock",
      hidden: true,
      to: "gatekeeper.doorlock.type"
    ],
    "gatekeeper.doorlock.gpio_pin": [
      commented: false,
      datatype: :integer,
      default: 4,
      doc: "GPIO pin for signaling the door strike to unlock",
      hidden: false,
      to: "gatekeeper.doorlock.gpio_pin"
    ],
    "gatekeeper.doorlock.duration": [
      commented: false,
      datatype: :integer,
      default: 3000,
      doc: "How long (in milliseconds) to keep the door unlocked after reading a badge",
      hidden: false,
      to: "gatekeeper.doorlock.duration"
    ],
    "gatekeeper.doorlock.door_id": [
      commented: false,
      datatype: :integer,
      default: 1,
      doc: "ID of the door controlled by this instance of Gatekeeper",
      hidden: false,
      to: "gatekeeper.doorlock.door_id"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.url.host": [
      commented: false,
      datatype: :binary,
      default: "localhost",
      doc: "Domain name at which application is running, for web server to generate links",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.url.host"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.root": [
      commented: false,
      datatype: :binary,
      default: Path.dirname(__DIR__),
      doc: "Base directory for Gatekeeper application",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.root"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.secret_key_base": [
      commented: false,
      datatype: :binary,
      doc: "Secret key used for securing session cookies",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.secret_key_base"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.http.port": [
      commented: false,
      datatype: :integer,
      default: 4000,
      doc: "Port on which application listens for HTTP connections",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.http.port"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.username": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper",
      doc: "Database username for the READ ONLY connection",
      hidden: false,
      env_var: "DATABASE_READ_USERNAME",
      to: "gatekeeper.Elixir.Gatekeeper.Repo.username"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.password": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper",
      doc: "Database password for the READ ONLY connection",
      hidden: false,
      env_var: "DATABASE_READ_PASSWORD",
      to: "gatekeeper.Elixir.Gatekeeper.Repo.password"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.database": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper_dev",
      doc: "Database name for the READ ONLY connection",
      hidden: false,
      env_var: "DATABASE_READ_DATABASE",
      to: "gatekeeper.Elixir.Gatekeeper.Repo.database"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.hostname": [
      commented: false,
      datatype: :binary,
      default: "localhost",
      doc: "Database hostname for the READ ONLY connection",
      hidden: false,
      env_var: "DATABASE_READ_HOSTNAME",
      to: "gatekeeper.Elixir.Gatekeeper.Repo.hostname"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.template": [
      commented: false,
      datatype: :binary,
      default: "template0",
      doc: "Postgresql template for the READ ONLY connection",
      hidden: true,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.template"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.pool_size": [
      commented: false,
      datatype: :integer,
      default: 10,
      doc: "Number of database connections available in the pool for the READ ONLY connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.pool_size"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.username": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper",
      doc: "Database username for the WRITABLE connection",
      hidden: false,
      env_var: "DATABASE_WRITE_USERNAME",
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.username"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.password": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper",
      doc: "Database password for the WRITABLE connection",
      hidden: false,
      env_var: "DATABASE_WRITE_PASSWORD",
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.password"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.database": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper_dev",
      doc: "Database name for the WRITABLE connection",
      hidden: false,
      env_var: "DATABASE_WRITE_DATABASE",
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.database"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.hostname": [
      commented: false,
      datatype: :binary,
      default: "localhost",
      doc: "Database hostname for the WRITABLE connection",
      hidden: false,
      env_var: "DATABASE_WRITE_HOSTNAME",
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.hostname"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.template": [
      commented: false,
      datatype: :binary,
      default: "template0",
      doc: "Postgresql template for the WRITABLE connection",
      hidden: true,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.template"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.pool_size": [
      commented: false,
      datatype: :integer,
      default: 10,
      doc: "Number of database connections available in the pool for the WRITABLE connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.pool_size"
    ],
    "ueberauth.Elixir.Ueberauth.providers.slack": [
      commented: false,
      datatype: {:atom, [list: {:atom, :atom}]},
      default: {Ueberauth.Strategy.Slack, [team: nil]},
      doc: "Team ID for Slack authentication",
      hidden: false,
      env_var: "SLACK_TEAM_ID",
      to: "ueberauth.Elixir.Ueberauth.providers.slack"
    ],
    "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_id": [
      commented: false,
      datatype: :binary,
      default: "",
      doc: "Slack OAuth Client ID",
      hidden: false,
      env_var: "SLACK_CLIENT_ID",
      to: "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_id"
    ],
    "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_secret": [
      commented: false,
      datatype: :binary,
      default: "",
      doc: "Slack OAuth Client Secret",
      hidden: false,
      env_var: "SLACK_CLIENT_SECRET",
      to: "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_secret"
    ],
    "guardian.Elixir.Guardian.ttl": [
      commented: false,
      datatype: {:integer, :atom},
      default: {30, :days},
      doc: "Web session duration",
      hidden: false,
      to: "guardian.Elixir.Guardian.ttl"
    ],
    "guardian.Elixir.Guardian.secret_key": [
      commented: false,
      datatype: :binary,
      default: "",
      doc: "Secret key to sign/secure session cookies",
      hidden: false,
      env_var: "GUARDIAN_SECRET_KEY",
      to: "guardian.Elixir.Guardian.secret_key"
    ],
  ],
  transforms: [
    "ueberauth.Elixir.Ueberauth.providers.slack": fn conf ->
      {Ueberauth.Strategy.Slack, [team: Conform.Conf.get(conf, "ueberauth.Elixir.Ueberauth.providers.slack")]}
    end
  ],
  validators: []
]

@moduledoc """
A schema is a keyword list which represents how to map, transform, and validate
configuration values parsed from the .conf file. The following is an explanation of
each key in the schema definition in order of appearance, and how to use them.

## Import

A list of application names (as atoms), which represent apps to load modules from
which you can then reference in your schema definition. This is how you import your
own custom Validator/Transform modules, or general utility modules for use in
validator/transform functions in the schema. For example, if you have an application
`:foo` which contains a custom Transform module, you would add it to your schema like so:

`[ import: [:foo], ..., transforms: ["myapp.some.setting": MyApp.SomeTransform]]`

## Extends

A list of application names (as atoms), which contain schemas that you want to extend
with this schema. By extending a schema, you effectively re-use definitions in the
extended schema. You may also override definitions from the extended schema by redefining them
in the extending schema. You use `:extends` like so:

`[ extends: [:foo], ... ]`

## Mappings

Mappings define how to interpret settings in the .conf when they are translated to
runtime configuration. They also define how the .conf will be generated, things like
documention, @see references, example values, etc.

See the moduledoc for `Conform.Schema.Mapping` for more details.

## Transforms

Transforms are custom functions which are executed to build the value which will be
stored at the path defined by the key. Transforms have access to the current config
state via the `Conform.Conf` module, and can use that to build complex configuration
from a combination of other config values.

See the moduledoc for `Conform.Schema.Transform` for more details and examples.

## Validators

Validators are simple functions which take two arguments, the value to be validated,
and arguments provided to the validator (used only by custom validators). A validator
checks the value, and returns `:ok` if it is valid, `{:warn, message}` if it is valid,
but should be brought to the users attention, or `{:error, message}` if it is invalid.

See the moduledoc for `Conform.Schema.Validator` for more details and examples.
"""
[
  extends: [],
  import: [],
  mappings: [
    "gatekeeper.rfidreader.device": [
      commented: false,
      datatype: :binary,
      default: "/dev/ttyUSB0",
      doc: "Path to device file for RFID reader",
      hidden: false,
      to: "gatekeeper.rfidreader.device"
    ],
    "gatekeeper.doorlock.type": [
      commented: false,
      datatype: :atom,
      default: GPIO,
      doc: "Module name representing the driver for the Door Lock",
      hidden: true,
      to: "gatekeeper.doorlock.type"
    ],
    "gatekeeper.doorlock.gpio_pin": [
      commented: false,
      datatype: :integer,
      default: 4,
      doc: "GPIO pin for signaling the door strike to unlock",
      hidden: false,
      to: "gatekeeper.doorlock.gpio_pin"
    ],
    "gatekeeper.doorlock.duration": [
      commented: false,
      datatype: :integer,
      default: 3000,
      doc: "How long (in milliseconds) to keep the door unlocked after reading a badge",
      hidden: false,
      to: "gatekeeper.doorlock.duration"
    ],
    "gatekeeper.doorlock.door_id": [
      commented: false,
      datatype: :integer,
      default: 1,
      doc: "ID of the door controlled by this instance of Gatekeeper",
      hidden: false,
      to: "gatekeeper.doorlock.door_id"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.url.host": [
      commented: false,
      datatype: :binary,
      default: "localhost",
      doc: "Domain name at which application is running, for web server to generate links",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.url.host"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.root": [
      commented: false,
      datatype: :binary,
      default: Path.dirname(__DIR__),
      doc: "Base directory for Gatekeeper application",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.root"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.secret_key_base": [
      commented: false,
      datatype: :binary,
      doc: "Secret key used for securing session cookies",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.secret_key_base"
    ],
    "gatekeeper.Elixir.GatekeeperWeb.Endpoint.http.port": [
      commented: false,
      datatype: :integer,
      default: 4000,
      doc: "Port on which application listens for HTTP connections",
      hidden: false,
      to: "gatekeeper.Elixir.GatekeeperWeb.Endpoint.http.port"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.username": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper",
      doc: "Database username for the READ ONLY connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.username"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.password": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper",
      doc: "Database password for the READ ONLY connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.password"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.database": [
      commented: false,
      datatype: :binary,
      default: "gatekeeper",
      doc: "Database name for the READ ONLY connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.database"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.hostname": [
      commented: false,
      datatype: :binary,
      default: "localhost",
      doc: "Database hostname for the READ ONLY connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.hostname"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.template": [
      commented: false,
      datatype: :binary,
      default: "template0",
      doc: "Postgresql template for the READ ONLY connection",
      hidden: true,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.template"
    ],
    "gatekeeper.Elixir.Gatekeeper.Repo.pool_size": [
      commented: false,
      datatype: :integer,
      default: 10,
      doc: "Number of database connections available in the pool for the READ ONLY connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.Repo.pool_size"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.username": [
      commented: false,
      datatype: :atom,
      doc: "Database username for the WRITABLE connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.username"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.password": [
      commented: false,
      datatype: :atom,
      doc: "Database password for the WRITABLE connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.password"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.database": [
      commented: false,
      datatype: :atom,
      doc: "Database name for the WRITABLE connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.database"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.hostname": [
      commented: false,
      datatype: :binary,
      default: "localhost",
      doc: "Database hostname for the WRITABLE connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.hostname"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.template": [
      commented: false,
      datatype: :binary,
      default: "template0",
      doc: "Postgresql template for the WRITABLE connection",
      hidden: true,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.template"
    ],
    "gatekeeper.Elixir.Gatekeeper.WriteRepo.pool_size": [
      commented: false,
      datatype: :integer,
      default: 10,
      doc: "Number of database connections available in the pool for the WRITABLE connection",
      hidden: false,
      to: "gatekeeper.Elixir.Gatekeeper.WriteRepo.pool_size"
    ],
    "ueberauth.Elixir.Ueberauth.providers.slack": [
      commented: false,
      datatype: {:atom, [list: {:atom, :atom}]},
      default: {Ueberauth.Strategy.Slack, [team: nil]},
      doc: "Team ID for Slack authentication",
      hidden: false,
      to: "ueberauth.Elixir.Ueberauth.providers.slack"
    ],
    "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_id": [
      commented: false,
      datatype: :atom,
      doc: "Slack OAuth Client ID",
      hidden: false,
      to: "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_id"
    ],
    "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_secret": [
      commented: false,
      datatype: :atom,
      doc: "Slack OAuth Client Secret",
      hidden: false,
      to: "ueberauth.Elixir.Ueberauth.Strategy.Slack.OAuth.client_secret"
    ],
    "guardian.Elixir.Guardian.ttl": [
      commented: false,
      datatype: {:integer, :atom},
      default: {30, :days},
      doc: "Web session duration",
      hidden: false,
      to: "guardian.Elixir.Guardian.ttl"
    ],
    "guardian.Elixir.Guardian.secret_key": [
      commented: false,
      datatype: :atom,
      doc: "Provide documentation for guardian.Elixir.Guardian.secret_key here.",
      hidden: false,
      to: "guardian.Elixir.Guardian.secret_key"
    ],
  ],
  transforms: [
    "ueberauth.Elixir.Ueberauth.providers.slack": fn conf ->
      {Ueberauth.Strategy.Slack, [team: Conform.Conf.get(conf, "ueberauth.Elixir.Ueberauth.providers.slack")]}
    end
  ],
  validators: []
]


made nice error messages for danbooru module failing

defmodule TougouBot.Modules.Danbooru do
  @moduledoc """
  The Danbooru module is in charge of the !pic commands which use the danbooru
  api to get a random image according to the tags given (or from all images if none given.)
  """
  use Alchemy.Cogs
  alias Alchemy.Embed
  import Embed

  Cogs.def pic do
    # random pic
    Embed.send(search(""))
  end

  Cogs.set_parser(:pic, &TougouBot.Util.Parsers.plus_parser/1)
  Cogs.def pic(args) do
    #pic based on args
    Embed.send(search(args))
  end

  @danbooru_colour_embed %Embed{color: 0xADD8E6}
  @danbooru_error_embed %Embed{color: 0xff0000}

  defp search(tags) do
    HTTPoison.start()
    case HTTPoison.get("https://danbooru.donmai.us/posts.json?search&random=true&limit=1&tags="<>tags) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        data = Poison.decode!(body)
        case List.first(data) == nil do
          true ->
            @danbooru_error_embed
            |> title("それは居ない")#that doesnt exist
          _ ->
            @danbooru_colour_embed
            |> title("<"<>"https://danbooru.donmai.us/posts/"<>Integer.to_string(List.first(data)["id"])<>">")
            |> image("https://danbooru.donmai.us"<>List.first(data)["file_url"])
        end
      {:ok, %HTTPoison.Response{status_code: 500}} ->
        @danbooru_error_embed
        |> title("何かが壊れちゃった…")
        |> description("Danbooru has a 2 tag search limit.")
      {:error, e} ->
        IO.inspect(e);
        @danbooru_error_embed
        |> title("何かが壊れちゃった…")
        |> description("got a bad error, check log")
    end
  end

end

defmodule TougouBot.Modules.Danbooru do
  @moduledoc """
  The Danbooru module is in charge of the !pic commands which use the danbooru
  api to get a random image according to the tags given (or from all images if none given.)
  """
  use Alchemy.Cogs
  alias Alchemy.Embed
  import Embed

  Cogs.def pic do
    # random pic
    Embed.send(search(""))
  end

  Cogs.set_parser(:pic, &TougouBot.Util.Parsers.plus_parser/1)
  Cogs.def pic(args) do
    #pic based on args
    Embed.send(search(args))
  end

  @danbooru_colour_embed %Embed{color: 0xADD8E6}
  @danbooru_error_embed %Embed{color: 0xff0000}

  defp search(tags) do
    HTTPoison.start()
    case HTTPoison.get("https://danbooru.donmai.us/posts.json?search&random=true&limit=1&tags="<>tags) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        data = Poison.decode!(body)
        case List.first(data) == nil do
          true ->
            @danbooru_error_embed
            |> title("それは居ない")#that doesnt exist
          _ ->
            @danbooru_colour_embed
            |> title("<"<>"https://danbooru.donmai.us/posts/"<>Integer.to_string(List.first(data)["id"])<>">")
            |> image("https://danbooru.donmai.us"<>List.first(data)["file_url"])
        end
      {:error, e} ->
        IO.inspect(e);
        "got a bad error, check log"#todo, flavour text.
    end
  end

end


Typo (#52)

Typo (#52)

import TypeClass

defclass Witchcraft.Apply do
  @moduledoc """
  An extension of `Witchcraft.Functor`, `Apply` provides a way to _apply_ arguments
  to functions when both are wrapped in the same kind of container. This can be
  seen as running function application "in a context".

  For a nice, illustrated introduction,
  see [Functors, Applicatives, And Monads In Pictures](http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html).

  ## Graphically

  If function application looks like this

      data |> function == result

  and a functor looks like this

      %Container<data> ~> function == %Container<result>

  then an apply looks like

      %Container<data> ~>> %Container<function> == %Container<result>

  which is similar to function application inside containers, plus the ability to
  attach special effects to applications.

                 data --------------- function ---------------> result
      %Container<data> --- %Container<function> ---> %Container<result>

  This lets us do functorial things like

  * continue applying values to a curried function resulting from a `Witchcraft.Functor.lift/2`
  * apply multiple functions to multiple arguments (with lists)
  * propogate some state (like [`Nothing`](https://hexdocs.pm/algae/Algae.Maybe.Nothing.html#content)
  in [`Algae.Maybe`](https://hexdocs.pm/algae/Algae.Maybe.html#content))

  but now with a much larger number of arguments, reuse partially applied functions,
  and run effects with the function container as well as the data container.

  ## Examples

      iex> ap([fn x -> x + 1 end, fn y -> y * 10 end], [1, 2, 3])
      [2, 3, 4, 10, 20, 30]

      iex> [100, 200]
      ...> |> Witchcraft.Functor.lift(fn(x, y, z) -> x * y / z end)
      ...> |> provide([5, 2])
      ...> |> provide([100, 50])
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
      # ↓                          ↓
      # 100 * 5 / 100          200 * 5 / 50

      iex> import Witchcraft.Functor
      ...>
      ...> [100, 200]
      ...> ~> fn(x, y, z) ->
      ...>   x * y / z
      ...> end <<~ [5, 2]
      ...>     <<~ [100, 50]
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
      # ↓                          ↓
      # 100 * 5 / 100          200 * 5 / 50

      %Algae.Maybe.Just{just: 42}
      ~> fn(x, y, z) ->
        x * y / z
      end <<~ %Algae.Maybe.Nothing{}
          <<~ %Algae.Maybe.Just{just: 99}
      #=> %Algae.Maybe.Nothing{}

  ## `convey` vs `ap`

  `convey` and `ap` essentially associate in opposite directions. For example,
  large data is _usually_ more efficient with `ap`, and large numbers of
  functions are _usually_ more efficient with `convey`.

  It's also more consistent consistency. In Elixir, we like to think of a "subject"
  being piped through a series of transformations. This places the function argument
  as the second argument. In `Witchcraft.Functor`, this was of little consequence.
  However, in `Apply`, we're essentially running superpowered function application.
  `ap` is short for `apply`, as to not conflict with `Kernel.apply/2`, and is meant
  to respect a similar API, with the function as the first argument. This also reads
  nicely when piped, as it becomes `[funs] |> ap([args1]) |> ap([args2])`,
  which is similar in structure to `fun.(arg2).(arg1)`.

  With potentially multiple functions being applied over potentially
  many arguments, we need to worry about ordering. `convey` not only flips
  the order of arguments, but also who is in control of ordering.
  `convey` typically runs each function over all arguments (`first_fun ⬸ all_args`),
  and `ap` runs all functions for each element (`first_arg ⬸ all_funs`).
  This may change the order of results, and is a feature, not a bug.

      iex> [1, 2, 3]
      ...> |> convey([&(&1 + 1), &(&1 * 10)])
      [
        2, 10, # [(1 + 1), (1 * 10)]
        3, 20, # [(2 + 1), (2 * 10)]
        4, 30  # [(3 + 1), (3 * 10)]
      ]

      iex> [&(&1 + 1), &(&1 * 10)]
      ...> |> ap([1, 2, 3])
      [
        2,  3,  4, # [(1 + 1),  (2 + 1),  (3 + 1)]
        10, 20, 30 # [(1 * 10), (2 * 10), (3 * 10)]
      ]

  ## Type Class

  An instance of `Witchcraft.Apply` must also implement `Witchcraft.Functor`,
  and define `Witchcraft.Apply.convey/2`.

      Functor  [map/2]
         ↓
       Apply   [convey/2]
  """

  alias __MODULE__
  use Quark

  alias Witchcraft.Functor
  extend Witchcraft.Functor
  use Witchcraft.Functor

  @type t   :: any()
  @type fun :: any()

  defmacro __using__(opts \\ []) do
    quote do
      use Witchcraft.Functor,     unquote(opts)
      import unquote(__MODULE__), unquote(opts)
    end
  end

  where do
    @doc """
    Pipe arguments to functions, when both are wrapped in the same
    type of data structure.

    ## Examples

        iex> [1, 2, 3]
        ...> |> convey([fn x -> x + 1 end, fn y -> y * 10 end])
        [2, 10, 3, 20, 4, 30]

    """
    @spec convey(Apply.t(), Apply.fun()) :: Apply.t()
    def convey(wrapped_args, wrapped_funs)
  end

  properties do
    def composition(data) do
      alias Witchcraft.Functor
      use Quark

      as = data |> generate() |> Functor.map(&inspect/1)
      fs = data |> generate() |> Functor.replace(fn x -> x <> x end)
      gs = data |> generate() |> Functor.replace(fn y -> y <> "foo" end)

      left  = Apply.convey(Apply.convey(as, gs), fs)

      right =
        fs
        |> Functor.lift(&compose/2)
        |> fn x -> Apply.convey(gs, x) end.()
        |> fn y -> Apply.convey(as, y) end.()

      equal?(left, right)
    end
  end

  @doc """
  Alias for `convey/2`.

  Why "hose"?

  * Pipes (`|>`) are application with arguments flipped
  * `ap/2` is like function application "in a context"
  * The opposite of `ap` is a contextual pipe
  * `hose`s are a kind of flexible pipe

  Q.E.D.

  ![](http://s2.quickmeme.com/img/fd/fd0baf5ada879021c32129fc7dea679bd7666e708df8ca8ca536da601ea3d29e.jpg)

  ## Examples

      iex> [1, 2, 3]
      ...> |> hose([fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

  """
  @spec hose(Apply.t(), Apply.fun()) :: Apply.t()
  def hose(wrapped_args, wrapped_funs), do: convey(wrapped_args, wrapped_funs)

  @doc """
  Reverse arguments and sequencing of `convey/2`.

  Conceptually this makes operations happen in
  a different order than `convey/2`, with the left-side arguments (functions) being
  run on all right-side arguments, in that order. We're altering the _sequencing_
  of function applications.

  ## Examples

      iex> ap([fn x -> x + 1 end, fn y -> y * 10 end], [1, 2, 3])
      [2, 3, 4, 10, 20, 30]

      # For comparison
      iex> convey([1, 2, 3], [fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

      iex> [100, 200]
      ...> |> Witchcraft.Functor.lift(fn(x, y, z) -> x * y / z end)
      ...> |> ap([5, 2])
      ...> |> ap([100, 50])
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
      # ↓                          ↓
      # 100 * 5 / 100          200 * 5 / 50

  """
  @spec ap(Apply.fun(), Apply.t()) :: Apply.t()
  def ap(wrapped_funs, wrapped) do
    lift(wrapped, wrapped_funs, fn(arg, fun) -> fun.(arg) end)
  end

  @doc """
  Async version of `convey/2`

  ## Examples

      iex> [1, 2, 3]
      ...> |> async_convey([fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

      0..10_000
      |> Enum.to_list()
      |> async_convey([
        fn x ->
          Process.sleep(500)
          x + 1
        end,
        fn y ->
          Process.sleep(500)
          y * 10
        end
      ])
      #=> [1, 0, 2, 10, 3, 30, ...] in around a second

  """
  @spec async_convey(Apply.t(), Apply.fun()) :: Apply.t()
  def async_convey(wrapped_args, wrapped_funs) do
    wrapped_args
    |> convey(lift(wrapped_funs, fn(fun, arg) ->
      Task.async(fn ->
        fun.(arg)
      end)
    end))
    |> map(&Task.await/1)
  end

  @doc """
  Async version of `ap/2`

  ## Examples

      iex> [fn x -> x + 1 end, fn y -> y * 10 end]
      ...> |> async_ap([1, 2, 3])
      [2, 3, 4, 10, 20, 30]

      [
        fn x ->
          Process.sleep(500)
          x + 1
        end,
        fn y ->
          Process.sleep(500)
          y * 10
        end
      ]
      |> async_ap(Enum.to_list(0..10_000))
      #=> [1, 2, 3, 4, ...] in around a second

  """
  @spec async_ap(Apply.fun(), Apply.t()) :: Apply.t()
  def async_ap(wrapped_funs, wrapped_args) do
    wrapped_funs
    |> lift(fn(fun, arg) ->
      Task.async(fn ->
        fun.(arg)
      end)
    end)
    |> ap(wrapped_args)
    |> map(&Task.await/1)
  end

  @doc """
  Operator alias for `ap/2`

  Moves against the pipe direction, but in the order of normal function application

  ## Examples

      iex> [fn x -> x + 1 end, fn y -> y * 10 end] <<~ [1, 2, 3]
      [2, 3, 4, 10, 20, 30]

      iex> import Witchcraft.Functor
      ...>
      ...> [100, 200]
      ...> ~> fn(x, y, z) -> x * y / z
      ...> end <<~ [5, 2]
      ...>     <<~ [100, 50]
      ...> ~> fn x -> x + 1 end
      [6.0, 11.0, 3.0, 5.0, 11.0, 21.0, 5.0, 9.0]

      iex> import Witchcraft.Functor, only: [<~: 2]
      ...> fn(a, b, c, d) -> a * b - c + d end <~ [1, 2] <<~ [3, 4] <<~ [5, 6] <<~ [7, 8]
      [5, 6, 4, 5, 6, 7, 5, 6, 8, 9, 7, 8, 10, 11, 9, 10]

  """
  defalias wrapped_funs <<~ wrapped, as: :provide

  @doc """
  Operator alias for `reverse_ap/2`, moving in the pipe direction

  ## Examples

      iex> [1, 2, 3] ~>> [fn x -> x + 1 end, fn y -> y * 10 end]
      [2, 10, 3, 20, 4, 30]

      iex> import Witchcraft.Functor
      ...>
      ...> [100, 50]
      ...> ~>> ([5, 2]     # Note the bracket
      ...> ~>> ([100, 200] # on both `Apply` lines
      ...> ~> fn(x, y, z) -> x * y / z end))
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]

  """
  defalias wrapped ~>> wrapped_funs, as: :supply

  @doc """
  Same as `convey/2`, but with all functions curried.

  ## Examples

      iex> [1, 2, 3]
      ...> |> supply([fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

  """
  @spec supply(Apply.t(), Apply.fun()) :: Apply.t()
  def supply(args, funs), do: convey(args, Functor.map(funs, &curry/1))

  @doc """
  Same as `ap/2`, but with all functions curried.

  ## Examples

      iex> [&+/2, &*/2]
      ...> |> provide([1, 2, 3])
      ...> |> ap([4, 5, 6])
      [5, 6, 7, 6, 7, 8, 7, 8, 9, 4, 5, 6, 8, 10, 12, 12, 15, 18]

  """
  @spec provide(Apply.fun(), Apply.t()) :: Apply.t()
  def provide(funs, args), do: funs |> Functor.map(&curry/1) |> ap(args)

  @doc """
  Sequence actions, replacing the first/previous values with the last argument

  This is essentially a sequence of actions forgetting the first argument

  ## Examples

      iex> [1, 2, 3]
      ...> |> then([4, 5, 6])
      ...> |> then([7, 8, 9])
      [
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9
      ]

      iex> {1, 2, 3} |> then({4, 5, 6}) |> then({7, 8, 9})
      {12, 15, 9}

  """
  @spec then(Apply.t(), Apply.t()) :: Apply.t()
  def then(wrapped_a, wrapped_b), do: over(&Quark.constant(&2, &1), wrapped_a, wrapped_b)

  @doc """
  Sequence actions, replacing the last argument with the first argument's values

  This is essentially a sequence of actions forgetting the second argument

  ## Examples

      iex> [1, 2, 3]
      ...> |> following([3, 4, 5])
      ...> |> following([5, 6, 7])
      [
        1, 1, 1, 1, 1, 1, 1, 1, 1,
        2, 2, 2, 2, 2, 2, 2, 2, 2,
        3, 3, 3, 3, 3, 3, 3, 3, 3
      ]

      iex> {1, 2, 3} |> following({4, 5, 6}) |> following({7, 8, 9})
      {12, 15, 3}

  """
  @spec following(Apply.t(), Apply.t()) :: Apply.t()
  def following(wrapped_a, wrapped_b), do: lift(wrapped_b, wrapped_a, &Quark.constant(&2, &1))

  @doc """
  Extends `Functor.lift/2` to apply arguments to a binary function

  ## Examples

      iex> lift([1, 2], [3, 4], &+/2)
      [4, 5, 5, 6]

      iex> [1, 2]
      ...> |> lift([3, 4], &*/2)
      [3, 6, 4, 8]

  """
  @spec lift(Apply.t(), Apply.t(), fun()) :: Apply.t()
  def lift(a, b, fun) do
    a
    |> lift(fun)
    |> fn f -> convey(b, f) end.()
  end

  @doc """
  Extends `lift` to apply arguments to a ternary function

  ## Examples

      iex> lift([1, 2], [3, 4], [5, 6], fn(a, b, c) -> a * b - c end)
      [-2, -3, 1, 0, -1, -2, 3, 2]

  """
  @spec lift(Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def lift(a, b, c, fun), do: a |> lift(b, fun) |> ap(c)

  @doc """
  Extends `lift` to apply arguments to a quaternary function

  ## Examples

      iex> lift([1, 2], [3, 4], [5, 6], [7, 8], fn(a, b, c, d) -> a * b - c + d end)
      [5, 6, 4, 5, 8, 9, 7, 8, 6, 7, 5, 6, 10, 11, 9, 10]

  """
  @spec lift(Apply.t(), Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def lift(a, b, c, d, fun), do: a |> lift(b, c, fun) |> ap(d)

  @doc """
  Extends `Functor.async_lift/2` to apply arguments to a binary function

  ## Examples

      iex> async_lift([1, 2], [3, 4], &+/2)
      [4, 5, 5, 6]

      iex> [1, 2]
      ...> |> async_lift([3, 4], &*/2)
      [3, 6, 4, 8]

  """
  @spec async_lift(Apply.t(), Apply.t(), fun()) :: Apply.t()
  def async_lift(a, b, fun) do
    a
    |> async_lift(fun)
    |> fn f -> async_convey(b, f) end.()
  end

  @doc """
  Extends `async_lift` to apply arguments to a ternary function

  ## Examples

      iex> async_lift([1, 2], [3, 4], [5, 6], fn(a, b, c) -> a * b - c end)
      [-2, -3, 1, 0, -1, -2, 3, 2]

  """
  @spec async_lift(Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def async_lift(a, b, c, fun), do: a |> async_lift(b, fun) |> async_ap(c)

  @doc """
  Extends `async_lift` to apply arguments to a quaternary function

  ## Examples

      iex> async_lift([1, 2], [3, 4], [5, 6], [7, 8], fn(a, b, c, d) -> a * b - c + d end)
      [5, 6, 4, 5, 8, 9, 7, 8, 6, 7, 5, 6, 10, 11, 9, 10]

  """
  @spec async_lift(Apply.t(), Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def async_lift(a, b, c, d, fun), do: a |> async_lift(b, c, fun) |> async_ap(d)

  @doc """
  Extends `over` to apply arguments to a binary function

  ## Examples

      iex> over(&+/2, [1, 2], [3, 4])
      [4, 5, 5, 6]

      iex> (&*/2)
      ...> |> over([1, 2], [3, 4])
      [3, 4, 6, 8]

  """
  @spec over(fun(), Apply.t(), Apply.t()) :: Apply.t()
  def over(fun, a, b), do: a |> lift(fun) |> ap(b)

  @doc """
  Extends `over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      iex> |> over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def over(fun, a, b, c), do: fun |> over(a, b) |> ap(c)

  @doc """
  Extends `over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      ...> |> over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def over(fun, a, b, c, d), do: fun |> over(a, b, c) |> ap(d)

  @doc """
  Extends `async_over` to apply arguments to a binary function

  ## Examples

      iex> async_over(&+/2, [1, 2], [3, 4])
      [4, 5, 5, 6]

      iex> (&*/2)
      ...> |> async_over([1, 2], [3, 4])
      [3, 4, 6, 8]

  """
  @spec async_over(fun(), Apply.t(), Apply.t()) :: Apply.t()
  def async_over(fun, a, b), do: a |> lift(fun) |> async_ap(b)

  @doc """
  Extends `async_over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      iex> |> async_over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec async_over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def async_over(fun, a, b, c), do: fun |> async_over(a, b) |> async_ap(c)

  @doc """
  Extends `async_over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      ...> |> async_over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec async_over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def async_over(fun, a, b, c, d), do: fun |> async_over(a, b, c) |> async_ap(d)
end

definst Witchcraft.Apply, for: Function do
  use Quark
  def convey(g, f), do: fn x -> curry(f).(x).(curry(g).(x)) end
end

definst Witchcraft.Apply, for: List do
  def convey(val_list, fun_list) when is_list(fun_list) do
    Enum.flat_map(val_list, fn(val) ->
      Enum.map(fun_list, fn fun -> fun.(val) end)
    end)
  end
end

# Contents must be semigroups
definst Witchcraft.Apply, for: Tuple do
  import TypeClass.Property.Generator, only: [generate: 1]
  use Witchcraft.Semigroup

  custom_generator(_) do
    {generate(""), generate(1), generate(0), generate(""), generate(""), generate("")}
  end

  def convey({v, w},          {a,          fun}), do: {v <> a, fun.(w)}
  def convey({v, w, x},       {a, b,       fun}), do: {v <> a, w <> b, fun.(x)}
  def convey({v, w, x, y},    {a, b, c,    fun}), do: {v <> a, w <> b, x <> c, fun.(y)}
  def convey({v, w, x, y, z}, {a, b, c, d, fun}) do
    {
      a <> v,
      b <> w,
      c <> x,
      d <> y,
      fun.(z)
    }
  end

  def convey(tuple_a, tuple_b) when tuple_size(tuple_a) == tuple_size(tuple_b) do
    last_index = tuple_size(tuple_a) - 1

    tuple_a
    |> Tuple.to_list()
    |> Enum.zip(Tuple.to_list(tuple_b))
    |> Enum.with_index()
    |> Enum.map(fn
      {{arg,  fun},   ^last_index} -> fun.(arg)
      {{left, right}, _}           -> left <> right
    end)
    |> List.to_tuple()
  end
end

import TypeClass

defclass Witchcraft.Apply do
  @moduledoc """
  An extension of `Witchcraft.Functor`, `Apply` provides a way to _apply_ arguments
  to functions when both are wrapped in the same kind of container. This can be
  seen as running function application "in a context".

  For a nice, illustrated introduction,
  see [Functors, Applicatives, And Monads In Pictures](http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html).

  ## Graphically

  If function application looks like like

      data |> function == result

  and a functor looks like this

      %Container<data> ~> function == %Container<result>

  then an apply looks like

      %Container<data> ~>> %Container<function> == %Container<result>

  which is similar to function application inside containers, plus the ability to
  attach special effects to applications.

                 data --------------- function ---------------> result
      %Container<data> --- %Container<function> ---> %Container<result>

  This lets us do functorial things like

  * continue applying values to a curried function resulting from a `Witchcraft.Functor.lift/2`
  * apply multiple functions to multiple arguments (with lists)
  * propogate some state (like [`Nothing`](https://hexdocs.pm/algae/Algae.Maybe.Nothing.html#content)
  in [`Algae.Maybe`](https://hexdocs.pm/algae/Algae.Maybe.html#content))

  but now with a much larger number of arguments, reuse partially applied functions,
  and run effects with the function container as well as the data container.

  ## Examples

      iex> ap([fn x -> x + 1 end, fn y -> y * 10 end], [1, 2, 3])
      [2, 3, 4, 10, 20, 30]

      iex> [100, 200]
      ...> |> Witchcraft.Functor.lift(fn(x, y, z) -> x * y / z end)
      ...> |> provide([5, 2])
      ...> |> provide([100, 50])
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
      # ↓                          ↓
      # 100 * 5 / 100          200 * 5 / 50

      iex> import Witchcraft.Functor
      ...>
      ...> [100, 200]
      ...> ~> fn(x, y, z) ->
      ...>   x * y / z
      ...> end <<~ [5, 2]
      ...>     <<~ [100, 50]
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
      # ↓                          ↓
      # 100 * 5 / 100          200 * 5 / 50

      %Algae.Maybe.Just{just: 42}
      ~> fn(x, y, z) ->
        x * y / z
      end <<~ %Algae.Maybe.Nothing{}
          <<~ %Algae.Maybe.Just{just: 99}
      #=> %Algae.Maybe.Nothing{}

  ## `convey` vs `ap`

  `convey` and `ap` essentially associate in opposite directions. For example,
  large data is _usually_ more efficient with `ap`, and large numbers of
  functions are _usually_ more efficient with `convey`.

  It's also more consistent consistency. In Elixir, we like to think of a "subject"
  being piped through a series of transformations. This places the function argument
  as the second argument. In `Witchcraft.Functor`, this was of little consequence.
  However, in `Apply`, we're essentially running superpowered function application.
  `ap` is short for `apply`, as to not conflict with `Kernel.apply/2`, and is meant
  to respect a similar API, with the function as the first argument. This also reads
  nicely when piped, as it becomes `[funs] |> ap([args1]) |> ap([args2])`,
  which is similar in structure to `fun.(arg2).(arg1)`.

  With potentially multiple functions being applied over potentially
  many arguments, we need to worry about ordering. `convey` not only flips
  the order of arguments, but also who is in control of ordering.
  `convey` typically runs each function over all arguments (`first_fun ⬸ all_args`),
  and `ap` runs all functions for each element (`first_arg ⬸ all_funs`).
  This may change the order of results, and is a feature, not a bug.

      iex> [1, 2, 3]
      ...> |> convey([&(&1 + 1), &(&1 * 10)])
      [
        2, 10, # [(1 + 1), (1 * 10)]
        3, 20, # [(2 + 1), (2 * 10)]
        4, 30  # [(3 + 1), (3 * 10)]
      ]

      iex> [&(&1 + 1), &(&1 * 10)]
      ...> |> ap([1, 2, 3])
      [
        2,  3,  4, # [(1 + 1),  (2 + 1),  (3 + 1)]
        10, 20, 30 # [(1 * 10), (2 * 10), (3 * 10)]
      ]

  ## Type Class

  An instance of `Witchcraft.Apply` must also implement `Witchcraft.Functor`,
  and define `Witchcraft.Apply.convey/2`.

      Functor  [map/2]
         ↓
       Apply   [convey/2]
  """

  alias __MODULE__
  use Quark

  alias Witchcraft.Functor
  extend Witchcraft.Functor
  use Witchcraft.Functor

  @type t   :: any()
  @type fun :: any()

  defmacro __using__(opts \\ []) do
    quote do
      use Witchcraft.Functor,     unquote(opts)
      import unquote(__MODULE__), unquote(opts)
    end
  end

  where do
    @doc """
    Pipe arguments to functions, when both are wrapped in the same
    type of data structure.

    ## Examples

        iex> [1, 2, 3]
        ...> |> convey([fn x -> x + 1 end, fn y -> y * 10 end])
        [2, 10, 3, 20, 4, 30]

    """
    @spec convey(Apply.t(), Apply.fun()) :: Apply.t()
    def convey(wrapped_args, wrapped_funs)
  end

  properties do
    def composition(data) do
      alias Witchcraft.Functor
      use Quark

      as = data |> generate() |> Functor.map(&inspect/1)
      fs = data |> generate() |> Functor.replace(fn x -> x <> x end)
      gs = data |> generate() |> Functor.replace(fn y -> y <> "foo" end)

      left  = Apply.convey(Apply.convey(as, gs), fs)

      right =
        fs
        |> Functor.lift(&compose/2)
        |> fn x -> Apply.convey(gs, x) end.()
        |> fn y -> Apply.convey(as, y) end.()

      equal?(left, right)
    end
  end

  @doc """
  Alias for `convey/2`.

  Why "hose"?

  * Pipes (`|>`) are application with arguments flipped
  * `ap/2` is like function application "in a context"
  * The opposite of `ap` is a contextual pipe
  * `hose`s are a kind of flexible pipe

  Q.E.D.

  ![](http://s2.quickmeme.com/img/fd/fd0baf5ada879021c32129fc7dea679bd7666e708df8ca8ca536da601ea3d29e.jpg)

  ## Examples

      iex> [1, 2, 3]
      ...> |> hose([fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

  """
  @spec hose(Apply.t(), Apply.fun()) :: Apply.t()
  def hose(wrapped_args, wrapped_funs), do: convey(wrapped_args, wrapped_funs)

  @doc """
  Reverse arguments and sequencing of `convey/2`.

  Conceptually this makes operations happen in
  a different order than `convey/2`, with the left-side arguments (functions) being
  run on all right-side arguments, in that order. We're altering the _sequencing_
  of function applications.

  ## Examples

      iex> ap([fn x -> x + 1 end, fn y -> y * 10 end], [1, 2, 3])
      [2, 3, 4, 10, 20, 30]

      # For comparison
      iex> convey([1, 2, 3], [fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

      iex> [100, 200]
      ...> |> Witchcraft.Functor.lift(fn(x, y, z) -> x * y / z end)
      ...> |> ap([5, 2])
      ...> |> ap([100, 50])
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
      # ↓                          ↓
      # 100 * 5 / 100          200 * 5 / 50

  """
  @spec ap(Apply.fun(), Apply.t()) :: Apply.t()
  def ap(wrapped_funs, wrapped) do
    lift(wrapped, wrapped_funs, fn(arg, fun) -> fun.(arg) end)
  end

  @doc """
  Async version of `convey/2`

  ## Examples

      iex> [1, 2, 3]
      ...> |> async_convey([fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

      0..10_000
      |> Enum.to_list()
      |> async_convey([
        fn x ->
          Process.sleep(500)
          x + 1
        end,
        fn y ->
          Process.sleep(500)
          y * 10
        end
      ])
      #=> [1, 0, 2, 10, 3, 30, ...] in around a second

  """
  @spec async_convey(Apply.t(), Apply.fun()) :: Apply.t()
  def async_convey(wrapped_args, wrapped_funs) do
    wrapped_args
    |> convey(lift(wrapped_funs, fn(fun, arg) ->
      Task.async(fn ->
        fun.(arg)
      end)
    end))
    |> map(&Task.await/1)
  end

  @doc """
  Async version of `ap/2`

  ## Examples

      iex> [fn x -> x + 1 end, fn y -> y * 10 end]
      ...> |> async_ap([1, 2, 3])
      [2, 3, 4, 10, 20, 30]

      [
        fn x ->
          Process.sleep(500)
          x + 1
        end,
        fn y ->
          Process.sleep(500)
          y * 10
        end
      ]
      |> async_ap(Enum.to_list(0..10_000))
      #=> [1, 2, 3, 4, ...] in around a second

  """
  @spec async_ap(Apply.fun(), Apply.t()) :: Apply.t()
  def async_ap(wrapped_funs, wrapped_args) do
    wrapped_funs
    |> lift(fn(fun, arg) ->
      Task.async(fn ->
        fun.(arg)
      end)
    end)
    |> ap(wrapped_args)
    |> map(&Task.await/1)
  end

  @doc """
  Operator alias for `ap/2`

  Moves against the pipe direction, but in the order of normal function application

  ## Examples

      iex> [fn x -> x + 1 end, fn y -> y * 10 end] <<~ [1, 2, 3]
      [2, 3, 4, 10, 20, 30]

      iex> import Witchcraft.Functor
      ...>
      ...> [100, 200]
      ...> ~> fn(x, y, z) -> x * y / z
      ...> end <<~ [5, 2]
      ...>     <<~ [100, 50]
      ...> ~> fn x -> x + 1 end
      [6.0, 11.0, 3.0, 5.0, 11.0, 21.0, 5.0, 9.0]

      iex> import Witchcraft.Functor, only: [<~: 2]
      ...> fn(a, b, c, d) -> a * b - c + d end <~ [1, 2] <<~ [3, 4] <<~ [5, 6] <<~ [7, 8]
      [5, 6, 4, 5, 6, 7, 5, 6, 8, 9, 7, 8, 10, 11, 9, 10]

  """
  defalias wrapped_funs <<~ wrapped, as: :provide

  @doc """
  Operator alias for `reverse_ap/2`, moving in the pipe direction

  ## Examples

      iex> [1, 2, 3] ~>> [fn x -> x + 1 end, fn y -> y * 10 end]
      [2, 10, 3, 20, 4, 30]

      iex> import Witchcraft.Functor
      ...>
      ...> [100, 50]
      ...> ~>> ([5, 2]     # Note the bracket
      ...> ~>> ([100, 200] # on both `Apply` lines
      ...> ~> fn(x, y, z) -> x * y / z end))
      [5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]

  """
  defalias wrapped ~>> wrapped_funs, as: :supply

  @doc """
  Same as `convey/2`, but with all functions curried.

  ## Examples

      iex> [1, 2, 3]
      ...> |> supply([fn x -> x + 1 end, fn y -> y * 10 end])
      [2, 10, 3, 20, 4, 30]

  """
  @spec supply(Apply.t(), Apply.fun()) :: Apply.t()
  def supply(args, funs), do: convey(args, Functor.map(funs, &curry/1))

  @doc """
  Same as `ap/2`, but with all functions curried.

  ## Examples

      iex> [&+/2, &*/2]
      ...> |> provide([1, 2, 3])
      ...> |> ap([4, 5, 6])
      [5, 6, 7, 6, 7, 8, 7, 8, 9, 4, 5, 6, 8, 10, 12, 12, 15, 18]

  """
  @spec provide(Apply.fun(), Apply.t()) :: Apply.t()
  def provide(funs, args), do: funs |> Functor.map(&curry/1) |> ap(args)

  @doc """
  Sequence actions, replacing the first/previous values with the last argument

  This is essentially a sequence of actions forgetting the first argument

  ## Examples

      iex> [1, 2, 3]
      ...> |> then([4, 5, 6])
      ...> |> then([7, 8, 9])
      [
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9,
        7, 8, 9
      ]

      iex> {1, 2, 3} |> then({4, 5, 6}) |> then({7, 8, 9})
      {12, 15, 9}

  """
  @spec then(Apply.t(), Apply.t()) :: Apply.t()
  def then(wrapped_a, wrapped_b), do: over(&Quark.constant(&2, &1), wrapped_a, wrapped_b)

  @doc """
  Sequence actions, replacing the last argument with the first argument's values

  This is essentially a sequence of actions forgetting the second argument

  ## Examples

      iex> [1, 2, 3]
      ...> |> following([3, 4, 5])
      ...> |> following([5, 6, 7])
      [
        1, 1, 1, 1, 1, 1, 1, 1, 1,
        2, 2, 2, 2, 2, 2, 2, 2, 2,
        3, 3, 3, 3, 3, 3, 3, 3, 3
      ]

      iex> {1, 2, 3} |> following({4, 5, 6}) |> following({7, 8, 9})
      {12, 15, 3}

  """
  @spec following(Apply.t(), Apply.t()) :: Apply.t()
  def following(wrapped_a, wrapped_b), do: lift(wrapped_b, wrapped_a, &Quark.constant(&2, &1))

  @doc """
  Extends `Functor.lift/2` to apply arguments to a binary function

  ## Examples

      iex> lift([1, 2], [3, 4], &+/2)
      [4, 5, 5, 6]

      iex> [1, 2]
      ...> |> lift([3, 4], &*/2)
      [3, 6, 4, 8]

  """
  @spec lift(Apply.t(), Apply.t(), fun()) :: Apply.t()
  def lift(a, b, fun) do
    a
    |> lift(fun)
    |> fn f -> convey(b, f) end.()
  end

  @doc """
  Extends `lift` to apply arguments to a ternary function

  ## Examples

      iex> lift([1, 2], [3, 4], [5, 6], fn(a, b, c) -> a * b - c end)
      [-2, -3, 1, 0, -1, -2, 3, 2]

  """
  @spec lift(Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def lift(a, b, c, fun), do: a |> lift(b, fun) |> ap(c)

  @doc """
  Extends `lift` to apply arguments to a quaternary function

  ## Examples

      iex> lift([1, 2], [3, 4], [5, 6], [7, 8], fn(a, b, c, d) -> a * b - c + d end)
      [5, 6, 4, 5, 8, 9, 7, 8, 6, 7, 5, 6, 10, 11, 9, 10]

  """
  @spec lift(Apply.t(), Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def lift(a, b, c, d, fun), do: a |> lift(b, c, fun) |> ap(d)

  @doc """
  Extends `Functor.async_lift/2` to apply arguments to a binary function

  ## Examples

      iex> async_lift([1, 2], [3, 4], &+/2)
      [4, 5, 5, 6]

      iex> [1, 2]
      ...> |> async_lift([3, 4], &*/2)
      [3, 6, 4, 8]

  """
  @spec async_lift(Apply.t(), Apply.t(), fun()) :: Apply.t()
  def async_lift(a, b, fun) do
    a
    |> async_lift(fun)
    |> fn f -> async_convey(b, f) end.()
  end

  @doc """
  Extends `async_lift` to apply arguments to a ternary function

  ## Examples

      iex> async_lift([1, 2], [3, 4], [5, 6], fn(a, b, c) -> a * b - c end)
      [-2, -3, 1, 0, -1, -2, 3, 2]

  """
  @spec async_lift(Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def async_lift(a, b, c, fun), do: a |> async_lift(b, fun) |> async_ap(c)

  @doc """
  Extends `async_lift` to apply arguments to a quaternary function

  ## Examples

      iex> async_lift([1, 2], [3, 4], [5, 6], [7, 8], fn(a, b, c, d) -> a * b - c + d end)
      [5, 6, 4, 5, 8, 9, 7, 8, 6, 7, 5, 6, 10, 11, 9, 10]

  """
  @spec async_lift(Apply.t(), Apply.t(), Apply.t(), Apply.t(), fun()) :: Apply.t()
  def async_lift(a, b, c, d, fun), do: a |> async_lift(b, c, fun) |> async_ap(d)

  @doc """
  Extends `over` to apply arguments to a binary function

  ## Examples

      iex> over(&+/2, [1, 2], [3, 4])
      [4, 5, 5, 6]

      iex> (&*/2)
      ...> |> over([1, 2], [3, 4])
      [3, 4, 6, 8]

  """
  @spec over(fun(), Apply.t(), Apply.t()) :: Apply.t()
  def over(fun, a, b), do: a |> lift(fun) |> ap(b)

  @doc """
  Extends `over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      iex> |> over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def over(fun, a, b, c), do: fun |> over(a, b) |> ap(c)

  @doc """
  Extends `over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      ...> |> over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def over(fun, a, b, c, d), do: fun |> over(a, b, c) |> ap(d)

  @doc """
  Extends `async_over` to apply arguments to a binary function

  ## Examples

      iex> async_over(&+/2, [1, 2], [3, 4])
      [4, 5, 5, 6]

      iex> (&*/2)
      ...> |> async_over([1, 2], [3, 4])
      [3, 4, 6, 8]

  """
  @spec async_over(fun(), Apply.t(), Apply.t()) :: Apply.t()
  def async_over(fun, a, b), do: a |> lift(fun) |> async_ap(b)

  @doc """
  Extends `async_over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      iex> |> async_over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec async_over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def async_over(fun, a, b, c), do: fun |> async_over(a, b) |> async_ap(c)

  @doc """
  Extends `async_over` to apply arguments to a ternary function

  ## Examples

      iex> fn(a, b, c) -> a * b - c end
      ...> |> async_over([1, 2], [3, 4], [5, 6])
      [-2, -3, -1, -2, 1, 0, 3, 2]

  """
  @spec async_over(fun(), Apply.t(), Apply.t(), Apply.t()) :: Apply.t()
  def async_over(fun, a, b, c, d), do: fun |> async_over(a, b, c) |> async_ap(d)
end

definst Witchcraft.Apply, for: Function do
  use Quark
  def convey(g, f), do: fn x -> curry(f).(x).(curry(g).(x)) end
end

definst Witchcraft.Apply, for: List do
  def convey(val_list, fun_list) when is_list(fun_list) do
    Enum.flat_map(val_list, fn(val) ->
      Enum.map(fun_list, fn fun -> fun.(val) end)
    end)
  end
end

# Contents must be semigroups
definst Witchcraft.Apply, for: Tuple do
  import TypeClass.Property.Generator, only: [generate: 1]
  use Witchcraft.Semigroup

  custom_generator(_) do
    {generate(""), generate(1), generate(0), generate(""), generate(""), generate("")}
  end

  def convey({v, w},          {a,          fun}), do: {v <> a, fun.(w)}
  def convey({v, w, x},       {a, b,       fun}), do: {v <> a, w <> b, fun.(x)}
  def convey({v, w, x, y},    {a, b, c,    fun}), do: {v <> a, w <> b, x <> c, fun.(y)}
  def convey({v, w, x, y, z}, {a, b, c, d, fun}) do
    {
      a <> v,
      b <> w,
      c <> x,
      d <> y,
      fun.(z)
    }
  end

  def convey(tuple_a, tuple_b) when tuple_size(tuple_a) == tuple_size(tuple_b) do
    last_index = tuple_size(tuple_a) - 1

    tuple_a
    |> Tuple.to_list()
    |> Enum.zip(Tuple.to_list(tuple_b))
    |> Enum.with_index()
    |> Enum.map(fn
      {{arg,  fun},   ^last_index} -> fun.(arg)
      {{left, right}, _}           -> left <> right
    end)
    |> List.to_tuple()
  end
end


Handle stringly keyed maps!

Handle stringly keyed maps!

defmodule Destructure do
  @moduledoc """
  Provides helpers for destructuring Elixir data structures. See the `d/1` macro
  for more information.
  """

  @doc """
  Easy destructuring of `map`, `structs`, `keyword`, with atom keys.
  String keys are supported via a special syntax `d(s%{param})`.

  ## Examples

  The macro can be used in simple match statements:

      iex> d(%{name}) = %{name: "Joe"}
      ...> name
      "Joe"

  Or in case/for/with statements.

      iex> case %{name: "Mike"} do
      ...>   d%{name} -> name
      ...> end
      "Mike"

  It can be used inside a complex match:

      iex> %{body: d%{name}} = %{body: %{name: "Robert"}}
      ...> name
      "Robert"

  With multiple keys:

      iex> d(%{first, last}) = %{first: "Daniel", last: "Berkompas"}
      ...> {first, last}
      {"Daniel", "Berkompas"}

  With multiple keys and custom variable naming:

      iex> d(%{first, last, email: mail}) = %{first: "Daniel", last: "Berkompas", email: "top@secret.com"}
      ...> {first, last, mail}
      {"Daniel", "Berkompas", "top@secret.com"}

  For Maps with String Keys:

      iex> d(s%{name}) = %{"name" => "Daniel Berkompas"}
      ...> name
      "Daniel Berkompas"

  With multiple keys:

      iex> d(s%{name, email}) = %{"name" => "Daniel Berkompas", "email" => "top@secret.com"}
      ...> {name, email}
      {"Daniel Berkompas", "top@secret.com"}

  With multiple keys and custom variable naming:

      iex> d(s%{name, "email" => mail}) = %{"name" => "Daniel Berkompas", "email" => "top@secret.com"}
      ...> {name, mail}
      {"Daniel Berkompas", "top@secret.com"}

  For structs:

      iex> d(%Person{name}) = %Person{name: "Daniel Berkompas"}
      ...> name
      "Daniel Berkompas"

  With multiple keys:

      iex> d(%Person{name, email}) = %Person{name: "Daniel Berkompas", email: "top@secret.com"}
      ...> {name, email}
      {"Daniel Berkompas", "top@secret.com"}

  With multiple keys and custom variable naming:

      iex> d(%Person{name, email: mail}) = %Person{name: "Daniel Berkompas", email: "top@secret.com"}
      ...> {name, mail}
      {"Daniel Berkompas", "top@secret.com"}

  For keyword lists:

      iex> d([name]) = [name: "Daniel"]
      ...> name
      "Daniel"

  With multiple keys:

      iex> d([first, last]) = [first: "Daniel", last: "Berkompas"]
      ...> [first, last]
      ["Daniel", "Berkompas"]

  With multiple keys and custom assignment:

      iex> d([first, last, email: mail]) = [first: "Daniel", last: "Berkompas", email: "top@secret.com"]
      ...> [first, last, mail]
      ["Daniel", "Berkompas", "top@secret.com"]

  And in function definitions:

      iex> defmodule Test do
      ...>   import Destructure
      ...>   def test(d%{name}) do
      ...>     name
      ...>   end
      ...> end
      ...> Test.test(%{name: "Daniel"})
      "Daniel"
  """
  # Handle maps, including ones with multiple keys
  # {%{}, [], [{:first, [], Elixir}, {:second, [], Elixir}]}
  defmacro d({:%{}, context, args}) do
    {:%{}, context, Enum.map(args, &pattern/1)}
  end

  # Handle string maps, including ones with multiple keys
  # {:s, [], [{:%{}, [], [{:stuff, [], Elixir}, {:things, [], Elixir}]}]}
  defmacro d({:s, _, [{:%{}, context, args}]}) do
    {:%{}, context, Enum.map(args, &s_pattern/1)}
  end

  # Handle structs, including ones with multiple keys
  # {:%, [],
  #  [{:__aliases__, [alias: false], [:Namespace]},
  #   {:%{}, [], [{:first, [], Elixir}, {:second, [], Elixir}]}]}
  defmacro d({:%, x_context, [namespace, {:%{}, y_context, args}]}) do
    {:%, x_context, [namespace, {:%{}, y_context, Enum.map(args, &pattern/1)}]}
  end

  # Handle 1 and 3+ element tuples
  # {:{}, [], [{:first, [], Elixir}]}
  defmacro d({:{}, _, args}) do
    Enum.map(args, &pattern/1)
  end

  # Handle the special case of two-element tuples, whose ASTs look like
  # {{:first, [], Elixir}, {:last, [], Elixir}}
  defmacro d({first, second}) do
    [pattern(first), pattern(second)]
  end

  # Handle keyword list using square bracket
  defmacro d(list) when is_list(list) do
    Enum.map(list, &pattern/1)
  end

  defp s_pattern({key, _, _} = variable) do
    {key |> Atom.to_string(), variable}
  end

  defp s_pattern(otherwise), do: pattern(otherwise)

  defp pattern({key, _, _} = variable) do
    {key, variable}
  end

  defp pattern([arg]) do
    arg
  end

  defp pattern(other) do
    other
  end
end

defmodule Destructure do
  @moduledoc """
  Provides helpers for destructuring Elixir data structures. See the `d/1` macro
  for more information.
  """

  @doc """
  Easy destructuring of `map`, `structs`, and `keyword`, with atom keys only.
  String keys are not supported because Elixir raises a `SyntaxError` on syntax
  like `%{"name"}`. Optional key also need to be placed at the last for the same
  reason with the string key.

  ## Examples

  The macro can be used in simple match statements:

      iex> d(%{name}) = %{name: "Joe"}
      ...> name
      "Joe"

  Or in case/for/with statements.

      iex> case %{name: "Mike"} do
      ...>   d%{name} -> name
      ...> end
      "Mike"

  It can be used inside a complex match:

      iex> %{body: d%{name}} = %{body: %{name: "Robert"}}
      ...> name
      "Robert"

  With multiple keys:

      iex> d(%{first, last}) = %{first: "Daniel", last: "Berkompas"}
      ...> {first, last}
      {"Daniel", "Berkompas"}

  With multiple keys and custom variable naming:

      iex> d(%{first, last, email: mail}) = %{first: "Daniel", last: "Berkompas", email: "top@secret.com"}
      ...> {first, last, mail}
      {"Daniel", "Berkompas", "top@secret.com"}

  For structs:

      iex> d(%Person{name}) = %Person{name: "Daniel Berkompas"}
      ...> name
      "Daniel Berkompas"

  With multiple keys:

      iex> d(%Person{name, email}) = %Person{name: "Daniel Berkompas", email: "top@secret.com"}
      ...> {name, email}
      {"Daniel Berkompas", "top@secret.com"}

  With multiple keys and custom variable naming:

      iex> d(%Person{name, email: mail}) = %Person{name: "Daniel Berkompas", email: "top@secret.com"}
      ...> {name, mail}
      {"Daniel Berkompas", "top@secret.com"}

  For keyword lists:

      iex> d([name]) = [name: "Daniel"]
      ...> name
      "Daniel"

  With multiple keys:

      iex> d([first, last]) = [first: "Daniel", last: "Berkompas"]
      ...> [first, last]
      ["Daniel", "Berkompas"]

  With multiple keys and custom assignment:

      iex> d([first, last, email: mail]) = [first: "Daniel", last: "Berkompas", email: "top@secret.com"]
      ...> [first, last, mail]
      ["Daniel", "Berkompas", "top@secret.com"]

  And in function definitions:

      iex> defmodule Test do
      ...>   import Destructure
      ...>   def test(d%{name}) do
      ...>     name
      ...>   end
      ...> end
      ...> Test.test(%{name: "Daniel"})
      "Daniel"
  """
  # Handle maps, including ones with multiple keys
  # {%{}, [], [{:first, [], Elixir}, {:second, [], Elixir}]}
  defmacro d({:%{}, context, args}) do
    {:%{}, context, Enum.map(args, &pattern/1)}
  end

  # Handle structs, including ones with multiple keys
  # {:%, [],
  #  [{:__aliases__, [alias: false], [:Namespace]},
  #   {:%{}, [], [{:first, [], Elixir}, {:second, [], Elixir}]}]}
  defmacro d({:%, x_context, [namespace, {:%{}, y_context, args}]}) do
    {:%, x_context, [namespace, {:%{}, y_context, Enum.map(args, &pattern/1)}]}
  end

  # Handle 1 and 3+ element tuples
  # {:{}, [], [{:first, [], Elixir}]}
  defmacro d({:{}, _, args}) do
    Enum.map(args, &pattern/1)
  end

  # Handle the special case of two-element tuples, whose ASTs look like
  # {{:first, [], Elixir}, {:last, [], Elixir}}
  defmacro d({first, second}) do
    [pattern(first), pattern(second)]
  end

  # Handle keyword list using square bracket
  defmacro d(list) when is_list(list) do
    Enum.map(list, &pattern/1)
  end

  defp pattern({key, _, _} = variable) do
    {key, variable}
  end
  defp pattern([arg]) do
    arg
  end
  defp pattern(other) do
    other
  end
end


Document the usage of tricky ref

Document the usage of tricky ref

defprotocol PDict, [
#  @doc """
#  Returns a list containing all dict's keys.
#
#  For Keyword and Orddict, the keys are guaranteed to be sorted. For other
#  types of dicts, the order is not enforced.
#
#  ## Examples
#
#      PDict.keys [a: 1, b: 2]  #=> [:a,:b]
#
#  """
  keys(dict),

#  @doc """
#  Returns a list containing all dict's values.
#
#  ## Examples
#
#      PDict.values [a: 1, b: 2]  #=> [1,2]
#
#  """
  values(dict),

#  @doc """
#  Returns the number of elements in `dict`.
#
#  ## Examples
#
#      PDict.size [a: 1, b: 2]  #=> 2
#
#  """
  size(dict),

#  @doc """
#  Returns whether the given key exists in the given dict.
#
#  ## Examples
#
#      PDict.has_key?([a:, 1], :a)  #=> true
#      PDict.has_key?([a:, 1], :b)  #=> false
#  """
  has_key?(dict, key),

#  @doc """
#  Returns the value associated with `key` in `dict`. If `dict` does not
#  contain `key`, returns `default` (or nil if not provided).
#
#  ## Examples
#
#      PDict.get [a: 1], :a     #=> 1
#      PDict.get [a: 1], :b     #=> nil
#      PDict.get [a: 1], :b, 3  #=> 3
#  """
  get(dict, key),
  get(dict, key, default),

#  @doc """
#  Stores the given `value` under `key` in `dict`.
#  If `dict` already has `key`, the stored value is replaced by the new one.
#
#  ## Examples
#
#      PDict.put [a: 1, b: 2], :a, 3
#      #=> [a: 3, b: 2]
#      PDict.put [a: 1, b: 2], {:c, 3}
#      #=> [a: 1, b: 2, c: 3]
#  """
  put(dict, pair),
  put(dict, key, val),

#  @doc """
#  Removes the entry stored under the given key from `dict`.
#  If `dict` does not contain `key`, returns the dictionary unchanged.
#
#  ## Examples
#
#      PDict.delete [a: 1, b: 2], :a  #=> [b: 2]
#      PDict.delete [b: 2], :a        #=> [b: 2]
#  """
  delete(dict, key),

#  @doc """
#  Merges two dicts into one. If the dicts have duplicated entries, the one
#  given as second argument wins.
#
#  ## Examples
#
#      PDict.merge [a: 1, b: 2], [a: 3, d: 4]
#      #=> [a:3, b:2, d: 4]
#  """
  merge(dict1, dict2),

#  @doc """
#  Merges two dicts into one. If the dicts have duplicated entries, the given
#  function is invoked to solve conflicts.
#
#  ## Examples
#
#      PDict.merge [a: 1, b: 2], [a: 3, d: 4], fn(_k, v1, v2) ->
#        v1 + v2
#      end
#      #=> [a: 4, b: 2, d: 4]
#  """
  merge(dict1, dict2, fun),

#  @doc """
#  Update a value in `dict` by calling `fun` on the value to get a new
#  value. An exception is generated if `key` is not present in the dict.
#
#  ## Examples
#
#      PDict.update [a: 1, b: 2], :a, fn(val) -> -val end
#      #=> [a: -1, b: 2]
#
#  """
  update(dict, key, fun),

#  @doc """
#  Update a value in `dict` by calling `fun` on the value to get a new value. If
#  `key` is not present in `dict` then `initial` will be stored as the first
#  value.
#
#  ## Examples
#
#      PDict.update [a: 1, b: 2], :c, 3, fn(val) -> -val end
#      #=> [a: 1, b: 2, c: 3]
#
#  """
  update(dict, key, initial, fun),

#  @doc """
#  Returns an empty dict of the same type as `dict`.
#  """
  empty(dict)
], only: [Record, List]

defmodule Dict.Common do
  defmacro __using__(module, impl_ref) do
    # This is an optimization trick that allows us to reference the
    # implementation module directly
    ref = Module.concat(PDict, impl_ref)
    quote do
      @doc """
      Creates a new empty dict.
      """
      def new do
        unquote(ref).empty(nil)
      end

      @doc """
      Creates a new dict with one entry.
      """
      def new({key, value}) do
        unquote(ref).put unquote(module).new(), {key, value}
      end

      @doc """
      Creates a new dict from a list of pairs.

      ## Examples

          Dict.new [{:b,1},{:a,2}]
          #=> [a: 1, b: 2]

      """
      def new(pairs) when is_list(pairs) do
        Enum.reduce pairs, unquote(module).new(), fn(pair, dict) ->
          unquote(ref).put(dict, pair)
        end
      end

      @doc """
      Creates a new dict from a list of elements with the
      help of the transformation function.

      ## Examples

          Dict.new ["a", "b"], fn(x) -> {x, x} end
          #=> ["a": "a", "b": "b"]
      """
      def new(list, transform) when is_list(list) and is_function(transform) do
        Enum.reduce list, unquote(module).new(), fn(i, dict) ->
          pair = transform.(i)
          unquote(ref).put(dict, pair)
        end
      end

      @doc """
      Creates a new dict with one entry for each element in `keys` and a
      corresponding element in `values`. Raises an error if `keys` and `values`
      have different size.
      """
      def new(keys, values) when is_list(keys) and is_list(values) do
        if :erlang.length(keys) !== :erlang.length(values) do
          raise ArgumentError, message: "Both arguments must have equal size"
        else:
          unquote(module).new List.zip(keys, values)
        end
      end
    end
  end
end

defprotocol PDict, [
#  @doc """
#  Returns a list containing all dict's keys.
#
#  For Keyword and Orddict, the keys are guaranteed to be sorted. For other
#  types of dicts, the order is not enforced.
#
#  ## Examples
#
#      PDict.keys [a: 1, b: 2]  #=> [:a,:b]
#
#  """
  keys(dict),

#  @doc """
#  Returns a list containing all dict's values.
#
#  ## Examples
#
#      PDict.values [a: 1, b: 2]  #=> [1,2]
#
#  """
  values(dict),

#  @doc """
#  Returns the number of elements in `dict`.
#
#  ## Examples
#
#      PDict.size [a: 1, b: 2]  #=> 2
#
#  """
  size(dict),

#  @doc """
#  Returns whether the given key exists in the given dict.
#
#  ## Examples
#
#      PDict.has_key?([a:, 1], :a)  #=> true
#      PDict.has_key?([a:, 1], :b)  #=> false
#  """
  has_key?(dict, key),

#  @doc """
#  Returns the value associated with `key` in `dict`. If `dict` does not
#  contain `key`, returns `default` (or nil if not provided).
#
#  ## Examples
#
#      PDict.get [a: 1], :a     #=> 1
#      PDict.get [a: 1], :b     #=> nil
#      PDict.get [a: 1], :b, 3  #=> 3
#  """
  get(dict, key),
  get(dict, key, default),

#  @doc """
#  Stores the given `value` under `key` in `dict`.
#  If `dict` already has `key`, the stored value is replaced by the new one.
#
#  ## Examples
#
#      PDict.put [a: 1, b: 2], :a, 3
#      #=> [a: 3, b: 2]
#      PDict.put [a: 1, b: 2], {:c, 3}
#      #=> [a: 1, b: 2, c: 3]
#  """
  put(dict, pair),
  put(dict, key, val),

#  @doc """
#  Removes the entry stored under the given key from `dict`.
#  If `dict` does not contain `key`, returns the dictionary unchanged.
#
#  ## Examples
#
#      PDict.delete [a: 1, b: 2], :a  #=> [b: 2]
#      PDict.delete [b: 2], :a        #=> [b: 2]
#  """
  delete(dict, key),

#  @doc """
#  Merges two dicts into one. If the dicts have duplicated entries, the one
#  given as second argument wins.
#
#  ## Examples
#
#      PDict.merge [a: 1, b: 2], [a: 3, d: 4]
#      #=> [a:3, b:2, d: 4]
#  """
  merge(dict1, dict2),

#  @doc """
#  Merges two dicts into one. If the dicts have duplicated entries, the given
#  function is invoked to solve conflicts.
#
#  ## Examples
#
#      PDict.merge [a: 1, b: 2], [a: 3, d: 4], fn(_k, v1, v2) ->
#        v1 + v2
#      end
#      #=> [a: 4, b: 2, d: 4]
#  """
  merge(dict1, dict2, fun),

#  @doc """
#  Update a value in `dict` by calling `fun` on the value to get a new
#  value. An exception is generated if `key` is not present in the dict.
#
#  ## Examples
#
#      PDict.update [a: 1, b: 2], :a, fn(val) -> -val end
#      #=> [a: -1, b: 2]
#
#  """
  update(dict, key, fun),

#  @doc """
#  Update a value in `dict` by calling `fun` on the value to get a new value. If
#  `key` is not present in `dict` then `initial` will be stored as the first
#  value.
#
#  ## Examples
#
#      PDict.update [a: 1, b: 2], :c, 3, fn(val) -> -val end
#      #=> [a: 1, b: 2, c: 3]
#
#  """
  update(dict, key, initial, fun),

#  @doc """
#  Returns an empty dict of the same type as `dict`.
#  """
  empty(dict)
], only: [Record, List]

defmodule Dict.Common do
  defmacro __using__(module, impl_ref) do
    ref = Module.concat(PDict, impl_ref)
    quote do
      @doc """
      Creates a new empty dict.
      """
      def new do
        unquote(ref).empty(nil)
      end

      @doc """
      Creates a new dict with one entry.
      """
      def new({key, value}) do
        unquote(ref).put unquote(module).new(), {key, value}
      end

      @doc """
      Creates a new dict from a list of pairs.

      ## Examples

          Dict.new [{:b,1},{:a,2}]
          #=> [a: 1, b: 2]

      """
      def new(pairs) when is_list(pairs) do
        Enum.reduce pairs, unquote(module).new(), fn(pair, dict) ->
          unquote(ref).put(dict, pair)
        end
      end

      @doc """
      Creates a new dict from a list of elements with the
      help of the transformation function.

      ## Examples

          Dict.new ["a", "b"], fn(x) -> {x, x} end
          #=> ["a": "a", "b": "b"]
      """
      def new(list, transform) when is_list(list) and is_function(transform) do
        Enum.reduce list, unquote(module).new(), fn(i, dict) ->
          pair = transform.(i)
          unquote(ref).put(dict, pair)
        end
      end

      @doc """
      Creates a new dict with one entry for each element in `keys` and a
      corresponding element in `values`. Raises an error if `keys` and `values`
      have different size.
      """
      def new(keys, values) when is_list(keys) and is_list(values) do
        if :erlang.length(keys) !== :erlang.length(values) do
          raise ArgumentError, message: "Both arguments must have equal size"
        else:
          unquote(module).new List.zip(keys, values)
        end
      end
    end
  end
end