Printing the current line in elixir source code - macros

In elixir we have the Pseudo-variables __MODULE__, __DIR__ et al. In erlang there is also the functionality to get the current line, using an erlang macro.
How do I get the current file line in elixir.
Something like
IO.puts __LINE__

It's all inside __ENV__:
IO.puts __ENV__.file
IO.puts __ENV__.line
Also see https://hexdocs.pm/elixir/Macro.Env.html

Related

Julia - How macros are related (based on exemple from OpenCL.jl

I've put a discord but no result. May be here somebody could help me
When you look in the OpenCL implementation code you have two macros that seems to refer one another in some (misterious) way, I cannot see how this happens. One macro is #check define in src/ macros.jl the other one #ocl_func define in api.jl.
#check is responsible for running the clfunc => $(esc(clfunc)), but as far as I understood it is the #ocl_func macro (in the api.jl file) that will ccall the clfunc.
My question how does the 'called' clfunc, for example this one:
#check api.clGetPlatformIDs(0, C_NULL, nplatforms) (in /platforms.jl)
is passed to the #ocl_func macro?
Is it implicit( because the ocl macro is define in api so every function called with api.will activate the macro, in the example it will be then:
#ocl_func(clGetPlatformIDs(0, C_NULL, nplatforms)) )
I cannot find the code which glue the two macros. Clarifying this will help me to understand how macros work.
An easy way to think about macros is that they are source-code generation tools.
as far as I understood it is the #ocl_func macro (in the api.jl file) that will ccall the clfunc.
If you look at the the implementation of #ocl_func, you'll see that the ccall is part of a quote block, that's being returned. So it's not that #ocl_func will ccall the clfunc, rather #ocl_func generates the source code expression which ccalls the clfunc. The quoted block that is returned by a macro is seen as source code by Julia and is compiled like any other code, a function definiton in this case.
So when Julia sees the line:
#ocl_func(clGetPlatformIDs, CL_int,
(CL_uint, Ptr{CL_platform_id}, Ptr{CL_uint}))
this gets turned into a function definition that uses ccall inside it.
Similarly, #check also generates code that does an error check and throws if it's not a success. #check doesn't need to know about #ocl_func, instead both just expand into the quoted code that they return, so any interaction is between those generated code blocks.
My question how does the 'called' clfunc, for example this one:
#check api.clGetPlatformIDs(0, C_NULL, nplatforms) (in /platforms.jl)
is passed to the #ocl_func macro?
The function definition for api.clGetPlatformIDs is generated by the #ocl_func macro, but once its generated it can be called like any normal function, and that's what's happening here. So there's no dependency between #check and #ocl_func necessary for this to work.
While Sundar's answer explains what is going on it is always a good idea to try #macroexpand too:
julia> #macroexpand OpenCL.cl.#check api.clGetPlatformIDs(0, C_NULL, nplatforms)
quote
#= C:\JuliaPkg\Julia-1.8.1\packages\OpenCL\yqbHv\src\macros.jl:3 =#
local var"#3#err"::OpenCL.cl.CL_int
#= C:\JuliaPkg\Julia-1.8.1\packages\OpenCL\yqbHv\src\macros.jl:4 =#
var"#3#err" = api.clGetPlatformIDs(0, C_NULL, nplatforms)
#= C:\JuliaPkg\Julia-1.8.1\packages\OpenCL\yqbHv\src\macros.jl:5 =#
if var"#3#err" != OpenCL.cl.CL_SUCCESS
#= C:\JuliaPkg\Julia-1.8.1\packages\OpenCL\yqbHv\src\macros.jl:6 =#
OpenCL.cl.throw(OpenCL.cl.CLError(var"#3#err"))
end
#= C:\JuliaPkg\Julia-1.8.1\packages\OpenCL\yqbHv\src\macros.jl:8 =#
var"#3#err"
end

Recursive macro Elixir

I am playing around with Elixir macros - specifically macros that call themselves which is something that I do often in Scheme. I have created a little test macro below however it just hangs iex - nothing is printed to console. Does anyone have any insight into why and what could be done to correct it?
defmodule RecMac do
defmacro test_rec(x) do
quote do
IO.puts("Started?")
if(unquote(x) < 1) do
IO.puts("Done?")
"done"
else
IO.puts("Where are we")
IO.puts(unquote(x))
RecMac.test_rec(unquote(x) - 1)
end
end
end
end
EDIT!!
OK, so it turns out you can define recursive macros where there is a structural difference to match on (eg lists). The following is working for me. And to confirm #Aleksei Matiushkin below, the above will not work and does indeed not work in scheme!
defmacro test_rec([h | t]) do
quote do
IO.inspect([unquote(h) | unquote(t)])
RecMac.test_rec(unquote(t))
end
end
defmacro test_rec([]) do
quote do
IO.puts "Done"
end
end
end
I am very happy to have dug into this as I learned something about two languages!
TL;DR: this is impossible.
Macros in elixir are not what you expect them to be. When a compiler sees a macro, it calls it during a compilation time and injects the AST it returned in the place of where it was called. That said, recursive macros would always lead to the infinite loop at the compilation stage.
Put IO.puts("something") before quote do instruction and you’ll see it to be printed infinitely, once per subsequent call to expand the macro.
You can use #compile {:inline, test_rec: 1} to achieve the behaviour you are after. For better understanding macros, you probably should read Macros section in the Elixir Guide in general and this excerpt in particular:
Macros are harder to write than ordinary Elixir functions and it’s considered to be bad style to use them when they’re not necessary. So write macros responsibly.
Actually you can do kind of recursion, but point is to think what you are doing to avoid calling macro inside quote do [....] end. Your example with IO.puts would be something like this
defmodule RecMac do
defmacro test_rec(list) do
{:__block__, [], quote_value(list)}
end
defp quote_value([]), do: []
defp quote_value([h | t]) do
if h < 1 do
[]
else
ast = quote do
IO.puts("#{unquote(h)}")
end
[ast | quote_value(t)]
end
end
end
you will notice two things, I do use recursion in macro but outside quote using private function quote_value/1 and that function has logic that "stops" after it finds value lower than 1. All "quotes" are put into list and trick is to put this list into tuple {:__block__, [], put_quote_list_here}
Now note that this macro wont compile if list parameter of test_rec is not know upfront (during compile time), so you need to call macro test_rec(["a", "b", 0, 100, 200]) so compiler knows size and elements of that list.
BTW, I used body optimized recursion, but you can easily add accumulator and convert this into tail optimized recursion.

Language constructs vs Macros in elixir

I'm learning control structures from https://elixirschool.com/en/lessons/basics/control-structures/
and I noticed that it mentions
Chances are you’ve encountered if/2 before, and if you’ve used Ruby you’re familiar with unless/2. In Elixir they work much the same way but they are defined as macros, not language constructs. You can find their implementation in the Kernel module.
so what's the difference between a language construct and a macro in Elixir and when is it necessary to write a macro?
A macro is a way to program a programming language. Simply put, a macro is a way to generate program code, instead of writing it yourself all the time.
A language construct on the other hand (sometimes called "special form"), is something that is at the core of elixir itself. An oversimplification could be that the implementation of if is not done in Elixir, but in the language in which Elixir is implemented.
Suppose you want to use the mentioned unless.
Edit: unless is available in Elixir. But let's assume for the remainder that it is not.
In Elixir, there is no unless available in the language. José Valim did not implement it. But you can always write something that has the same semantics: a negated if.
We would like to have this, but we don't:
unless sun_shines() do
open_umbrella()
end
But we only have an if and a not, so we can write:
if not sun_shines() do
open_umbrella()
end
Secondly, a macro is a special kind of function, but its parameters are code, and the result of executing a macro is code as well. Assuming we have the unless macro, it takes in a condition (i.e., sun_shines()), and a body (i.e., open_umbrella()), and returns if not sun_shines(), do: open_umbrella(). So a macro is a function that works at the level of your "dead code" and generates "dead code".
You might think that this is just too stupid to write a macro for. That's true. But these types of problems happen more often than you think, and then a macro is a great solution to that problem. It's just a way to program your programming language.
An example implementation of the unless macro has been provided by Aleksei Matiushkin:
defmodule MyMacros do
defmacro unless(ast, do: block) do
quote do
if not unquote(ast) do
unquote(block)
end
end
end
end
Here you can clearly see that you give it an AST (Abstract Syntax Tree), and it will transform it to another AST (quote), and inject that in the place where you called the macro. Note that this all happens at compile time. Your program is not being executed at this point!
For example, suppose you have the above module available, and this is your program:
defmodule MyProgram do
def my_function(x) do
unless sun_shining() do
open_umbrella()
end
end
end
After compilation, and before execution, your program will look like this:
defmodule MyProgram do
def my_function(x) do
if not sun_shining() do
open_umbrella()
end
end
end
This phase is what we call macro expansion phase.
As an extra, here you can find two actual macros used in Elixir and ExUnit respectively.
https://github.com/elixir-lang/elixir/blob/d48b16cf549eca0629449a47cc5574a7170706c3/lib/ex_unit/lib/ex_unit/assertions.ex#L104
https://github.com/elixir-lang/elixir/blob/13ced80fcda1bea69037aacd4b052a0c44b4be61/lib/elixir/lib/stream/reducers.ex#L58
Note: I keep adding more and more information to this answer. The answer actually deserves a whole book and Metaprogramming Elixir by Chris McCord is the best one.

Reading unknown symbols as strings in at-exp languages

I have created a module which provides various functions, including #%module-begin. I want to use it with at-exp syntax, which I can do using the following #lang line:
#lang at-exp s-exp "my-library.rkt"
However, this does not read unknown symbols as strings, as would happen for example when using the scribble/text language. How can I provide this functionality from my library, to save me writing quote marks around all my strings?
I think it may have something to do with the #%top function. Perhaps I can require it somehow from scribble/text and then provide it from my library?
What scribble/text does, is it starts reading the file in "text" mode, whereas at-exp starts reading the file in "racket" mode. Messing with #%top is not what you want here. To do the same thing as scribble/text, you would need a version of at-exp that starts in text mode. That doesn't exist (yet) though.
The function read-syntax-inside from scribble/reader does this. However, you will have to define your own #lang language that uses it. For that, you might find this documentation helpful, but there's no quick answer.
Update:
I looked at the implementation of scribble/text, and the answer seems a lot quicker than I thought it would be. Something like this should work:
my-library/lang/reader.rkt:
#lang s-exp syntax/module-reader
my-library/main
#:read scribble:read-inside
#:read-syntax scribble:read-syntax-inside
#:whole-body-readers? #t
#:info (scribble-base-reader-info)
(require (prefix-in scribble: scribble/reader)
(only-in scribble/base/reader scribble-base-reader-info))
Testing it:
#lang my-library
This is text
#(list 'but 'this 'is 'not)
I tested this with my-library/main.rkt re-providing everything from racket/base.

Macro expansion in elixir: how to define 2 macros with one using the other?

I am experimenting with Macros in elixir. Therefore, the code I'm about to show should certainly be done with simple functions but.. I'm experimenting !
I want to define 2 macros (A and B) and make A use B to experiment with macro expansion.
When I use A, I get a compile error saying that the function B is undefined.
Here is the code:
defmodule MyMacros do
defmacro print_expr(expr) do
quote do
IO.puts(unquote(expr))
end
end
defmacro print_hashes_around(expr) do
quote do
IO.puts "###"
print_expr(unquote(expr))
IO.puts "###"
end
end
end
defmodule MyModule do
require MyMacros
def my_print(expr) do
MyMacros.print_hashes_around(expr)
end
end
MyModule.my_print("hello world")
And here is the compile error:
macro_test.exs:17: warning: redefining module MyModule
** (CompileError) macro_test.exs:21: function print_expr/1 undefined
(stdlib) lists.erl:1336: :lists.foreach/2
macro_test.exs:17: (file)
(elixir) lib/code.ex:307: Code.require_file/2
The way I (mis)understand things:
By requiring MyMacros, the module MyModule should know the existence of both macros. Therefore I should be able to use any macros.
When print_hashes_around is expanded in MyModule, the compiler should find that print_expr is also a macro. Therefore, another expansion should happen.
What seems to happen is that second expansion does not happen. Therefore the compiler looks for a function definition that does not exist.
Am I right ?
As suggested in slack, prefixing print_expr with MyMacros. fixes it. I still don't understand why. MyModule requires MyMacros so both Macros should be known and expandable... When I look at the definition of unless, it uses if, not Kernel.if.
By requiring MyMacros, the module MyModule should know the existence of both macros. Therefore I should be able to use any macros.
The misunderstanding is here. :) require only makes the module available to the compiler, it does not import the module functions. If you used import MyModule then it would work.
However, it would be best to fix the issue by prefixing the module name, because then you allow developers using your code to use your macros explicitly (with require) or by importing them.
Another option is to avoid multiple macro invocations like this:
defmodule MyMacros do
defmacro print_expr(expr) do
quoted_print_expr(expr)
end
defmacro print_hashes_around(expr) do
quote do
IO.puts "###"
unquote(quoted_print_expr(expr))
IO.puts "###"
end
end
defp quoted_print_expr(expr) do
quote do
IO.puts(unquote(expr))
end
end
end