The Crystal Programming Language Forum

Generic & to_i

I want to do this:

class Intcode(T)
  @memory : Array(T)

  def initialize(listing : String)
    @memory = listing.split(',').map(&.to_i)
  end
end

where T is an integer type.

How would you write the map call to convert to an integer of type T?

1 Like

Hehe, I had the same refractor idea!

Number types have a new method for that, so you can do T.new(...). So convenient, right? :-)

Haha, thanks Ary!

Let me share my refactored Intcode with you then and ask further questions!

Usage is:

Intcode(Int64).new("104,1125899906842624,99").run do |output|
  p output
end

If the program needs input, you share an array:

input = [] of Int32
Intcode(Int32).new("...", input).run do |output|
  case output
  when 1 then input << 7
  ...
  end
end

Here’s my current Intcode:

class Intcode(T)
  @memory : Array(T)
  @ip : T
  @rb : T

  def initialize(listing : String, @input : Array(T) = Array(T).new)
    @memory = listing.split(',').map { |n| T.new(n) }
    @ip = zero # instruction pointer
    @rb = zero # relative base
  end

  def run
    loop do
      case current_opcode
      when 1
        write 3, read(1) + read(2)
        @ip += 4
      when 2
        write 3, read(1) * read(2)
        @ip += 4
      when 3
        write 1, @input.shift
        @ip += 2
      when 4
        yield read(1)
        @ip += 2
      when 5
        @ip = read(1) != 0 ? read(2) : @ip + three
      when 6
        @ip = read(1) == 0 ? read(2) : @ip + three
      when 7
        write 3, read(1) < read(2) ? one : zero
        @ip += 4
      when 8
        write 3, read(1) == read(2) ? one : zero
        @ip += 4
      when 9
        @rb += read(1)
        @ip += 2
      when 99
        break
      end
    end
  end

  private def ensure_enough_memory(offset)
    address = address(offset)
    if address >= @memory.size
      extension = Array(T).new(address - @memory.size + 1, zero)
      @memory.concat(extension)
    end
    yield address
  end

  private def write(offset, value)
    ensure_enough_memory(offset) do |address|
      @memory[address] = value
    end
  end

  private def read(offset)
    ensure_enough_memory(offset) do |address|
      @memory[address]
    end
  end

  private def current_instruction
    @memory[@ip]
  end

  private def current_opcode
    current_instruction % 100
  end

  private def current_modes
    current_instruction // 100 #/
  end

  private def mode(offset)
    modes = current_modes
    (offset - 1).times { modes //= 10 } #/
    modes % 10
  end

  private def address(offset)
    case mode(offset)
    when 0 then @memory[@ip + offset]       # position mode
    when 1 then @ip + offset                # immediate mode
    when 2 then @rb + @memory[@ip + offset] # relative mode
    else        raise "invalid mode"
    end
  end

  @[AlwaysInline]
  private def zero
    T.new(0)
  end

  @[AlwaysInline]
  private def one
    T.new(1)
  end

  @[AlwaysInline]
  private def three
    T.new(3)
  end
end

Questions:

  1. Does it look idiomatic?
  2. I wanted to define constants like ZERO = T.new(0), but the compiler complained (undefined constant T), that’s why the inlined methods. Is there a better way to accomplish this?

I think it looks idiomatic and I don’t think there’s another way to do it.

Hey Ary!

Regarding the generic example, which are the valid places where you can refer to T?

I mistakenly thought of generics as pure templates, but I see require does execute the class-level code. So, for example, you can conditionally execute code (not method definitions), define constants, etc, and at that point no concrete type is being defined, so I understand the error I got.

I guess you can use T within class or instance methods, and top-level declaration of ivars types. Are there other valid places?

Ah, I know what made me think that generics had to be templates.

Coming from Ruby, I expect a top-level self to be a class object. In Crystal, I guess the analogous concept is that self is a type (?). So, I thought of generic types as useless per se, only used to generate concrete types.

But self in the body class of a generic type evaluates to the “abstract” generic type itself. That surprises me and I suspect it is telling me I do not fully understand the fine details of generics.

I smell a good taste in Advent of Code here :slight_smile:
For reference my solution https://github.com/schovi/advent-of-code/blob/master/cpu2.cr
Definitely not the best or most right, but it is mine and I like it. So just for inspiration and reference.

I am curious in solutions of others in Crystal :pray:

1 Like