Thanks for your input.
Yes, I think that makes sense as well. So then I can simplify the code some more.
Thanks for your input.
Yes, I think that makes sense as well. So then I can simplify the code some more.
It may be an idea to read up on sum types (aka union types) vs product types (which youâre likely already familiar with), example:
Sum Types Are Coming: What You Should Know
That should reinforce the idea that the various function clauses assemble to one single function operating on a sum type (rather than falling back on the idea of âoverloadedâ functions).
I donât want to be too nitpicky but check/1
here is really just one function with multiple clauses. It is the elixir syntax which makes it look like 2 functions.
Youâre rightâThanks for pointing that out
Thanks for the pointers! Iâll check out that blog post. Iâm working my way through âProgramming Elixirâ nowâI imagine it will probably come up there as well in some later chapter.
Thank you everyone for taking the time to give me feedback and ideas on how to improve this simple program. I have pushed some improvements to the git repo after your suggestions.
Thank you for the helpful pointers, Iâm also working my way through âProgramming Elixirâ. Iâve modified the code slightly, as to make it more verbose:
defmodule FizzBuzz do
@type result :: integer | :fizz | :buzz | :fizz_buzz
@spec check(integer) :: result
def check(number) when is_integer(number) do
case {rem(number, 3), rem(number, 5), number} do
{0, 0, 0} ->
0
{0, 0, _} ->
:fizz_buzz
IO.puts("FizzBuzz: " <> to_string(number))
{0, _, _} ->
:fizz
IO.puts("Fizz: " <> to_string(number))
{_, 0, _} ->
:buzz
IO.puts("Buzz: " <> to_string(number))
{_, _, _} ->
:number
IO.puts("Number: " <> to_string(number))
end
end
# can actually be arbitrary nested
@spec check(list | Range.t()) :: [result]
def check(numbers) do
IO.inspect(numbers)
Enum.map(numbers, fn number -> check(number) end)
end
end
`FizzBuzz.check([1..15])`
Hereâs the output:
[Running] elixir â/home/master/Documents/dev/spectre/elixir/scratch.exsâ
[1âŠ15]
1âŠ15
Number: 1
Number: 2
Fizz: 3
Number: 4
Buzz: 5
Fizz: 6
Number: 7
Number: 8
Fizz: 9
Buzz: 10
Number: 11
Fizz: 12
Number: 13
Number: 14
FizzBuzz: 15
[Done] exited with code=0 in 0.198 seconds
However, what I donât understand is this:
[1âŠ15]
1âŠ15
That seems like IO.inspect(numbers) is called twice, which means that the function âcheck(numbers)â is called twice, before the âcheck(number)â function is called through âEnum.map()â. What am I missing here?
Iâm not sure (havenât tested this hypothesis), but I think that might be happening because youâre passing a list of ranges to FizzBuzz.check()
, instead of just a single range.
Try changing FizzBuzz.check([1..15])
to FizzBuzz.check(1..15)
Also, thanks for sharing your solution
Thank you! Thatâs exactly right, I did pass a list instead of a simple range:)
FizzBuzz.check(1..15):
[Running] elixir "/home/master/Documents/dev/spectre/elixir/scratch.exs"
1..15
Number: 1
Number: 2
OK, kewl - that makes sense. However:
FizzBuzz.check([[1..15]])
[Running] elixir "/home/master/Documents/dev/spectre/elixir/scratch.exs"
[[1..15]]
[1..15]
1..15
Number: 1
Why/how does it âautomagicallyâ flatten the list until it gets a range?
Recursion.
def check(numbers) do
IO.inspect(numbers)
Enum.map(numbers, fn number -> check(number) end)
end
The âotherâ check/1
clause only works with integers. This one is run again and again - which happens to work for single element lists. Again, you have two clauses for the same check/1
function.
The following typing would be more explicit:
@type result :: list | integer | :fizz | :buzz | :fizz_buzz
@spec check(list | Range.t() | integer) :: result
Thank you, very much appreciated. âEnum.map()â behaves differently than I would have expected, but I got it now:
iex(9)> a = [[[[["foo","bar","baz"]]]]]
[[[[["foo", "bar", "baz"]]]]]
iex(10)> Enum.map(a, fn x -> IO.puts(x) end)
foobarbaz
https://hexdocs.pm/elixir/Enumerable.html
Internally, [ `Enum.map/2` ](https://hexdocs.pm/elixir/Enum.html#map/2) is implemented as follows:
```
def map(enumerable, fun) do
reducer = fn x, acc -> {:cont, [fun.(x) | acc]} end
Enumerable.reduce(enumerable, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
end
```
In dynamic languages types beyond the intrinsic types are rarely discussed as there isnât any static type checking. In the case of Programming Elixir 1.6
type specifications and Dialyzer/Dialixir are only discussed in Appendix 2: Type Specifications and Type Checking. And even there it only talks about Combining types without exploring that the new type is a sum type (or union type).
Only statically typed functional languages like Haskell, Elm or ReasonML (variant types) tend to broach that subject. Even so, it is often helpful to be type-aware even with dynamically typed languages.
For some background on the limitations of Dialyzerâs Success Typing see: learn you some Erlang: Type Specifications and Erlang (Donât Drink Too Much Kool-Aid).
Thanks for the heads-up and useful info.
These are awesome, I hate squishing. ^.^
But yes, learn Dialyzer, the Elixir wrapper library for it of Dialyxir makes it so simple to use.
Also make sure youâre using the latest RC version of Dialyxir, it prints out the errors in Elixir format (instead of Erlang format)