Hi guys!
At first I want thank you very much for Ceylon! Hope this language has a great future.
I am a big fan of Java and wrote many lines of code on it. I like Java and now, but I want reduce verbosity and I'm getting a little tired from Null Pointer trap. Also I don't happy how Java growing - lambda syntax and implementation in 1.8 is ugly for me.
I tried to C#, but don't like it so much, it look like a Java with similar downsides and additional limitations.
I had great hopes of Scala, but most the language's idioms excite discomfort for me.
Maybe Kotlin... But I found Ceylon!
It was great! This language is very beautiful logical and think over.
Now I'am trying to learn Ceylon and have some questions:
1) I don't understand any reason why references to function and method couldn't be compared. I am not mathematic and look from programmer point of view:
shared Integer someFunction(String param) {
...
}
value ref1 = someFunction;
value ref2 = someFunction;
In my opinion ref1 is identical (and equals) with ref2.
Could you please explain why them not equal?
class SomeClass() {
shared void classMethod() {
}
}
value firstInstance = SomeClass();
value secondInstance = SomeClass();
value ref1 = fistInstance.classMethod;
value ref2 = fistInstance.classMethod;
value ref3 = secondInstance.classMethod;
value ref4 = secondInstance.classMethod;
In this example ref1 === ref2 and ref3 === ref4, but ref1(2) != ref3(4).
It’s logical for me.
It’s no matter in most cases but some useful things couldn’t be performed:
e.g. we have unique list of listeners: Set<Integer(Event)> listeners…
listeners.add(refToFunc); // OK
listeners.add(refToFunc); // We’ll had same listener two times, not good for unique set.
And we can't remove listener from the set.
It's sad… Are there some heavy reason for this strange restriction?
2) I don’t understand reasons of ‘protected’ visibility level elimination. In my opinion it helpful for example in design pattern ‘Template method’ when base abstract class has some protected methods which should be visible only in subclasses.
I think some customization of shared annotation is reasonable. The extra 'restricted' annotation could be replaced by shared with optional visibility level:
+ shared
+ shared(subclasses)
+ shared(package)
+ shared(module)
3) I'm trying to write something working on Ceylon as to learn it faster.
To investigate it metamodel I want create some base class 'DataContainer' which allow to instantiate immutable classes with build-in equals+hash implementation: Identifier(125, "ab") == Identifier(125, "ab")
So I wrote this:
shared abstract class DataContainer(ClassDeclaration classDecl) {
value members = {
for (item in classDecl.memberDeclarations<ValueDeclaration>())
if (!item.variable
&& item.name != "hash"
&& item.name != "string") item
};
variable Integer? hashCode = null;
shared actual Boolean equals(Object that) {
if (is DataContainer that) {
for (item in members) {
value thisMember = item.memberGet(this);
value thatMember = item.memberGet(that);
if (exists thisMember, exists thatMember, thisMember != thatMember) {
return false;
}
if (thisMember exists != thatMember exists) {
return false;
}
}
return true;
}
return false;
}
shared actual Integer hash => hashCode else (hashCode = calculateHash());
Integer calculateHash() {
variable value result = 0;
for(item in members) {
value itemValue = item.memberGet(this);
if (exists itemValue) {
result = result.xor(itemValue.hash);
}
}
return result;
}
}
class Identifier(shared Integer? id, shared String? name) extends DataContainer(`class`) {}
All good for me except invoking DataContainer constructor with (`class`). Because if I use `class` inside super class it doesn't see any members of subclass.
How can I obtain actual list of extended class's members in base class methods?
Something likes `this` is not working...
shared class GenericTest<Element>() {
shared void dump(Element precision) {
if (this is GenericTest<String>) {
print("Method for String called");
} else if (this is GenericTest<Integer>) {
print("Method for Integer called");
}
}
}
I found solution to detect Element type in runtime in this way: “if (this is GenericTest<Integer>) …”.
Can I just ask “Element” like “if (is Integer Element) …”? Maybe something wrong with syntax?
Can I write special method which will be used only for particular type e.g.:
shared void dump<String>(Element precision) {
}
Version of method when Element is String.
shared void dump<Integer>(Element precision) {
}
Version of method for Integer.
shared void dump (Element precision) {
}
Version for other types of Element.
5) Question about Eclipse IDE. The Idea IDE starting suggest me something when I typing text it very helpful – I don’t need write exactly “shared” and other annotations, keywords, values and etc. I just choose required. But in Eclipse completion triggered only when I typed dot. Is it possible to change triggering logic? For example activate completer when user typed at least 3 characters and after 200 ms delay?
For #4, try using "if (is Integer precision)" instead of "if (is Integer Element)" and it should work.
For #6, check out this part of the Tour: https://ceylon-lang.org/documentation/1.3/tour/modules/#services_and_service_providers
I haven't tried it myself, but it sounds like you might be looking to have your plugins act as service providers.
It's OK when you have at least one parameter of method with type Element (ie generic), but if not? Nevertheless infromation about Element type is present in runtime but I don't know simple syntax to extract it.
I thought about it, but where modules files will be searched? I need provide some path on file system where my plugins are installed but API doesn't allow this.
Good point. There might be an issue with the design of the class. That is, if it's trying to be generic, why do some of its functions need to change their behavior so much when the parameter is Integer or String?
7) Anything - what kind of beast is that?
On the one hand it is synonym of 'void' in Ceylon and means "nothing" in this case.
But on the other hand it is the root of classes hierarchy and means any object as superclass of any other classes. If we take into account first definition (synonym of 'void') I can return from 'void' function e.g. Object because 'Anything' is superclass of it.
One substance with two mutually exclusive definitions - is enough confusing for me.
8) Are there direct support of JNI in Ceylon? I found 'native' annotation but it means call of Java (or JS). Are there in Ceylon something like Java 'native' keyword?
For the Anything class, it's not quite a synonym of void. Rather, void functions are considered to have a return type of Anything. It doesn't quite mean "nothing," either, but the Nothing class does! Basically, I think what happened is that the type checker needed to have a concept of the union of every type in the universe, which is the Anything class. For symmetry, it also needed to have the concept of the *intersection* of every type in the universe, which should be a completely empty class, so they made the Nothing class.
And as for the question of void functions being considered to return Anything, it makes sense when you're, say, making a collection of callback functions or something. You don't want to declare it as "collection of void functions," in case you want to add a function that returns something. You would want to declare it as "collection of functions that have return type Anything." Since void functions count as returning Anything, you can do exactly that.
--
You received this message because you are subscribed to the Google Groups "ceylon-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ceylon-users+unsubscribe@googlegroups.com.
To post to this group, send email to ceylon...@googlegroups.com.
Visit this group at https://groups.google.com/group/ceylon-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/86e9dd47-2702-46bb-88a0-c097bc9d6b17%40googlegroups.com.
For the Anything class, it's not quite a synonym of void. Rather, void functions are considered to have a return type of Anything. It doesn't quite mean "nothing," either, but the Nothing class does! Basically, I think what happened is that the type checker needed to have a concept of the union of every type in the universe, which is the Anything class. For symmetry, it also needed to have the concept of the *intersection* of every type in the universe, which should be a completely empty class, so they made the Nothing class.
And as for the question of void functions being considered to return Anything, it makes sense when you're, say, making a collection of callback functions or something. You don't want to declare it as "collection of void functions," in case you want to add a function that returns something. You would want to declare it as "collection of functions that have return type Anything." Since void functions count as returning Anything, you can do exactly that.
2) The truth is that Java "protected" does not offer the kind of protection its name promises. You can call a protected method from anywhere in your code, just by creating an auxiliary class that extends the "protective" class:class A {protected void p() {}}A a = new A();a.p(); // sure, this does not compileclass Aux extends A {public void p2() {p();}}A a = new Aux();a.p2(); // but this does compileI've used this hack dozens of times in my life. So, the designers of Ceylon thought that mixing inheritance and protection in the same concept was not a good idea. You either want to share your code or not, it doesn't matter if the consumer is a child of yours or a total stranger.
3) Your intuition is right, you don't need to pass the ClassDeclaration of the as a constructor argument. You can get it in the parent like this:
late value members = {
for (item in classDeclaration(this).memberDeclarations<ValueDeclaration>())
if (!item.variable
&& item.name != "hash"
&& item.name != "string") item
};Note that, since "value members" is declared in the initializer section, it initially forbids the use of "this", which can be solved by annotating it as "late". Read the chapter on initialization of the Ceylon Tour for more on this.
late value members = {
for (i in `class`.memberDeclarations<ValueDeclaration>())
if (!i.variable, i.name != "string", i.name != "hash") i
};
4) I guess you want something like this:
shared void dump(Element precision) {
if (`Element` == `String`) {
print("Method for String called");
} else if (`Element` == `Integer`) {
print("Method for Integer called");
}
}
import java.lang { jni = native }
native void doNativeCall(Integer num);
native jni void doNativeCall(Integer num);
Anything is the root class. It only has two subclasses: Object and Null. Yes, Null is a type in Ceylon; that's how you get null safety: all types extends Object, except Null. The only way you can accept null somewhere is if the type is Anything, because it's the supertype of Null, or if you use a union type of Null and something else.Nothing, on the other hand, is the bottom class. It extends all existing types in the system, and it cannot have any subtypes.
I have tried use this one:late value members = {
for (i in `class`.memberDeclarations<ValueDeclaration>())
if (!i.variable, i.name != "string", i.name != "hash") i
};
But result is same - members is empty.
`this` - not working - compilation error.
What is "classDeclaration(this)." in your example?#8 (JNI): I have tried this one:
shared abstract class DataContainer() {
variable Integer? _hash = null;
variable ValueDeclaration[]? _members = null;
shared actual Boolean equals(Object that) {
if (is DataContainer that) {
for (i in members) {
value thisMember = i.memberGet(this);
value thatMember = i.memberGet(that);
if (exists thisMember, exists thatMember) {
if (thisMember != thatMember) { return false; }
} else if (thisMember exists != thatMember exists) { return false; }
}
return true;
}
return false;
}
shared actual Integer hash => _hash else (_hash = calculateHash());
ValueDeclaration[] members => _members else (_members = [
for (i in classDeclaration(this).memberDeclarations<ValueDeclaration>())
if (!i.variable, i.name != "string", i.name != "hash") i
]);
Integer calculateHash() {
variable Integer result = 0;
for (i in members) {
if (exists member = i.memberGet(this)) {
result = result.xor(member.hash);
}
}
return result;
}
}
Import 'native' keyword from Java:import java.lang { jni = native }
Declare native method without body in some class:native void doNativeCall(Integer num);
It's OK on this stage, but "doNativeCall" is just call to Java (not JNI).
Try add 'jni' annotation (synonym of java.lang.native):native jni void doNativeCall(Integer num);
And have got compilation error: "illegal reference to native declaration 'jni': native declaration 'doNativeCall' has a different backend"
What backend means?
jni void doNativeCall();And it compilable, does it means that is real JNI call?
value id1Copy = id1.copy(`Identifier.name`->"newName");
shared abstract class DataContainer<Subclass>() {
variable Integer? _hash = null;
variable Map<String, ValueDeclaration>? _members = null;
shared actual Boolean equals(Object that) {
if (!is DataContainer<Subclass> that) {
return false;
}
for (i in members) {
if (!isAnythingEqual(i.item.memberGet(this), i.item.memberGet(that))) { return false; }
}
return true;
}
hash => _hash else (_hash = calculateHash());
"Returns copy of instance with modified attribute(s) if there are changes in provided attributes and returns the same instance otherwise."
shared Subclass? copy(Entry<Attribute<>, Anything>* attributes) {
try {
value changedValues = HashMap<String, Anything>();
for (attr -> v in attributes) {
if (exists member = members[attr.declaration.name], member.openType == attr.declaration.openType) {
if (!isAnythingEqual(v, member.memberGet(this))) {
changedValues[attr.declaration.name] = v;
}
} else { return null; }
}
if (changedValues.empty) {
return if (is Subclass thisInstance = this) then thisInstance else null;
}
value constructor = classDeclaration(this).defaultConstructor;
if (!exists constructor) {
return null;
}
value args = ArrayList<Anything>();
for (p in constructor.parameterDeclarations) {
if (exists member = members[p.name], member.openType == p.openType) {
args.add(if (changedValues.defines(p.name)) then changedValues[p.name] else member.memberGet(this));
} else { return null; }
}
return if (is Subclass newInstance = constructor.invoke([], *args)) then newInstance else null;
} catch (Exception e) {
return null;
}
}
Map<String, ValueDeclaration> members => _members else (_members = HashMap {
for (i in classDeclaration(this).memberDeclarations<ValueDeclaration>())
if (!i.variable, i.name != "string", i.name != "hash") i.name -> i
});
Integer calculateHash() {
variable Integer result = 0;
for (i in members) {
if (exists member = i.item.memberGet(this)) {
result = result.xor(member.hash);
}
}
return result;
}
Boolean isAnythingEqual(Anything value1, Anything value2)
=> if (exists value1, exists value2) then value1 == value2 else value1 exists == value2 exists;
}
class Identifier(shared Integer? id, shared String? name) extends DataContainer<Identifier>() {}
shared abstract class DataContainer<Subclass>() {
variable Integer? _hash = null;
variable Map<String, ValueDeclaration>? _members = null;
shared actual Boolean equals(Object that) {
if (!is DataContainer<Subclass> that) {
return false;
}
for (i in members) {
if (!isAnythingEqual(i.item.memberGet(this), i.item.memberGet(that))) { return false; }
}
return true;
}
hash => _hash else (_hash = calculateHash());
"Returns copy of instance with modified attribute(s) if there are changes in provided attributes and returns the same instance otherwise."
shared Subclass? copy(Entry<Attribute<>, Anything>* attributes) {
try {
value changedValues = HashMap {
for (a in attributes) if (isAttributeChanged(a)) a.key.declaration.name->a.item
};
if (changedValues.empty) {
return if (is Subclass thisInstance = this) then thisInstance else null;
}
return if (exists constructor = classDeclaration(this).defaultConstructor, is Subclass newInstance = constructor.invoke([], for (p in constructor.parameterDeclarations) getArg(p, changedValues))) then newInstance else null;
} catch (Exception e) {
return null;
}
}
Map<String, ValueDeclaration> members => _members else (_members = HashMap {
for (i in classDeclaration(this).memberDeclarations<ValueDeclaration>())
if (!i.variable, i.name != "string", i.name != "hash") i.name -> i
});
Integer calculateHash() {
variable Integer result = 0;
for (i in members) {
if (exists member = i.item.memberGet(this)) {
result = result.xor(member.hash);
}
}
return result;
}
Boolean isAttributeChanged(Entry<Attribute<>, Anything> attribute) {
value member = members[attribute.key.declaration.name];
assert(exists member, member.openType == attribute.key.declaration.openType);
return !isAnythingEqual(attribute.item, member.memberGet(this));
}
Anything getArg(FunctionOrValueDeclaration param, Map<String, Anything> attributes) {
value member = members[param.name];
assert(exists member, member.openType == param.openType);
return if (attributes.defines(param.name)) then attributes[param.name] else member.memberGet(this);
}
Boolean isAnythingEqual(Anything value1, Anything value2)
=> if (exists value1, exists value2) then value1 == value2 else value1 exists == value2 exists;
}
class Identifier(shared Integer? id, shared String? name) extends DataContainer<Identifier>() {}
shared {Service*} loadServices<Service>(ClassOrInterface<Service> service, {String+} paths, String? filter = null) {
value manager = CeylonUtils.repoManager()
.noSystemRepo(true)
.noDefaultRepos(true)
.noCacheRepo(true)
.noOutRepo(true)
.offline(true)
.extraUserRepos(JavaList(JavaStringList(ArrayList<String> { *paths })))
.buildManager();
value loader = CeylonModuleLoader(manager, false);
value searchResult = manager.searchModules(ModuleQuery(filter else ".",ModuleQuery.Type.ceylonCode));
for (name in searchResult.moduleNames) {
try {
loader.loadModuleSynchronous(name.string, searchResult.getResult(name.string).lastVersion.version);
} catch (Exception e) {
//print(e.message);
}
}
for (m in modules.list) {
if (JString(m.name) in searchResult.moduleNames) {
print("Name: ``m.name``, services: ``m.findServiceProviders(service)``");
}
}
return `module`.findServiceProviders(service);
}
return `module`.findServiceProviders(service);
for (m in modules.list) {
if (JString(m.name) in searchResult.moduleNames) {
print("Name: ``m.name``, services: ``m.findServiceProviders(service)``");
}
}
--
You received this message because you are subscribed to the Google Groups "ceylon-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ceylon-users+unsubscribe@googlegroups.com.
To post to this group, send email to ceylon...@googlegroups.com.
Visit this group at https://groups.google.com/group/ceylon-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/71df60d8-f3b1-4caf-a2d4-ee2368389e43%40googlegroups.com.
9) Can I disable generation of sources package for module (just .car with comipled code)?
We've never used this outside of the runtime and JBoss Modules. I don't think it will work because it's not meant for composition. You should take a look at how `ceylon.test` in the SDK loads modules dynamically, or the JBossModuleLoader class. We've never standardized dynamic module loading, sorry.
shared {Service*} loadServices<Service>(ClassOrInterface<Service> service, String? filter = null) {
value loader = ceylonModuleLoader;
if (!is CeylonModuleLoader loader) {
return {};
}
value searchResult = CeylonUtils.repoManager()
.noSystemRepo(true)
.noDefaultRepos(false)
.noCacheRepo(true)
.noOutRepo(true)
.offline(true)
.buildManager()
.searchModules(ModuleQuery(filter else ".",ModuleQuery.Type.ceylonCode));
if (searchResult.moduleNames.empty) {
return {};
}
for (name in searchResult.moduleNames) {
try {
value details = searchResult.getResult(name.string);
if (!details.remote) {
loader.loadModuleSynchronous(name.string, details.lastVersion.version);
}
} catch (Exception e) {
}
}
return { for (m in modules.list)
if (Types.nativeString(m.name) in searchResult.moduleNames)
for (s in m.findServiceProviders(service)) s };
}
I have prepared some workable solution, but it can only dynamically load modules from "~/.ceylon/repo" (paths customizations is not possible):
As I understand from Ceylon sources the class CeylonModuleLoader is custom implementation of JBossModuleLoader, correct?
Could you please point me where in ceylon.test code the module loaded dynamically?
I don't see why not, but the loader you're using is using the settings passed at startup. You can add your lookup repos at startup. Otherwise if you're using the current loader, then yes indeed you can't add paths after startup.
import org.jboss.modules {I cann't add new path for seach to existing CeylonModuleLoader.
JBossModule = Module {
ceylonModuleLoader = callerModuleLoader
}
}
As I understand from Ceylon sources the class CeylonModuleLoader is custom implementation of JBossModuleLoader, correct?No, I don't think so. They're two separate systems.
/**
* Ceylon JBoss Module loader.
* It understands Ceylon repository notion.
*
* @author <a href="mailto:">Ales Justin</a>
*/
public class CeylonModuleLoader extends ModuleLoader
Could you please point me where in ceylon.test code the module loaded dynamically?
Really? But I see that CeylonModuleLoader is subclass of JBoss ModuleLoader:
Any way, what is your idea? How JBoss module loader might help me? Can it direct load .car file?
OK. Thank you. I already use this trick in my code. But why when I created new instance of CeylonModuleLoader it doesn't work as expceted?
Stéphane, what you think about my other questions? As I understand from FAQ you working on Ceylon compiler.
1) About function/method's refernces equality.
2) About "protected"/subclass visibility?
Any way, what is your idea? How JBoss module loader might help me? Can it direct load .car file?Yes
OK. Thank you. I already use this trick in my code. But why when I created new instance of CeylonModuleLoader it doesn't work as expceted?Indeed you can't create a new one.
2) About "protected"/subclass visibility?https://ceylon-lang.org/documentation/1.3/faq/language-design/#no_protected_modifier has information about that.
import ceylon.collection "1.3.3";But if I don't want has dependency on particular version? I just want have latest version of module. How can I do this?
Naturally I don't want external classes which use TemplateMethodClient can access to EvaluateSomeValue().
How can I implement this pattern in Ceylon?
Is 'template method' design pattern "superstition"? :)
And more questions:
10) In module descriptor I have to set particular version of module:import ceylon.collection "1.3.3";But if I don't want has dependency on particular version? I just want have latest version of module. How can I do this?
On Dec 5, 2017, at 11:17 AM, Alexander Kornilov <akorni...@gmail.com> wrote:2) About "protected"/subclass visibility?https://ceylon-lang.org/documentation/1.3/faq/language-design/#no_protected_modifier has information about that.
Yeh, I already read it... But what about my example:
I think the designers' view was that, if you're importing that base class from a module you didn't write and extending it with code you did write, you're going to need to know about the EvaluateSomeData method anyway, so it's pretty much part of the public interface already.
shared abstract class TemplateMethod() {
shared String performSomthing(String input) {
return evaluatePrefix() + input;
}
shared formal String evaluatePrefix();
}
shared class TemplateClient() extends TemplateMethod() {
shared actual restricted String evaluatePrefix() {
return "Client prefix";
}
}
shared abstract class VisibilityLevel() of outside | limited | subclass {}
"[Default] The element is visible outside block"
shared object outside extends VisibilityLevel() {}
"The visibility of element is restricted by package or module"
shared object limited extends VisibilityLevel() {}
"The element is vivible only in subclasses"
shared object subclass extends VisibilityLevel() {}
shared final sealed annotation class PublicAnnotation(VisibilityLevel level = outside, Module* modules)
satisfies OptionalAnnotation<PublicAnnotation,
FunctionOrValueDeclaration
| ClassOrInterfaceDeclaration
| ConstructorDeclaration
| Package> {}
shared annotation PublicAnnotation public(VisibilityLevel level = outside, Module* modules)
=> PublicAnnotation(level, *modules);
void privateFunction() {
}
public void publicFunction() {
}
public(subclass) void protectedFunction() {
}
public(limited) void packageRestrictedFunction() {
}
public(limited, `module`) void moduleRestrictedFunction() {
}
shared class SomeService() {
Map<Integer, String> clients = HashMap();
shared Map<Integer, String> getClients()
=> clients;
}
shared void someServicesUser(SomeService service) {
value clients = service.getClients();
//clients[125] = "Hi Guys!";
// This doesn't work but I can do:
if (is HashMap<Integer, String> clients) {
//Yeh!! I have got hack them!!!
clients[125] = "Hi Guys!";
}
}
shared interface Map<out Key=Object, out Item=Anything>
satisfies Collection<Key->Item> &
Correspondence<Object,Item>
given Key satisfies Object {
shared const actual formal Item? get(Object key);
"The clients is 'const' reference"
shared void someMapUser(const HashMap<Integer, String> clients) {
clients[125] = "Hi Guys!"; // Doesn't work because 'put' is not marked as 'const'
}