How do I access an object's eigenclass in Crystal? - crystal-lang

In Ruby, it's possible to access the eigenclass (or "singleton class") of an object by reopening it. This is particularly useful for defining "private class methods":
class Foo
class << self
private
def declarative_method_name
end
end
declarative_method_name
end
# Foo.declarative_method_name => ERROR!
However, in Crystal this is not syntax:
Syntax error in ./test.cr:2: expecting token 'CONST', not '<<'
class << self
^
Is there another (or indeed any) way to achieve this in Crystal currently?

There's no eigenclass, or more commonly called singleton class in Ruby these days (given there's Object#singleton_class), in Crystal.
However defining class methods and calling them on the class level is supported:
class Foo
private def self.declarative_method_name
puts "hey"
end
declarative_method_name
end
https://carc.in/#/r/1316
The def self. construct here is specialized by the compiler and there's no more general concept beneath it, yet.

How would you make a super classes' new method private while still allowing it's subclasses' to be public?
class Foo
private self.new; end
end
class Bar < Foo
end
Bar.new #=> error: private method 'new' called for Foo:Class

It's also worth noting here that unlike in Ruby, class variables don't transcend inheritance. In Ruby the following code has a strange side effect...
class Foo
##var = 'foo'
def var
##var
end
end
class Bar < Foo
##var = 'bar'
end
puts Foo.new.var
It'll return 'bar' despite the fact that we modified the class variable on Bar. In crystal it returns 'foo' meaning that another reason we'd access the eiganclass, to store and read class level state safely, isn't necessary in crystal, we can just use class variables.

Related

Why doesn't crystal's type inference work on classes as expected?

Why can I define a method like that in Crystal:
def foo(bar): String
bar.to_json
end
foo({"x" => 1, "y" => 2})
but that kind of type inference doesn't work with classes:
class Foo
def initialize(bar)
#bar = bar
end
def foo: String
#bar.to_json
end
end
Foo.new({"x" => 1, "y" => 2}).foo
and it ends up with
Error: can't infer the type of instance variable '#bar' of Foo
What am I missing about Crystal's type inference and what is the workaround for this?
The equivalent class based approach is making the class a generic:
require "json"
class Foo(T)
def initialize(#bar : T)
end
def foo
#bar.to_json
end
end
puts Foo.new({"x" => 1, "y" => 2}).foo
Instance variables need their type set in one way or another because lexicographical type flow analysis is much harder and thus slower to do for them. Also classes build the base of your program so typing them as narrow as possible not only makes the compiler's job easier, it also makes them easier to use. Too open type restrictions on instance variables can lead to quite long and confusing error messages.
You can read more at the original proposal introducing the change to require type annotations on instance variables: https://github.com/crystal-lang/crystal/issues/2390

Access base class attribute in derived class - in "class scope"

class Outer(object):
class InnerBase(object): _var = {'foo', 'bar'}
class Derived(InnerBase):
_var = _var | {'baz'} # NameError: name '_var' is not defined
_var = InnerBase._var | {'baz'} # name 'InnerBase' is not defined
_var = Outer.InnerBase._var | {'baz'} # free variable 'Outer'
# referenced before assignment in enclosing scope
Moving _var in Outer does not help - moving it in module scope would work but defeats the purpose of having classes. So how to go about that ?
EDIT: coming from Java so the scoping rules of classes are a head scratcher for me - a briefing would be appreciated. This works btw:
class Derived(InnerBase): pass
Derived._var = InnerBase._var | {'baz'}
but it's not the pinnacle of elegance.
Related: Nested classes' scope? - but here we specifically want to access our parent class (rather than the Outer type)
EDIT2: What I am actually after is a _var = __class__._var-like syntax (or hack), or an explanation as to why it's not there
Python never searches for a name in enclosing class statements. Mark Lutz uses the acronym LEGB to summarize scope in his introduction to Python (Learning Python): Python searches the local scope, then the local scope of any enclosing def statements, then the global scope, and finally the built-in scope. Class statements are excluded from this scope list; Python does not search enclosing class statements for a name.
One solution is to un-nest your classes. In Python, using un-nested classes is often preferred for its simplicity. But, of course, there are good reasons to nest classes as well. Why have you nested InnerBase? I wonder if you might have nested that class because of your experience in Java. Would the following work for you just as well?
class InnerBase(object):
_var = {'foo', 'bar'}
class Derived(InnerBase):
_var = InnerBase._var | {'baz'}
>>> Derived._var
set(['baz', 'foo', 'bar'])
The moment you nest these two class statements under another class statement they will be excluded from name searches, since they have become part of the larger class statement and are thus excluded from the searching of the various scopes.
You can bypass the class statement and use an explicit call to type.
class Outer(object):
class InnerBase(object): _var = {'foo', 'bar'}
Derived = type('Derived',
(InnerBase,),
{'_var': InnerBase._var | {'baz'}}
)
This works because Derived._var is not being set via an assignment statement in the class statement defining Derived, but is imported from a dictionary you create in the same environment as InnerBase itself.
When you put it in a method it works.
def __init__(self):
_var = Outer.InnerBase._var | {'baz'}
#_var = super(Outer.Derived, self)._var | {'baz'} # or with super
print(_var)
So my impression is that it's all about initialization.

scope not working on Mongoid (undefined method `to_criteria')

I invoke ReleaseSchedule.next_release in other controller
and got the following error
NoMethodError (undefined method `to_criteria' for #<ReleaseSchedule:0x007f9cfafbfe70>):
app/controllers/weekly_query_controller.rb:15:in `next_release'
releae_schedule.rb
class ReleaseSchedule
scope :next_release, ->(){ ReleaseSchedule.where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at).first }
end
That's not really a scope at all, that's just a class method wrapped up to look like a scope. There are two problems:
You're saying ReleaseSchedule.where(...) so you can't chain the "scope" (i.e. ReleaseSchedule.where(...).next_release won't do what it is supposed to do).
Your "scope" ends in first so it won't return a query, it just returns a single instance.
2 is probably where your NoMethodError comes from.
If you really want it to be a scope for some reason then you'd say:
# No `first` or explicit class reference in here.
scope :next_release, -> { where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at) }
and use it as:
# The `first` goes here instead.
r = ReleaseSchedule.next_release.first
But really, you just want a class method:
def self.next_release
where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at).first
end
The scope macro is, after all, just a fancy way to build class methods. The only reason we have scope is to express an intent (i.e. to build queries piece by piece) and what you're doing doesn't match that intent.

Pyamf register_class not mapping strongly typed objects as expected

I'm using Pyamf as my backend for my Flex app and I'm seeing some weird problems with the mapping of the stongly typed classes.
Here is the model that I'm returning
class MilestonActBase(RewardActBase):
def __unicode__(self):
return self.milestone.title
class Meta:
abstract = True
class SouvenirAct(MilestonActBase):
souvenir = models.ForeignKey(Souvenir)
group = models.ForeignKey(Group, blank=True, null=True)
def __unicode__(self):
return self.souvenir.title
Here is my method that returns the objects in my views.py:
try:
pyamf.register_class(Souvenir, 'com.rain.dennys.services.vo.Souvenir')
pyamf.register_class(SouvenirAct, 'com.rain.dennys.services.vo.SouvenirAct')
except ValueError:
print "Classes already registered"
#login_required
def get_souvenir_acts(http_request):
user = http_request.user
souvenirActs = SouvenirAct.objects.filter(user=user)
return souvenirActs
Here is my AS3 class:
package com.rain.dennys.model
{
[RemoteClass (alias="com.rain.dennys.services.vo.SouvenirAct")]
[Bindable]
public class SouvenirAct extends RewardActBase
{
public var souvenir:Souvenir;
public function SouvenirAct()
{
}
}
}
When I call the service, I get back and array of anonymous objects, even though I've done the register_class in python and RemoteClass in Flex. So that doesn't make sense to me. I must be doing something wrong?
In playing around with it, I've tried a few different things. One thing that kinda worked was to iterate on the array in Flex and cast the items as SouvenirAct objects like so:
private function onResult(r:Array):void
{
for each(var o:Object in r)
{
var c:SouvenirAct = o as SouvenirAct;
}
}
When I do that in Flex, I get my SouvenirAct objects are typed as they should be, BUT then the child souvenir objects are all null. So when I force the casting of the SouvenirAct objects in the return result, I get null for the child properties that are strongly typed.
Has anyone see this before? Is there a different way I should be mapping classes?
So I'm now pretty certain the problem was with the netConnection class. I switched it out so I could use RemoteObject, and now everything works exactly as expected.
This is how I was connecting:
netConnection.connect("http://127.0.0.1:8000/gateway/");
netConnection.addEventListener(NetStatusEvent.NET_STATUS, onError);
var responder:Responder = new Responder(onResult, handleFault);
Then I switched to what is described here: http://www.adobe.com/devnet/flex/articles/flex_django.html If anyone else runs into this, and you are using netConnection, my advice is to go with RemoteObject
Okay, so this is kind of a guess but this has stung me a few times. Have you ever instantiated an instance of Souvenir anywhere in your flex application? If not... AS did not bother to compile it and you'll get anonymous objects back.
When you do your onResult looping block of code, it works because you're instantiating an object of SouvenirAct, but never instantiating a Souvenir (child), so it's still null because ActionScript never compiled it...Try this before your service call
//TODO: remove me later
var imjustheretocompile:Souvenir = new Souvenir();
var alsoCompileMetoo:SouvenirAct = new SouvenirAct();
Now since you've created an instance of SouvenirAct, it should actually be compiled into your app. This is usually never a problem since we presume you will be using that class at some point, then you can go back and remove the imjustheretocompile and alsoCompileMetoo variables.

Codeigniter always load in controller

I`m from Russia, so sorry for bad English.
I want to load template in every page in controller.
For example (library parser in autoload),
class Blog extends CI_Controller {
$header = array(
'header' => 'Welcome to my blog!'
);
$this->parser->parse('header', $header);
function index() {
...
echo "My text";
}
header.php:
<h1>{header}</h1>
<script>
alert("Welcome!");
</script>
But I get an PHP error:
syntax error, unexpected T_VARIABLE, expecting T_FUNCTION in line 6. Line 6:
$header = array(
How can I load header in every page? Thanks.
config.php:
$autoload['libraries'] = array('parser');
controller blog.php:
<?php
class Blog extends Controller {
function __construct()
{
parent::Controller();
$header = array('header' => 'Welcome to my blog!');
$this->parser->parse('header', $header);
}
function index()
{
echo "Мой текст";
}
}
?>
view header.php:
{header}
it works for me..
try calling the array with $this->header
Maybe it's a typing error but in the code from header.php you have typed {header} where I guess it should be {$header}
When loading class properties, like your $header above, php expects the property's visibility to be declared before the variable name. PHP5 has three visibility options: 'public', 'private', or 'protected'. I think this is why you are getting "unexpected T_VARIABLE". The difference between them are described at swik.net as:
To protect from accessibility pollution, PHP v5 introduces 3 prefixes for declaring class methods or variables: public, protected, and private.
Public methods and variables are accessible outside of the class. Protected are only accessible from inside of the class and inherited or parent classes. Private are only accessible from within the class itself.
try this: (I chose 'public' visibility, you can determine which is appropriate for your use)
public $header = array('header'=>'Welcome to my blog");
Next, I think you should call your parser in a constructor, rather than outside of a class method.
function _construct(){
parent::_construct();
$this->parser->parse('header',$this->header);
}
The constructor will be called every time the class is instantiated, loading your parser library method along with it.
Update:
Your comment suggests that the parser isn't working like you expect. I assume you have placed
$this->parser->parse('header,$this->header);
in the constructor function like I suggested. If that isn't working, create a function with the same name as your class and put the parser in there, that function will load every time the class is called, similar to the constructor, but let's see if that does the trick. I suggest taking the parser library out of auto load until you've resolved your problem, just to simplify things.
function blog(){
$this->load->library('parser');
$this->parser->parse('header',$this->header);
}