-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove top-level methods and macros #4442
Comments
@asterite If all top-level declarations were file private by default, surely |
I suppose we need something more high-level than require. Yes, it's about import, just like (pseudocode) def import( name, symbols_to_import... )
require name
if symbols_to_import.empty?
include name
return
end
symbols_to_import.each do |symbol|
if symbol.is_a? Type
private alias symbol = name.symbol
elsif symbol.is_a? Module
include symbol
end
end
end |
I like the goal. Don't polute the global namespace, let me include what I want to be global (or include it in another module). The drawback is having to include Kemal or Spec (for example) in each and every file, which may be annoying over time. Same for methods becoming file private, which may be confusing (at first). I think I'd like having to include Kemal or Spec once, but I guess that would somewhat defeat the purpose? |
Yes, a more general solution can't work with |
At first glance, this is starting to remind of Java (everything must be defined inside a class) and Python (having to require every dependency at the top of every file), why I left them, and why I like Ruby so much. I think it's good and idiomatic for libraries to provide both a "global-polluting", easy-to-use system, as well as a way to namespace the library if the user is worried about collisions. Sinatra is a good example of this: require 'sinatra'
get 'thing' do
# This just works out of the box and is simple and intuitive
end
---
require 'sinatra/base'
# Now the only thing in the top-level is `Sinatra::Base`
class SomeApp < Sinatra::Base
# And things like `get`, and `post` are scoped inside this class
get 'thing' do
end
end With that, I don't think Crystal needs anything in the language to do this, it should just be considered good, idiomatic practice.
I'm concerned that this would be more surprising to users than it is helpful. For example, I defined a macro # helpers/render2.cr
macro render2(filename, path="src/templates", layout=nil)
{% if layout %}
render "#{{{path}}}/#{{{filename}}}.ecr", "templates/layouts/#{{{layout}}}.ecr"
{% else %}
render "#{{{path}}}/#{{{filename}}}.ecr"
{% end %}
end
# controllers/thing_controller.cr
require "helpers/*"
render2(...) If top-level macros are now file-private, how would I implement this to make it usable in other files? Would I have to create a module and include it everywhere I want to use it? # helpers/render2.cr
module Helpers
def render2(filename, path="src/templates", layout=nil)
{% if layout %}
render "#{{{path}}}/#{{{filename}}}.ecr", "templates/layouts/#{{{layout}}}.ecr"
{% else %}
render "#{{{path}}}/#{{{filename}}}.ecr"
{% end %}
end
end
# controllers/things_controller.cr
require "helpers/*"
include Helpers
render2(...) I think the latter is good practice, I just don't think it should be strictly enforced. |
Mmm... let's leave things are they are now and wait and see if things break :-) |
@faultyserver You wouldn't need the |
Maybe a "good practices" section in the crystal book would be interesting? We could list this idea of not polluting the global namespace by default, or as requested by the user (e.g. the Sinatra example above), along many other ideas :) |
@asterite as for #4439 (comment) and this issue, the cornerstone for that to work is that the I think changing |
@straight-shoota you're right. The example I used has a pretty well-defined best practice as you described. I guess I'm just not onboard with forcing things into modules in general. I like it as an idiom, but not so much as a compiler restriction. @ysbaddaden I think a "good practices" section would be a great addition. Many languages (and companies) have style guides, but few have official "convention guides". |
@bcardiff I like the idea of a separate keyword (e.g., That way, if a user knows that they want But, it the user wants more control of where |
@bcardiff How about adding a |
@RX14 |
@faultyserver |
Yeah, I'm not entirely sure what it is that connects the two in my mind. I think the more pressing thing for me is that Something like this makes sense to me: private class Thing
end In my Ruby-trained mind, that says "make a class class Thing
def foo
end
private :foo
# equivalent to
private def foo
end
end Interestingly, though, private constants in Ruby don't work this way: class Person
class Secret
end
private_constant :Secret
private :Secret # does nothing
end In any case, I'm probably just wrong for thinking about this from a Ruby perspective :) |
FWIW, there's an existing, open issue (#3319) that suggests different semantics for |
@faultyserver Yeah, |
FWIW I also like the idea of |
(extracted from #4439 (comment))
Right now methods and macros defined at the top-level pollute the global namespaces. The recommendation is not to use them, but they are available. And, for example, Kemal defines a whole bunch of global methods, macros and constants:
get
,post
,ws
,error
,content_for
,CONTENT_FOR_BLOCKS
,yield_content
,render
,halt
,add_context_storage_type
and probably more. I also saw some other libraries that just don't care about polluting the global namespace, maybe because they don't know that defining a top-level method has that consequence. I can imagine eventually they'd have to be refactored to avoid clashing problems.To solve this, one idea that came to my mind is this:
include
For example:
In this way there's absolutely no way to pollute the global namespace of methods and macros, because such namespace ceases to exist.
Using Kemal's DSL becomes just one line longer, and the
get
method is only available in that file, not in files required by"main.cr"
.And
Kemal
couldextend self
so we could writeKemal.get "/" {}
instead if we don't want to pollute that file's methods.If we do this, we loose the ability to use
puts
,raise
,record
and others, because these were top-level methods/macros. To solve this, we can define aKernel
module, put these definitions there, and let all files automatically include thisKernel
module. This seems like bringing the global namespace back again, but it's not: to pollute the global namespace one would have to define methods inKernel
, but that practice would be discouraged. Of course we can't force users not to reopenKernel
, but at least writing this simple and innocent looking code:won't pollute the global namespace anymore, you have to be conscious about reopening
Kernel
and adding more methods to it.This also brings another breakage:
spec
definesdescribe
andit
as global methods. We'd need to move them to theSpec
module and then you'd have to do:But in my opinion, even though it's a line longer, it's better because the definitions of
describe
andit
won't exist in other files.(there's still the "issue" that
should
andshould_not
are added to every type, we can think of a solution to that in another issue, or simply start usingexpect
orassert
)We can do the same for some methods that
colorize
defines, and for the not very commonly usedlazy
,future
anddelay
methods (we could put them in aConcurrent
module).Another nice thing that we can do with this change is to apply the same things for types: if you include a module in the top-level, you can refer to types inside that module, but those types won't be available in other files:
This is specially nice to avoid having to write long type names, although a solution for that already exists, which is a bit more verbose but maybe cleaner:
I can imagine this step as a first change towards a cleaner require system. Maybe
include Kemal
isn't very clear because how do we know what methods that module defines? Maybeinclude Kemal, :get
could be better and more explicit. Same goes for types provided by a module. But that's a much bigger change.The text was updated successfully, but these errors were encountered: