www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Allowing arbitrary types for a function's argument and return type

reply pineapple <meapineapple gmail.com> writes:
I'm just starting to hammer D's very pleasant syntax into my 
head. After "Hello world", the first thing I do when learning any 
language is to write a simple program which generates and outputs 
the Collatz sequence for an arbitrary number. (I also like to 
golf it.) This is what I wrote in D:

import std.stdio;void main(){int i;readf(" 
%d",&i);while(i>1){writeln(i=i%2?i*3+1:i/2);}}

Any ways I could shorten it further?

Anyway, then I thought I should try something that was less of a 
mess, too, and wrote this:

import std.concurrency;
Generator!int sequence(int i){
     return new Generator!int({
         yield(i);
         while(i > 1){
             yield(i = (i % 2) ? (i * 3 + 1) : (i >> 1));
         }
     });
}

Which can be used like so:

import std.stdio;
void main(){
     foreach(i; sequence(11)){
         writeln(i);
     }
}

And now I'd like to make one more improvement, but this I haven't 
been able to figure out. What if I wanted the argument and output 
types to be longs instead of ints? Or some other, arbitrary 
discrete numeric type? Is there any template-like syntax I can 
use here instead of just copypasting for each numeric type I can 
think of? I've been spoiled by the likes of Python to be thinking 
in this duck-typing way.

Thanks!
Oct 22 2015
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 22 October 2015 at 13:53:33 UTC, pineapple wrote:
 import std.concurrency;
 Generator!int sequence(int i){
     return new Generator!int({
         yield(i);
         while(i > 1){
             yield(i = (i % 2) ? (i * 3 + 1) : (i >> 1));
         }
     });
 }

 Which can be used like so:

 import std.stdio;
 void main(){
     foreach(i; sequence(11)){
         writeln(i);
     }
 }

 And now I'd like to make one more improvement, but this I 
 haven't been able to figure out. What if I wanted the argument 
 and output types to be longs instead of ints?
D's templates are easy (you actually used one in there, the Generator is one!) Try this: import std.concurrency; Generator!T sequence(T)(T i){ return new Generator!T({ yield(i); while(i > 1){ yield(i = (i % 2) ? (i * 3 + 1) : (i >> 1)); } }); } The first set of args, `(T)`, are the template arguments. You can then use hat anywhere in teh function as a type placeholder. Now, when you call it, it can automatically deduce the types: foreach(i; sequence(11)){ // still works, uses int foreach(i; sequence(11L)){ // also works, uses long now Or you can tell your own args explicitly: foreach(i; sequence!long(11)){ // uses long The pattern is name!(template, args, ...)(regular, args...) The ! introduces template arguments. If there is just one simple argument - one consisting of a single word - you can leaves the parenthesis out.
Oct 22 2015
parent pineapple <meapineapple gmail.com> writes:
On Thursday, 22 October 2015 at 13:58:56 UTC, Adam D. Ruppe wrote:
 D's templates are easy (you actually used one in there, the 
 Generator is one!)

 Try this:

 import std.concurrency;
 Generator!T sequence(T)(T i){
     return new Generator!T({
         yield(i);
         while(i > 1){
             yield(i = (i % 2) ? (i * 3 + 1) : (i >> 1));
         }
     });
 }
Wonderful, thanks much! It keeps surprising me how easy it is to do stuff like this.
Oct 22 2015
prev sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Thursday, 22 October 2015 at 13:53:33 UTC, pineapple wrote:
 I'm just starting to hammer D's very pleasant syntax into my 
 head. After "Hello world", the first thing I do when learning 
 any language is to write a simple program which generates and 
 outputs the Collatz sequence for an arbitrary number. (I also 
 like to golf it.) This is what I wrote in D:

 import std.stdio;void main(){int i;readf(" 
 %d",&i);while(i>1){writeln(i=i%2?i*3+1:i/2);}}

 Any ways I could shorten it further?

 Anyway, then I thought I should try something that was less of 
 a mess, too, and wrote this:

 import std.concurrency;
 Generator!int sequence(int i){
     return new Generator!int({
         yield(i);
         while(i > 1){
             yield(i = (i % 2) ? (i * 3 + 1) : (i >> 1));
         }
     });
 }

 Which can be used like so:

 import std.stdio;
 void main(){
     foreach(i; sequence(11)){
         writeln(i);
     }
 }

 And now I'd like to make one more improvement, but this I 
 haven't been able to figure out. What if I wanted the argument 
 and output types to be longs instead of ints? Or some other, 
 arbitrary discrete numeric type? Is there any template-like 
 syntax I can use here instead of just copypasting for each 
 numeric type I can think of? I've been spoiled by the likes of 
 Python to be thinking in this duck-typing way.

 Thanks!
Using ranges instead of threads or fibers, slightly over-engineered to show off features: import std.traits : isIntegral; auto collatzStep(T)(T i) if(isIntegral!T) { return (i % 2) ? (i * 3 + 1) : (i >> 1); } auto collatz(T)(T a) if(isIntegral!T) { import std.range : recurrence; import std.algorithm : until, OpenRight; return a.recurrence!((a, n) => collatzStep(a[n-1])) .until!(n => n == 1)(OpenRight.no); } unittest { import std.algorithm : equal; import std.range : only; assert(collatz(6L).equal(only(6, 3, 10, 5, 16, 8, 4, 2, 1))); }
Oct 22 2015
parent reply pineapple <meapineapple gmail.com> writes:
On Thursday, 22 October 2015 at 14:36:52 UTC, John Colvin wrote:
 Using ranges instead of threads or fibers, slightly 
 over-engineered to show off features:
What does if(isIntegral!T) do? It looks like it would verify that the template type is a discrete number? If I were to create my own class, say a BigNum as an example, how could I specify that the isIntegral condition should be met for it? Apart from the aesthetics, what are the functional differences between using recurrence and using a Generator? Will one be more efficient than the other? It's not fair how easy it is to incorporate unit tests in D. Now what excuse will I have when my code is buggy?
Oct 22 2015
next sibling parent John Colvin <john.loughran.colvin gmail.com> writes:
On Thursday, 22 October 2015 at 15:10:58 UTC, pineapple wrote:
 On Thursday, 22 October 2015 at 14:36:52 UTC, John Colvin wrote:
 Using ranges instead of threads or fibers, slightly 
 over-engineered to show off features:
What does if(isIntegral!T) do? It looks like it would verify that the template type is a discrete number? If I were to create my own class, say a BigNum as an example, how could I specify that the isIntegral condition should be met for it?
Only with builtin types: http://dlang.org/phobos/std_traits.html#isIntegral However there are a variety of ways you could mark special properties of types and have your own isIntegral-like template that recognised them. user-defined-attributes are one possibility, adding an `enum isIntegral = true;` is another.
 Apart from the aesthetics, what are the functional differences 
 between using recurrence and using a Generator? Will one be 
 more efficient than the other?
ranges are likely to be more efficient because, apart from anything else, they are easier for the compiler to reason about and optimise. Both are good tools, but ranges are more widespread in D.
 It's not fair how easy it is to incorporate unit tests in D. 
 Now what excuse will I have when my code is buggy?
:)
Oct 22 2015
prev sibling parent Kagamin <spam here.lot> writes:
On Thursday, 22 October 2015 at 15:10:58 UTC, pineapple wrote:
 What does if(isIntegral!T) do? It looks like it would verify 
 that the template type is a discrete number?
It doesn't verify, but filters: you can have several templates with the same name, when filter doesn't match, compiler tries another template. Maybe you can write collatzStep for string type, then this code wouldn't compile, because it doesn't make sense for strings. Remove the filter and the template will always compile with whatever it's supplied including BigNum as long as the code makes sense.
Oct 22 2015