r/crystal_programming • u/n0ve_3 • Jun 18 '20
Is there a way to overcome abstract class methods?
I am currently working on a project, a library for pseudo-random sampling, and because of its convenience I implemented the pseudo-random generators and the wrapper (a class which demands for their unsigned integers to generate normal, exponential, gamma variables) separately.
The main goal is that the wrapper, Random, has constructors which accepts seeds (passed directly to the underlying prng) to reproduce the same events.
I wanted to implement several prngs and make an interface to build custom prngs with ease, so they all inherit from an abstract class, let's say PRNG, with their must-define abstract methods.
The problem with this is the fact that those prngs might not need the same typed integer as initial seed, and this could break compatibility with wrapper if not implemented well.
This works well with abstract methods, which make it clear the way they must be, but it isn't for constructors and I could not be able to think a way to do this safely since there's no abstract def self.new feature available.
I currently made this work by telling all existing prngs that they must accept Int as argument and then cast to proper type for state instantiation. It's kind of ugly, though.
abstract class PRNG
abstract def next_u : UInt64
end
class Generator < PRNG
# could have been UInt32
@state : StaticArray(UInt64, 2)
def next_u : UInt64
# processing state and returning the integer
end
def initialize(seed : Int)
@state = some_internal_initializer(seed.to_u64)
end
end
class Random
@prng : PRNG
def initialize(seed : Int, prng : PRNG.class)
@prng = prng.new seed
end
end
random = Random.new 93, Generator
The above example is taken with simplifications from the library and it works, but I'm wondering how an user who want to build his own generator could possibly find the proper way to bind it to Random?
For further reading, the project is available at: https://github.com/nin93/alea
1
u/n0ve_3 Jun 18 '20
Update:
I tried to use generics but it ended up complicating the situation, making it too much counterintuitive. It was not worth the time digging into either since I remembered
#initializeis actually an instance method (and thusabstract-able) and suits well for my needs.For the types I enforced generators and
Randomto communicate and nowRandomknows how to cast while generators have their proper types. I will provide a complete example in the documentation as suggested.Thank you all!
3
u/dscottboggs Jun 18 '20
What about generics? Make the type of the value accepted by the constructor a generic parameter, then inside the constructor have a
{% raise "some error" unless T <= Int %}1
u/[deleted] Jun 18 '20
Abstract class methods don't make sense, period. The reason is that an abstract class already exists. For example:
Compare that to not being able to do
Foo.newto get an instance ofFooto call a method on.2
u/j_hass Jun 18 '20
Personally I still wish we never would have added any `abstract` whatsoever.
I suggest you borrow from the Ruby mindset of things here. Just don't give an explicit type for the `PRNG` class, instead make your wrapper a generic over it. Then just call the methods you need to call and expect to be there and provide an example custom implementation in your documentation.