@spec process(Order.t()) :: Order.t()
def process(%Order{id: integer} = order)...
@spec speed(path :: float, duration :: float) :: float <---- How can we express business rules mentioned above?
def speed(path, time)...
defmodule Order do
defstruct [:id, ...]
deftag Id, for_value_of: non_neg_integer
@type t :: %__MODULE__{id: Id.t(), ...}
end
alias Order.Id, as: OrderId
def process(%Order{id: OrderId ~ id} == order)...
deftag Meter, for_value_of: float
deftag Kilometer, for_value_of: float
deftag Path, for_value_of: Meter.t() | Kilometer.t()
deftag Sec, for_value_of: float
deftag MeterPerSec, for_value_of: float
@spec speed(path :: Path.t(), duration :: Sec.t()) :: MeterPerSec.t()
def speed(Path ~ Meter ~ path, Sec ~ duration), do: MeterPerSec ~ path / duration
def speed(Path ~ Kilometer ~ path, Sec ~ duration), do: MeterPerSec ~ path * 1000.0 / duration
speed(Path ~ Kilometer ~ 30.0, Sec ~ 3600 * 3600)
mix dialyzer --format short
lib/variants.ex:38:call The function call speed will not succeed.
...
lib/variants.ex:89:call The function call print_score will not succeed.
defmodule Tag do
defmacro deftag(name, for_value_of: type) do
quote do
defmodule unquote(name), do: @type(t :: {__MODULE__, unquote(type)})
end
end
defmacro deftag(name) do
quote do
defmodule unquote(name), do: @type(t :: __MODULE__)
end
end
defmacro a >>> b do
quote do
{unquote(a), unquote(b)}
end
end
end
defmodule Variants do
defmodule Speed do
import Tag
deftag Meter, for_value_of: float
deftag Kilometer, for_value_of: float
deftag Sec, for_value_of: float
deftag MeterPerSec, for_value_of: float
@type path :: Meter.t() | Kilometer.t()
@type duration :: Sec.t()
@type speed :: MeterPerSec.t()
@spec speed(path, duration) :: speed
def speed(Meter >>> path, Sec >>> duration), do: MeterPerSec >>> (path / duration)
def speed(Kilometer >>> path, Sec >>> duration), do: MeterPerSec >>> (path * 1000.0 / duration)
def speed_fail_1(), do: speed(1.0, 3.0)
def speed_fail_2(), do: speed(Sec >>> 1.0, Meter >>> 2.0)
@spec speed_ok :: MeterPerSec.t()
def speed_ok(), do: speed(Kilometer >>> 0.01, Sec >>> 2.0)
end
defmodule Tennis do
# https://en.wikipedia.org/wiki/Tennis_scoring_system
import Tag
deftag Player, for_value_of: :one | :two
deftag PointValue, for_value_of: :love | :fifteen | :thirty
deftag Points, for_value_of: {PointValue.t(), PointValue.t()}
deftag Forty, for_value_of: {Player.t(), PointValue.t()}
deftag Deuce # option has no associated value
deftag Advantage, for_value_of: Player.t()
deftag Game, for_value_of: Player.t()
deftag Score, for_value_of: Points.t() | Forty.t() | Deuce.t() | Advantage.t() | Game.t()
@spec print_score(Score.t()) :: :ok
def print_score(Score >>> score) do
IO.puts(
case score do
Points >>> {PointValue >>> p1, PointValue >>> p2} ->
"points: #{Atom.to_string(p1)}, #{Atom.to_string(p2)}"
Deuce ->
"both players have 40 points"
Advantage >>> (Player >>> player) ->
"player #{Atom.to_string(player)} has 41 and other 40"
end
)
end
@spec score_ok(:start | :middle | :final) :: :ok
def score_ok(:start),
# add brackets to overcome left to right precedence
do: print_score(Score >>> (Points >>> {PointValue >>> :love, PointValue >>> :love}))
def score_ok(:middle),
do: print_score(Score >>> Deuce)
def score_ok(:finals),
do: print_score(Score >>> (Advantage >>> (Player >>> :one)))
def score_fail_print(),
do: print_score(Score >>> (Game >>> :one))
end
defmodule CRM do
import Tag
defmodule Order do
@keys [
:id,
:customer_id,
:shipping_address,
:billing_address,
:order_lines,
:amount_to_bill
]
@enforce_keys @keys
defstruct @keys
deftag Id, for_value_of: String.t()
deftag CustomerId, for_value_of: String.t()
deftag BillingAmount # type of the associated value hasn't been known yet.
@type t :: %__MODULE__{
id: Id.t(),
customer_id: CustomerId.t(),
shipping_address: Address.t(),
billing_address: Address.t(),
order_lines: [OrderLine.t()],
amount_to_bill: BillingAmount.t()
}
@spec new(
Id.t(),
CustomerId.t(),
shipping_address :: Address.t(),
billing_address :: Address.t(),
[OrderLine.t()],
BillingAmount.t()
) :: t()
def new(id, customer_id, shipping_address, billing_address, order_lines, amount_to_bill) do
%__MODULE__{
id: id,
customer_id: customer_id,
shipping_address: billing_address,
billing_address: shipping_address,
order_lines: order_lines,
amount_to_bill: amount_to_bill
}
end
end
defmodule Order.Address do
@enforce_keys [:country, :city, :street1, :house]
defstruct [:country, :city, :street1, :street2, :house, :comment]
deftag Country, for_value_of: :usa | :germany | :brazil
deftag City, for_value_of: String.t()
deftag Street, for_value_of: String.t()
deftag House, for_value_of: String.t()
deftag Comment, for_value_of: String.t()
@type t :: %__MODULE__{
country: Country.t(),
city: City.t(),
street1: Street.t(),
street2: Street.t() | nil,
house: House.t(),
comment: Comment.t() | nil
}
@spec new(Country.t(), City.t(), Street.t(), House.t()) :: t()
def new(country, city, street1, house) do
%__MODULE__{country: country, city: city, street1: street1, house: house}
end
end
defmodule Order.OrderLine do
@keys [:id, :order_id, :product_code, :order_quantity, :price]
@enforce_keys @keys
defstruct @keys
deftag Id, for_value_of: non_neg_integer
deftag ProductCode, for_value_of: String.t()
deftag Unit, for_value_of: non_neg_integer
deftag Kilos, for_value_of: float
deftag OrderQuantity, for_value_of: Unit.t() | Kilos.t()
deftag Price, for_value_of: float
@opaque t :: %__MODULE__{
id: Id.t(),
order_id: Order.Id.t(),
product_code: ProductCode.t(),
order_quantity: OrderQuantity.t(),
price: Price.t()
}
@spec new(Id.t(), Order.Id.t(), ProductCode.t(), OrderQuantity.t(), Price.t()) :: t()
def new(id, order_id, product_code, order_quantity, price) do
%__MODULE__{
id: id,
order_id: order_id,
product_code: product_code,
order_quantity: order_quantity,
price: price
}
end
end
# usage
alias Order.{Id, CustomerId, Address, OrderLine, BillingAmount}
alias Order.Address.{Country, City, Street, House}
alias Order.OrderLine.Id, as: OrderLineId
alias Order.OrderLine.{ProductCode, Kilos, Unit, OrderQuantity, Price}
@spec new_order_bananas(
id :: String.t(),
customer_id :: String.t(),
country :: :usa | :germany,
city :: String.t(),
street :: String.t(),
house :: String.t(),
kilos :: float,
price :: float
) :: Order.t()
def new_order_bananas(
id,
customer_id,
country,
city,
street,
house,
kilos,
price
) do
addr = Address.new(Country >>> country, City >>> city, Street >>> street, House >>> house)
Order.new(
Id >>> id,
CustomerId >>> customer_id,
addr,
addr,
[
OrderLine.new(
OrderLineId >>> 0,
Id >>> id,
ProductCode >>> "AHBN",
OrderQuantity >>> (Kilos >>> kilos),
Price >>> price
)
],
BillingAmount
)
end
@spec add_line(
order :: Order.t(),
code :: ProductCode.t(),
units :: Unit.t(),
price :: Price.t()
) :: Order.t()
def add_line(%Order{id: ord_id, order_lines: lines} = order, code, units, price) do
%OrderLine{id: OrderLineId >>> ln_id} =
Enum.max_by(lines, fn %OrderLine{id: OrderLineId >>> id1} -> id1 end)
%Order{
order
| order_lines: [
OrderLine.new(
OrderLineId >>> (ln_id + 1),
ord_id,
code,
OrderQuantity >>> units,
price
)
| lines
]
}
end
@spec bananas_order_ok :: Order.t()
def bananas_order_ok do
order =
new_order_bananas(
"12143",
"AXC-0033",
:germany,
"Hamburg",
"Feldstrasse",
"18",
120.0,
0.35
)
add_line(order, ProductCode >>> "BOX-005", Unit >>> 1, Price >>> 1.00)
end
def oranges_line_dialyzer_fail do
OrderLine.new(
1125,
Id >>> nil,
ProductCode >>> "AHORJ",
OrderQuantity >>> (Kilos >>> 10),
Price >>> 0.14
)
end
end
end
elixir -v
Erlang/OTP 22 [erts-10.6.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Elixir 1.10.2 (compiled with Erlang/OTP 21)
defmodule Bug do
defmacro a | b do
quote do
{unquote(a), unquote(b)}
end
end
def do_foo, do: foo(:first | :next | 1)
def foo(:first | :next | bar), do: IO.puts(bar)
end
mix compile
Compiling 1 file (.ex)
== Compilation error in file lib/bug.ex ==
** (FunctionClauseError) no function clause matching in :v3_core.pattern/2
The following arguments were given to :v3_core.pattern/2:
# 1
{:call, 10, {:atom, 10, :|}, [{:atom, 0, :first}, {:tuple, 0, [{:atom, 0, :next}, {:var, 10, :_bar@1}]}]}
# 2
{:core, 1, 0, {:foo, 1}, false, true, [extra_chunks: [{"Docs", <<131, 80, 0, 0, 0, 222, 120, 156, 109, 204, 77, 14, 194, 32, 16, 5, 224, 161, 72, 75, 76, 188, 67, 151, 186, 50, 158, 168, 25, 25, 106, 169, 133, 73, 16, 181, 139, 30, 94, ...>>}, {"ExCk", <<131, 104, 2, 100, 0, 17, 101, 108, 105, 120, 105, 114, 95, 99, 104, 101, 99, 107, 101, 114, 95, 118, 49, 116, 0, 0, 0, 2, 100, 0, 7, 101, 120, 112, 111, 114, 116, 115, ...>>}], debug_info: {:elixir_erl, {:elixir_v1, %{attributes: [], compile_opts: [], definitions: [{{:|, 2}, :defmacro, [line: 2], [{[line: 2], [{:a, [version: 0, line: 2], nil}, {:b, [version: 1, line: 2], nil}], [], {{:a, [version: 0, line: 4], nil}, {:b, [version: 1, line: 4], nil}}}]}, {{:foo, 1}, :def, [line: 10], [{[line: 10], [{:|, [line: 10], [:first, {:next, {:bar, [version: 0, line: 10], nil}}]}], [], {{:., [line: 10], [IO, :puts]}, [line: 10], [{:bar, [version: 0, line: 10], nil}]}}]}, {{:do_foo, 0}, :def, [line: 8], [{[line: 8], [], [], {:foo, [line: 8], [first: {:next, 1}]}}]}], deprecated: [], file: "/Users/levvibraun/Repos/Alchemistry/ddd_invariants/lib/bug.ex", is_behaviour: false, line: 1, module: Bug, relative_file: "lib/bug.ex", unreachable: []}, []}}], [], [file: 'lib/bug.ex']}
(compiler 7.5.1) v3_core.erl:1841: :v3_core.pattern/2
(compiler 7.5.1) v3_core.erl:2002: :v3_core.pattern_list/2
(compiler 7.5.1) v3_core.erl:272: :v3_core.clause/2
(compiler 7.5.1) v3_core.erl:263: :v3_core.clauses/2
(compiler 7.5.1) v3_core.erl:233: :v3_core.body/4
(compiler 7.5.1) v3_core.erl:221: :v3_core.function/4
(compiler 7.5.1) v3_core.erl:182: :v3_core.form/3
(stdlib 3.11.1) lists.erl:1263: :lists.foldl/3
Thank you for the detailed proposal.Unfortunately adding ~ as an operator will introduce ambiguities since it is already used for sigils. For example, if introduced, foo~r”foo” can be read either as foo ~ r(“foo”) or as foo(~r”foo”).Ultimately, we don’t plan to introduce new operators beyond the custom operators already available in the language.
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/43d98d89-02cb-4d70-a381-639edef02914%40googlegroups.com.
To expand a little on this, there are two other approaches you could take to introduce new syntax in Elixir right now:
1. Define a macro. The arguments you pass to the macro still have
to follow Elixir's parsing and precedence rules, but you are able
to interpret the AST passed to the macro any way you wish. This is
used by a couple of libraries to create very user-friendly DSL's.
(An example: Ecto.)
2. Define your own sigil. Inside the sigil's body you can use any
syntax that you desire. You can parse the sigil (potentially) at
compile-time to provide nice safety checks and other feedback to
the user that way. (An example: EEx templates.)
As an aside, your proposal seems to be somewhat in line with what
the `Norm' library (https://github.com/keathley/norm) is
attempting to do. Maybe an opportunity for cross-pollination of
ideas? :-)
Happy Easter,
~Wiebe-Marten / Qqwy
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4J_2VPpUSWFnzSF%2BcprN0k_wk8eYDUZwaVvj6w%3DhXUo8w%40mail.gmail.com.
To expand a little on this, there are two other approaches you could take to introduce new syntax in Elixir right now:
1. Define a macro. The arguments you pass to the macro still have to follow Elixir's parsing and precedence rules, but you are able to interpret the AST passed to the macro any way you wish. This is used by a couple of libraries to create very user-friendly DSL's. (An example: Ecto.)
2. Define your own sigil. Inside the sigil's body you can use any syntax that you desire. You can parse the sigil (potentially) at compile-time to provide nice safety checks and other feedback to the user that way. (An example: EEx templates.)
As an aside, your proposal seems to be somewhat in line with what the `Norm' library (https://github.com/keathley/norm) is attempting to do. Maybe an opportunity for cross-pollination of ideas? :-)
Happy Easter,
~Wiebe-Marten / Qqwy
On 13-04-2020 12:48, José Valim wrote:
If you introduce this operator, you make the following completely valid syntax today ambiguous:
[:foo | :bar]
It should not be able to redefine the | operator. We will tag it as a special form and it will raise in future Elixir versions.
I can say upfront that if you pick any operator beyond the list of available operators, you will most likely introduce compiler errors or bugs in the software.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/43d98d89-02cb-4d70-a381-639edef02914%40googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.
:foo«bar "m" == :foo « bar("m") == {:foo, bar("m")}
[:foo « :bar] == [foo: :bar] == [{:foo, :bar}]
[:foo « :bar « :buzz] == [{:foo, {:bar, :buzz}}]
If you introduce this operator, you make the following completely valid syntax today ambiguous:[:foo | :bar]It should not be able to redefine the | operator. We will tag it as a special form and it will raise in future Elixir versions.I can say upfront that if you pick any operator beyond the list of available operators, you will most likely introduce compiler errors or bugs in the software.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/C215W5L92I8K.3CLRLHPIZJ885%40nixos.
def speed(Path +++ Kilometer +++ path, Time +++ Sec +++ duration), do: KilometerPerSec +++ path / duration
def speed(Path « Kilometer « path, Time « Sec « duration), do: KilometerPerSec « path / duration
We could probably add +++ and --- with right associativity to the list, to tag along ++ and -- which are right associative.
On Tue, Apr 14, 2020 at 9:06 PM 'Justin Wood' via elixir-lang-core <elixir-l...@googlegroups.com> wrote:
> Is it a ridiculous idea to add one of those symbols as a new operator
> with the right to left associativity?
As an Emacs (and light Vim) user, I would personally suggest against
something like this. At least in Emacs, it is not as simple as just
alt+keys in order to get these characters. And even if it were,
newcomers to the language, and people who rarely use these operators,
will have to lookup the key combination, or figure out how to do such a
thing in their editor.
I would suggest that if any new operators were added to the language,
they should be easily typed in as many keyboard layouts as possible.
Justin
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/f8ebb77a-540d-44d1-bbee-8efbb101d981%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CABu8xFDLGqx2kZV%2BCy7jjxesVHtVqC7KSEQTy%3DFURba06SYEUw%40mail.gmail.com.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/f8ebb77a-540d-44d1-bbee-8efbb101d981%40googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/080c8c18-6d5a-4fa4-934c-26a3d5f06e92%40googlegroups.com.
Please do send a PR.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/080c8c18-6d5a-4fa4-934c-26a3d5f06e92%40googlegroups.com.