Let's say I have a modules Silent and Definer. I want to define a couple of functions for Silent, based on its attribute. Let me explain:
defmodule Silent do
#function_names [:a, :b, :c]
use Definer
end
defmodule Definer do
defmacro __using__(_) do
quote do
Enum.each(#function_names, fn(n) ->
def unquote(n)() do # line 5
IO.puts "a new method is here!"
end
end)
end
end
end
But this approach actually doesn't work because I have undefined function n/0 on line 5. How can I implement desired functionality?
You need to pass unquote: false to quote in Definer.__using__/1 to be able to inject an unquote fragment inside a quote.
defmodule Definer do
defmacro __using__(_) do
quote unquote: false do
Enum.each(#function_names, fn(n) ->
def unquote(n)() do # line 5
IO.puts "a new method is here!"
end
end)
end
end
end
defmodule Silent do
#function_names [:a, :b, :c]
use Definer
end
Silent.a
Silent.b
Silent.c
prints
a new method is here!
a new method is here!
a new method is here!
A similar case is documented in detail in the Kernel.SpecialForms.quote/2 docs which also mentions how to use bind_quoted if you want to both inject some variables into a quote and create unquote fragments.
Related
I'm trying to figure out a way to safely free resources acquired by a class. I tried using finalize, but it's unreliable. Sometimes I close my program before GC has a chance to free resources.
So I decided to use class instance in a block like this:
class Foo
def destroy # free resources
#...
end
#...
def self.create(*args)
instance = self.new(*args)
begin
yield instance
ensure
instance.destroy
end
end
Foo.create do |foo|
# use foo
end
That works fine, but I still can create an instance using new that I'd have to destroy explicitly. I tried to write my own new but it seems like it's just overloaded by default new.
Is there a way to redefine\disable new?
That is initialize method, that should be made private:
class Foo
#foo : String
private def initialize(#foo)
end
def destroy
puts "Destroying #{self}"
end
def self.create(arg)
instance = new(arg)
yield instance
ensure
instance.destroy if instance
end
end
Foo.create("bar") do |foo| # will work
p foo
end
Foo.new("bar") # will raise
Playground
I have been trying to make a program that requires reading from a file and then making the string inside the file part of a string in the program. I have written an example of what I do:
gameinfo = [0,0]
def readsave(savefile):
"Reads a file and adds its statistics to variables"
filename = savefile
with open(filename) as file_object:
gameinfo = file_object.readlines()
print(gameinfo)
readsave('gamesave.txt')
print (gameinfo)
But whenever I run this code, all I seem to get is:
['thisworks\n', '7']
[0, 0]
The [0,0] string is what I am trying to change to ['thisworks\n, 7'], however it only changes inside the function. Is there any way which I can make this change global?
The problem here is scope, the gameinfo variable in the function is a local, not a global. You can declare it global, or pass gameinfo around as a parameter. Generally, I avoid global declarations as they can get confusing. I'd recommend passing gameinfo around:
def readsave(savefile, gameinfo=[0,0]): # Declare it as a default to the function.
"Reads a file and adds its statistics to variables"
with open(savefile) as file_object: # No need to rename this.
gameinfo = file_object.readlines()
return gameinfo # Return it so it escapes the scope of this function.
gameinfo = readsave('gamesave.txt') # Save it.
print(gameinfo) # Print it.
Variables are not shared in functions which means you define gameinfo = [0,0] but you are never actually getting that variable in the function. I you want to save in gameinfo you need to use return or global. global will make it possible to share variables inside the function and outside however this is considered bad practice so don't use it.
To use return simply put it in your function. Always make sure you have only one variable, string, integer returning once per call.
Here is your example rewritten to include the return statement I mentioned above:
gameinfo = [0,0]
def readsave(savefile):
"Reads a file and adds its statistics to variables"
filename = savefile
with open(filename) as file_object:
gameinfo = file_object.readlines()
print(gameinfo)
return gameinfo
gameinfo = readsave('gamesave.txt')
print (gameinfo)
You have also made a few other mistakes:
"Reads a file and adds its statistics to variables" is not a comment. Use """my text here""" (triple quotes) or #my text here to insert comments.
All these things you will learn as you read the Python tutorial. Here is one illustrating the use of return.
I have three identical methods in my module, that do (almost) exactly the same thing. Instead of repeating the function definitions, I am trying to define them once to keep code minimal and same for all of them.
So far I've tried using Code.eval_string:
defmodule MyModule do
Enum.each ~w(method1 method2 method3), fn method ->
#method method
Code.eval_string """
def #{#method}(map) when is_map(map) do
do_map_stuff(:#{#method}, map)
end
def #{#method}(arg) do
do_other_stuff(:#{#method}, arg)
end
"""
end
## Other Methods
end
but this throws ArgumentError:
Compiling 1 file (.ex)
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:4297: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3299: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
nofile:1: (file)
I think quote/unquote might be the way to go, but I'm not exactly sure how to do this using them (I've already read the Meta Guide on the Elixir website).
Something like this?
defmodule MyModule do
def do_map_stuff(method, arg) do
IO.inspect([method, arg])
end
Enum.each [:method1, :method2, :method3], fn method ->
def unquote(method)(map) when is_map(map) do
do_map_stuff(unquote(method), map)
end
def unquote(method)(arg) do
do_map_stuff(unquote(method), arg)
end
end
end
for i in [1,2,3,4,5]:
if i==3:
pass
print ("Pass when value is",i)
print (i)
for i in [1,2,3,4,5]:
if i==3:
print ("Pass when value is",i)
print (i)
I get the same output for both the codes.
Then what is the use of pass statement here.
pass is used when the syntax requires a statement but you don't want to do anything.
Example:
def foo(bar):
pass # does nothing
pass is a "do nothing" statement. In this case, it's indeed pointless. It was created to come in place on logically empty blocks, which Python does not allow (unlike c or java, for example). E.g.:
try:
doSomething()
catch ExpectedException:
# We expect this, do nothing
pass
catch OtherException:
# Oh no, something bad happened
handleOtherException()
I want to dynamically use page objects. Something like this:
text_field(:company_name_field, id: 'company_directory_name')
select_list(:state_select, id: 'company_directory_workflow_state')
def input_text_field (page_object)
sample_text = Faker::Lorem.paragraph
$text_array.push(sample_text)
wait_until{send("#{page_object}_field?")}
send("#{page_object}_field=", sample_text)
end
But, using a select_index object, instead of a input_field:
def input_select_list(page_object)
wait_until{send("#{page_object}_select?")}
x = rand(0..send("#{page_object}_select_element.options.length"))
send("#{page_object}_select_element.option(#{:index}, #{x})).select")
end
But this is giving me an error of "undefined method `state_select_element.option(index, 1).select'"
How can this be done?
When using send, the first argument needs to be a single method. send does not break up the state_select_element.option(index, 1).select into 3 method calls.
Since only the first method call state_select_element needs to evaluated from the string, just use send for that. The rest can be called as normal. Applying this to your method gives:
def input_select_list(page_object)
wait_until{send("#{page_object}?")}
x = rand(0..send("#{page_object}_element").options.length) - 1
send("#{page_object}_element").option(:index, x).select
end
However, the option and select methods will give a depreciation warning. To prevent the error, I would probably re-write the method as:
def input_select_list(page_object)
select = send("#{page_object}_element")
select.when_present
select.select(send("#{page_object}_options").sample)
end