tommy

tommy

Is there a workaround to the inner anonymous function of a closure not being able have a default parameter or clauses with different arities

I’m trying to create a closure that takes in a html tag such as “p” or “h1” and returns an anonymous function that can take a string (to go between the tags) and an optional string of tag attributes such as “style=“color: green;””.

I have tried to do this with an optional parameter and see that this can’t be done and I have also tried clauses with different arities and see this can’t also be done.

Here is my effort with an optional parameter:

defmodule Wrapper do
 
  def html(tag) do
    fn
      (str, attr \\ "" )  ->  "<#{tag} #{attr}>" <> str <> "<#{tag}>"
    end
  end
end

And here is my effort with two arities in the anonymous function

defmodule Wrapper do
 
  def html(tag) do
    fn
      (str, attr)  ->  "<#{tag} #{attr}>" <> str <> "<#{tag}>"
      (str) -> "<#{tag}>" <> str <> "<#{tag}>"
    end
  end
end

And used like:

h1 = Wrapper.html("h1" )
h1.("I'm a green h1 title", "style=\"color: green;\"") |> IO.puts
h1.("I'm a title")  |> IO.puts

Is there another way of doing what I need here? I don’t actually need this other than learning.


Edited: spelling

Marked As Solved

trisolaran

trisolaran

A simple idea could be to “simulate” different arities with tuples:

defmodule Wrapper do

 def html(tag) do
   fn
     {str, attr}  ->  "<#{tag} #{attr}>" <> str <> "<#{tag}>"
     str -> "<#{tag}>" <> str <> "<#{tag}>"
   end
 end
end

Wrapper.html("p").("this is a paragraph")
Wrapper.html("p").({"this is a paragraph with style", "style=\"color: green;\""})

Also Liked

eksperimental

eksperimental

When you define a regular function with default values, what are are actually doing is defining more functions with lesser arities.
so:

def x(a, b, c \\ :c),
  do: {a, b, c}

what is does in reality is

def x(a, b),
  do: x(a, b, :c)

def x(a, b, c),
  do: {a, b, c}

So for Elixir x/2 and x/3 are two different functions that just happen to have the same name (well, the compiler does some annotation so you can know they have been defined by same clause).

So if anonymous functions allowed default arguments, what is the variable that they will be assigned to the other functions of lesser arirty?

For the second part, functions can only have a fixed numbers of arities,
so you cannot have have anonymous functions either with more than one arity (you might have seen this in the error thrown by the compiler).
You can make up for this with tuples and keyword lists.

My take builds on the idea of @trisolaran

defmodule Wrapper do
  def html(tag) when is_binary(tag) do
    fn
      (str) when is_binary(str) ->
        "<#{tag}>#{str}</#{tag}>"

      ({str, attr}) when is_list(attr) ->
        "<#{tag} #{join_attr(attr)}>" <> str <> "</#{tag}>"
    end
  end

  def join_attr(attr, level \\ 1) do
    list = 
      for {key, value} <- attr do
        cond do
          is_list(value) ->
            "#{key}=\"#{Wrapper.join_attr(value, 2)}\""

          level == 1 ->
            "#{key}=\"#{value}\""

          level == 2 ->
            "#{key}: #{value}"  
        end
      end

    case level do
      1 -> Enum.join(list, " ")
      2 -> Enum.join(list, "; ")
    end
  end
end

In IEx:

iex(44)> h1 = Wrapper.html("h1")
#Function<0.76550479/1 in Wrapper.html/1>

iex(45)> h1.("Wow")
"<h1>Wow</h1>"

iex(46)> h1.({"Title", style: "color: red; font-size: 10em", title: "The title"})
"<h1 style=\"color: red; font-size: 10em\" title=\"The title\">Title</h1>"

iex(47)> h1.({"Title", style: [color: "red", "font-size": "10em"], title: "The title"})
"<h1 style=\"color: red; font-size: 10em\" title=\"The title\">Title</h1>"
derek-zhou

derek-zhou

I do not know the answer to your question but I think in Elixir people tend to do more macros than high-order functions. Macros should run faster too.

For example, you can do:

  def element(s, tag, text, attrs) when is_binary(text) do
    start_tag = "<#{tag}#{attr_string(attrs)}>"
    end_tag = "</#{tag}>\n"
    [end_tag | text([start_tag | s], text)]
  end

[:div, :h1]
  |> Enum.each(fn k ->
    @doc ~s"""
    build non-void element #{to_string(k)}
    """
    defmacro unquote(k)(s, inner, attrs \\ []) do
      str = to_string(unquote(k))

      quote do
        element(unquote(s), unquote(str), unquote(inner), unquote(attrs))
      end
    end
  end)

This code is a snippet from html_writer that more or less do what you are trying to do.

tommy

tommy

This is fantastic! This will also help me with a logger I’m creating too.
It’s such a clean solution. I’m going to play with that in the morning. Thanks

tommy

tommy

This is great! I’m going to copy it into my editor now to play with it. I can see that what you have done here can be used in many different scenarios.
Thank you for helping me.

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New

We're in Beta

About us Mission Statement