I know you can type declare arguments and returns on functions
some-func: function [
"some func"
number [ integer! ]
] [
result [ integer! ]
] [
help number
return number
]
some-func 1
some-func "blah"
NUMBER is an integer of value: 1
** Script error: some-func does not allow string! for its number argument
How about object properties though?
o: make object! [
a [string!]
b [integer!]
c [o2]
none
]
o2: make object! [
c [string!]
]
an-object: make o [
a: 3.141
b: "an integer"
c: "blah"
]
help an-object
N-OBJECT is an object of value:
a decimal! 3.141
b string! "an integer"
c string! "blah"
I've seen the type declaration on properties as examples, but is it just for documentation?
This is a really good question, and something I've thought about for years. It turns out that Rebol's internal object storage mechanism can handle this, but there's no way of expressing it in source code. Why not you ask? Here's why:
Rebol currently has the concept of name-value pairs. That's how contexts and objects are expressed. However, it is often desirable for objects to include other information that's not just a name or value. The datatype is a good example. Other examples are comments attached to values, and protections/permissions on values (such as allowing read and write).
So, the problem becomes: how many various features do we want to support in the language syntax, and specifically how would we do that? It gets further complicated by the "optional" characteristic of these features. So, you can't really use positional semantics to describe the object. That means adding a syntactic method, which means adding keywords (because Rebol really tries to avoid punctuation.)
So, as a result, the source form would become fairly verbose, and I think we could question whether it would be worth the benefit we'd get from allowing the feature in the first place.
So, this is a case where the simple principle of Rebol takes precedence over feature creep.
All that said, if you've got an idea for a simple method of doing it, let it be known!
It's just for documentation .. type checking is only done on functions.
Related
I have a Module AttrX::Mooish which implements some of attribute features of Moo/Moose frameworks (lazyness, trigger, etc.). I also wanted the module to be as transparent to the end user as possible meaning support for both private and public attributes. It works by replacing attribute's container with a Proxy and storing its value in external storage. It also means that all the type checking and coercion Perl6 was doing is now my responsibility. My target is to mimic the default behavior is much as possible. I.e. for the end user:
has MyClass #foo is mooish(...);
must work the same as it would without the trait applied. Unfortunately, the subject of type manipulations is so much complicated and ununified in the language core that the more problems I fix the more problems I get afterwards. For example:
my Str #a = <a b c>; my Str #b = [1,2,3]
Type check failed in assignment to #b; expected Str but got Int (1)
As expected.
my Str #a; say #a.WHAT
(Array[Str])
Sure.
my Array[Str] $a = ["a", "b", "c"];
Type check failed in assignment to $a; expected Array[Str] but got Array ($["a", "b", "c"])
Well....
my Array[Str] $a = <a b c>;
Type check failed in assignment to $a; expected Array[Str] but got List ($("a", "b", "c"))
Not even coercing List to Array!
No wonder that the final typecheck line in my trait code:
$coerced-value ~~ $attr.type
Fails here and there despite same values/types used in variable/attribute assignments work OK.
I have a question with no hope of getting any positive answer to it: is there a single entry point used by the assignment operator which does all the coerce/typecheck? Ideally I would simply:
$value = coerce($value, $type);
check-type($value, :type($attr.type), :name($attr.name))
I tried to trace down from the grammar, but haven't got enough spare time to complete this yet. Besides, it is mostly nqp which I don't know and can't really understand.
But since the existence of such entry point(s) is unlikely I would like to ask for any advises related to this area. Like, for example, SmokeMachine on #perl6 provided me with a great idea of obtaining base type of a parametrized type using .^parents method.
So far, the biggest problems are with:
To check if type is parametrized I can't use a single role or class to match against. So far the only approach I have is by finding if there is of method and testing its output. Unfortunately, if a class provides FALLBACK very unclear error message (the one about AUTOGEN) is produced. :no_fallback is desirable, but definite and subset types have their own find_method which doesn't support named parameters and I end up with another error message.
If a prepare type-related attributes ($!coerce-type) in compose method of my trait role applied to the Attribute object (where actually the attributes are declared) I find them later at run-time unitialized. Guessing its something related to compose time. But wanna be sure if nothing is missed here.
Is there any better way to perform type-check than $value ~~ $type?
[Comments indicate that this out-of-date question was supposed to be closed in 2020 but it never was. Here's a very brief answer in case someone comes across this.]
Sometime after asking this question, the asker significantly revised Raku's coercion mechanism. See Report on New Coercions. This new syntax for coercion is in the docs.
Using this new style, the following line from the question:
my Array[Str] $a = ["a", "b", "c"];
say $a;
# OUTPUT: Type check failed in assignment to $a;
# expected Array[Str] but got Array ($["a", "b", "c"])
becomes
my Array[Str]() $a = ["a", "b", "c"];
say $a;
# OUTPUT: [a b c]
(That is, Array[Str]() means "something that can be coerced into an Array of Strings". That's different from Array[Str()] ("an Array of things that can be coerced to Strings") and from Array[Str()]() ("something that can be coerced into an Array of things that can be coerced into Strings").)
Similarly, the following part of the question now has an answer:
is there a single entry point used by the assignment operator which does all the coerce/typecheck? Ideally I would simply [define how a type is coerced into my type]
With the new coercion protocol, the "single entry point" is the COERCE method, which users can define for their types to teach Raku how to coerce into those types.
I can document a function like this:
f: func [
"a description"
arg1 [string!] "a description of an argument 1"
][
arg1
]
I can use ?/help in order to retrieve informations about the the function (a description, a usage, the argument list, a description of each argument and it's type)
? f
USAGE:
F arg1
DESCRIPTION:
a description
F is a function value.
ARGUMENTS:
arg1 -- a description of an argument 1 (Type: string)
I cannot document dialects like this. Is there an automatic way to document dialects (like func does)? Do I have to do this manually?
There's nothing for it currently, but it's a good idea. So good that someone has suggested it before. :-)
Do I have to do this manually?
You can manually write a new generator which defines your "dialect spec" format. Then either do something like give it a HELP command, or extend HELP to recognize it.
Very short example to demonstrate a group of techniques which may come in handy in doing something like this (not all expected to be obvious, rather to hint at the flexibility):
make-dialect: function [spec [block!] body [block!]] [
return function ['arg [block! word!]] compose/deep/only [
case [
arg = 'HELP [
foreach keyword (spec/keywords) [
print [keyword "-" {your help here}]
]
]
block? arg [
do func [arg] (body) arg
]
'default [
print "Unrecognized command. Try HELP."
]
]
]
]
So there's your function that takes a dialect spec and makes a function. Once you've got your generator, using it can be less manual:
mumble: make-dialect [keywords: [foo baz bar]] [
print ["arg is" mold arg]
]
>> mumble help
foo - your help here
baz - your help here
bar - your help here
>> mumble [<some> [dialect] {stuff}]
arg is [<some> [dialect] {stuff}]
The techniques used here are:
Soft Quoting - Usually you would have to say mumble 'help to "quote" the help as a lit-word! to get it to pass the word! to mumble (as opposed to running the default HELP command). But because arg was declared in the generated function as 'arg it was "soft quoted"...this means that words and paths will not be evaluated. (Parens, get-words, and get-paths still will be.) It's a tradeoff because it means that if someone has a variable they want to pass you they have to say :var or (var) as the argument instead of just var (imagine if the block to pass the dialect is in a variable) so you don't necessarily want to use it...but I thought it an interesting demo to make mumble help work without the lit-word!
Deep Composition - The spec and the body variables which are passed to make-dialect only exist as long as make-dialect is running. Once it's over, they'll be gone. So you can't leave those words in the body of the function you are generating. This uses COMPOSE/DEEP to evaluate parens in the body before the function generator runs to make the result, effectively extracting the data for the blocks and stitching them into the function's body structure.
Reusing Function's Binding Work - The generated function has a spec with a parameter arg that didn't exist at the call site of make-dialect. So arg has to be rebound to something, but what? It's possible to do it manually, but one easy way is to let FUNC do the work for you.
Those are some of the techniques that would be used in the proposed solution, which seeks to not only document dialects but provide an easy method by which their keywords might be remapped (e.g. if one's Rebol system has been configured for another spoken language).
I'm reading a book on Smalltalk and I have an exercise about the anomaly of disappearing element I'm not able to solve.
Object subclass: Book [
| isbn |
<comment: 'A book class'>
setIsbn: anIsbn [
isbn := anIsbn.
]
getIsbn [
^isbn.
]
= anotherBook [
^self getIsbn = anotherBook getIsbn.
]
]
| Library |
Library := Set new: 100.
Library add: (Book new setIsbn: '0-671-2-158-1').
(Library includes: (Book new setIsbn: '0-671-2-158-1')) printNl.
I read I have to override the hash method too, but I don't know how to do it. How do I amend the Book class in order to avoid the anomaly?
I can't really tell what are you asking about, but to override hash, you should do the same as with =, which you have overridden as well, just by including different definition. So you do something like:
hash [
"return hash here"
]
If you are asking what hash should be like… well think about it like that: objects that are equal have to have the same hash (but this doesn't have to work the other way around). So I'd suggest you to do something like
hash [
^ self getIsbn hash
]
Also about disappearing element. Set is a hashed collection. This means that before comparing it's element with the one you are looking for, it select's a subset by hash. So if you are not overriding hash it may select a subset that won't contain your desired element.
In the end I'd suggest you to use some different implementation of Smalltalk, because my head hurt when I was starting to learn it with gnu-smalltalk. Personaly I use pharo it provides a nice UI and allows you to see what you override, allows you to debug, etc.
There are a few further Smalltalk idiomatic issues with your code:
normally accessors in Smalltalk don't use get and set. So you'd have isbn: anIsbn and isbn.
you probably want to create an extra constructor that has the ISBN as a parameter so you don't have to send both new and the setter yourself:
Book>>onIsbn: anIsbn
^self new
isbn: anIsbn;
yourself
The basic rule is never override #= without overriding #hash
Let us say I have a situation like this:
;; Capture whatever the print word pointed to into a variable
outer-print: :print
foo: context [
;; within foo, override print and implement in terms of outer-print
print: func [value] [
outer-print "About to print"
outer-print value
outer-print "Done printing"
]
]
I can do this, or if I have more than one thing I want from the outer context I could capture it explicitly:
;; Capture current context into something called outer
outer: self
foo: context [
;; within foo, override print and implement in terms of outer/print
print: func [value] [
outer/print "About to print"
outer/print value
outer/print "Done printing"
]
]
Is this the right idiom, or is there a better way of doing it? Are there circumstances where this might not give me what I expect?
this is good style, especially the second, which is more flexible as it allows you to mass-effect all uses of the outer print, without any ambiguity. when using direct binding, it may occur that the word outer-print is redefined or the context changes between two calls to make foo [] and in the end, points to two different bindings.
static symbol resolving
For the sake of completeness there is a third alternative which doesn't require any extra words to be setup. I don't have a proper naming for it, feel free to suggest a better title.
This method defies any binding issues down the line because you use the function value directly:
foo: context compose/deep [
;; within foo, override print and implement using native print directly
print: func [value] [
(:print) "About to print"
(:print) value
(:print) "Done printing"
]
]
Now the interesting part is if you SOURCE the inner print function:
>> p: get in foo 'print
>> SOURCE P
== p: func [value][native "About to print" native value native "Done printing"]
see how the native value of print is used directly in the body, instead of a word referring to it.
This is, in fact, probably the closest we can get to some form of compilation in pure REBOL. instead of constantly using symbols to fetch and evaluate, we can simply statically resolve them manually, using reduce or compose as in the above.
pros:
It can never be hi-jacked by some advanced and malicious binding code, i.e. even if there are no direct word bounds to PRINT in ANY and ALL contexts, you still have a direct reference to the original function in your body.
cons:
Its a very static way to code, and isn't very "Rebolish".
The
;; Capture current context into something called outer
comment suggests that you think there is some "current context" in Rebol. That is false. Every word has got its own context. Thus, there are cases when your
outer: self
code doesn't work as you expect. For example, let's suppose that you want to access two variables, 'print and 'set. It is possible for the words to have different "outer" contexts. In that case the trick will be certain to not work for at least one of the words, but it may, in fact, not work for both.
Any idea why the following doesn't work? (R3)
o: make object! [
foo: does [do "bar"]
bar: does [print "hello from bar"]
]
o/foo
** Script error: bar has no value
** Where: catch either -apply- do foo
** Near: catch/quit either var [[do/next data var]] [data]
Try this:
o: make object! [
foo: does [do get bind load "bar" self]
bar: does [print "hello from bar"]
]
o/foo ;this will work
You need that BINDing because your "bar" lives in the object, not in global scope.
Check this out as well:
my-func: does [print "ok"]
o: make object! [
foo: does [do "my-func"]
bar: does [print "hello from bar"]
]
o/foo ;this will work too
You need to LOAD it because it is a string, but it has to be a word to be BINDed.
So these will work too (put them in your object):
do get bind to-word "bar" self
or
do get bind 'bar self
No Scope!!!?
The reason do "self/bar" cannot know where to find 'BAR is because there is no scope in Rebol (not in the traditional CS meaning at least).
Words in Rebol only have meaning once they have been statically bound to a context. This automagically occurs when you 'MAKE an object, so many people don't even realize it even after years of use.
Here are the steps (loosely) when an object (a.k.a. context) is created.
It picks up all the root set words in its spec (in this case [FOO: BAR:] )
Adds them to its current internal words (SELF: by default, more if you are using an object as basis)
Then binds all the words in the block (hierarchicaly) to those set-words it added to its spec.
Executes the block.
So you see, once you execute the block its too late, the words already got assigned their meaning, which allows the interpreter to ask for their values (which could trigger an expression evaluation, hence the E in REBOL).
Global, cause that all there really is once executing.
DO and LOAD cannot automatically bind to anything but the global context... because there is no such thing as the "current context" like you'd have in traditional OOP and imperative languages (remember, no scope). Really, once its executing, that information doesn't exist anymore, unless you bound the "current" context to a word... which is what 'SELF does, but to be bound it has to already be loaded, which, when executing a string, never occured.
clueless functions
I'll finish by adding that it may not be obvious at first sight, but while it was binding the Object spec block, it still didn't know what FOO and BAR really were. in fact, the only way FOO and BAR could access the 'O object, is because their function block, when it was run thru 'MAKE, got bound to the object... yep, before it even knew it was a function. then if the function defined its own locals, it would re-bind its body block to those new locals.. because you guessed it... a function creates its own inner context, which gets the same MAKE treatment (but without the internal SELF word).
I hope this helps clear things in a more obvious light.
here is a proof that code isn't scoped:
a: make object! [
data: "HAHAHAAAAA!!!"
action: does [print self/data]
]
b: make object! [
data: "BUMBLING BEHEMOT"
action: does [print self/data]
]
b/action: get in a 'action
; this will print HAHAHAAAAA!!!
b/action
To explain moliad's answer a bit more, see the following explanation:
REBOL words carry a reference to their context with them. It’s not
where a word is evaluated that makes the difference, but where it’s
declared.
from http://blog.revolucent.net/2009/07/deep-rebol-bindology.html
Here is a very good example:
x: 0
b: [] loop 3 [use [x] [x: random 100 append b 'x]]
;== [x x x] <-- there are three X which looks same words (and they are same actually)
reduce b
;== [95 52 80] <-- but they have different values in their contexts
probe x
;== 0 <-- in global context, it has another value as well
This looks weird at first look, but actually it is not. USE creates a new context each time in the LOOP, we set X (in the context the USE created) to a value, then APPEND the WORD (not the value!) to a block.
All the words that we append to the block carries their own contexts with them, but they look same word.
When we REDUCE (or PRINT etc.), when we GET their values in their own contexts, we see that they all have different values!