Recursive type not unifying with itself - crystal-lang

The following code fails to compile with error:
type must be Tuple(Thing::Ish, Slice(UInt8)), not Tuple(Array(Array(Thing::Ish) | UInt8) | UInt8, Slice(UInt8))
These two types seem equivalent to me... and adding an .as(Ish) in the right place works... what am I missing? Why do these types not unify?
module Thing
alias Ish = UInt8 | Array(Ish)
def self.decode(bytes : Bytes) : {Ish, Bytes}
case bytes[0]
when 0x00..0x17
{bytes[0], bytes + 1}
when 0x18
MultiItemDecoder.new(0x80, ->(x: Bytes) { Thing.decode(x) }).decode(bytes)
else
raise "unknown"
end
end
class MultiItemDecoder(T)
def initialize(#base : UInt8, #item_decoder : Bytes -> {T, Bytes})
end
def decode(bytes): {Array(T), Bytes}
decode_some(bytes + 1, bytes[0])
end
def decode_some(bytes, n)
items = n.times.map do
item, bytes = #item_decoder.call(bytes)
item
end
{items.to_a, bytes}
end
end
end

This works:
module Thing
alias Ish = UInt8 | Array(Ish)
def self.decode(bytes : Bytes) : {Ish, Bytes}
case bytes[0]
when 0x00..0x17
{bytes[0], bytes + 1}
when 0x18
MultiItemDecoder.new(0x80, ->(x : Bytes) { Thing.decode(x) }).decode(bytes)
else
raise "unknown"
end
end
class MultiItemDecoder(T)
def initialize(#base : UInt8, #item_decoder : Bytes -> {T, Bytes})
end
def decode(bytes) : {Ish, Bytes}
decode_some(bytes + 1, bytes[0])
end
def decode_some(bytes, n)
items = n.times.map do
item, bytes = #item_decoder.call(bytes)
item.as(Ish)
end
{items.to_a.as(Ish), bytes}
end
end
end
Thing.decode(Bytes[1, 2, 3])
The thing is that Array(Array(Ish)) is not an Array(Ish), because for that it must be Array(Array(Ish) | UInt8) (note that it's an array of a union).
This all boils down to how things are represented in memory.
My advice is to avoid using recursive aliases. They are not intuitive and we might eventually remove them from the language.

Without seeing code to make this actually complile and get the error, self.decode wants to return a {Ish, Bytes}, which it does in when 0x00..0x17. But in 0x18 it is going to return an {Array(Ish), Bytes}. This will expand to {Array(UInt8 | Array(Ish)), Bytes} (recursively)

Related

How can I optimize my code so that I dont duplicate it

I'm trying to create a procedure that puts "-" between different dates and "0" if the is single digit, but i'm having a very hard time not duplicating my code.
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 1);
Put("-");
if Date.M <= 9 then
Put("0");
end if;
Put(Date.M, Width => 1);
Put("-");
if Date.D <= 9 then
Put("0");
end if;
Put(Date.D, Width => 1);
end put;
This is the best solution I came up with
An example of a nested procedure is:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
subtype Year_Num is Integer range 1_900 .. 2_040;
subtype Month_Num is Integer range 1 .. 12;
subtype Day_Num is Integer range 1 .. 31;
type Date_Type is record
Y : Year_Num;
M : Month_Num;
D : Day_Num;
end record;
procedure Put (Date : Date_Type) is
procedure zerofill (Val : in Integer) is
begin
Put ("-" & (if (Val < 10) then "0" else ""));
Put (Item => Val, Width => 0);
end zerofill;
begin
Put (Item => Date.Y, Width => 0);
zerofill (Date.M);
zerofill (Date.D);
end Put;
A_Date : Date_Type := (2022, 12, 8);
begin
Put (A_Date);
end Main;
The nested nature of this answer is because the zerofill procedure is defined within the put procedure.
Came to this solution, I didnt duplicate my code but I somehow feel like I made it more complicated
procedure Zero(item : in integer) is
begin
Put("-");
if item < 10 then
Put('0');
end if;
Put(Item,Width =>0);
end Zero;
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 0);
Zero(Date.M);
Zero(Date.D);
end put;

When I defined a function I got: error unexpected token: (

I have already seen the community of Crystal, but I couldn't find this problem.
def Twosum(a = [] of Int32, target = 0)
map = {} of Int32 : Int32
a.each_index do |i|
diff = target - a[i]
if map.key?(diff):
return [map.fetch(diff), i]
elsif
map[a[i]] = i
end
end
return 0`enter code here`
end
a = [1,4,6,3]
target = 7
puts(Twosum(a,target))
What's the problem?
Many problems. The one you ask about is: Crystal is very opinionated regarding case. Methods must start with lowercase; yours starts with uppercase, which Crystal does not like at all. Some other problems:
{} of Int32 : Int32 should use a fat arrow, not a colon: {} of Int32 => Int32
if statement does not end with a colon, it is not Python.
There is no method named key?; use has_key?
fetch (in current Crystal version) requires either a block or a second argument that specifies a default; if you do not need to specify a default behaviour (and you don't, since you check whether the key exists), you can just use [].
I'm really not sure what the code is intended to do, so I can't comment on the logic, semantics and style; but here's your code without syntax errors:
def twosum(a = [] of Int32, target = 0)
map = {} of Int32 => Int32
a.each_index do |i|
diff = target - a[i]
if map.has_key?(diff)
return [map[diff], i]
elsif
map[a[i]] = i
end
end
return 0
end
a = [1, 4, 6, 3]
target = 7
puts(twosum(a, target))

Crystal no overload matches 'Array(Type)#[]' with type (Int32 | Nil)

I am finding some weird behavior when using index.
#Defined in the class's initialize
#my_list = [] of Type
index = #my_list.index { |i| i.value == 2 } # => 0
#my_list[0] # => 2
#my_list[index] # => error
I get the error:
no overload matches 'Array(Type)#[]' with type (Int32 | Nil)
Not sure why index does not work, as index = 0.
EDIT:
More information. If I do this:
if index == nil
#Do something
#error => undefined method '>=' for Nil (compile-time type is (Int32 | Nil))
elsif index >= 0
#Do something else
end
Which I understand. It could be nil, but since I'm already checking to see if it is nil there shouldn't be a problem here. I'm thinking the previous code snippet is running into the same issue.
The problem is that Array#index is nilable; it may not find anything and return nil, hence it returns an Int32|Nil union.
The compiler eventually fails because Array#[] expects an Int32 argument, but we pass it an Int32|Nil. You must take care of this case (to avoid later bugs) by checking if the return value is truthy for example.
As stated by #julien-portalier :
The problem is that Array#index is nilable; it may not find anything in the array and return Nil, hence it returns an (Int32 | Nil) union.
You can use Object#not_nil! to get ride of the Nil type :
#my_list = [2, 4, 6, 8]
index = #my_list.index { |i| i == 6 }.not_nil! # => 2
# compile type of 'index' is Int32
#my_list[0] # => 2
#my_list[index] # => 6
It will make sure the type returned by Array#index is not Nil, if it is, an exception will be raised (see Nil#not_nil!)
And if you need to handle an index error without using exceptions, you can simply check if Array#index failed :
#my_list = [2, 4, 6, 8]
index = #my_list.index { |i| i == 6 } # => 2
# compile-time type of 'index' is (Int32 | Nil)
if index
# In this scope, compile-time type of 'index' is Int32
#my_list[0] # => 2
#my_list[index] # => 6
end
I'm just going to do this:
def get_index(val)
i = 0
while i < #my_list.size
if #my_list[i].value == val
return i
end
i += 1
end
return -1
end
This way only int values will be returned, no nils. It seems to work fine.
better way is to use times method it's more simple and more clear:
def get_index(val)
#my_list.size.times do |i|
return i if #my_list[i] == val
end
-1
end
UPD
or more simple
def get_index(val)
#my_list.index { |value| value == val } || -1
end

How to set a default value for a method argument

def my_method(options = {})
# ...
end
# => Syntax error in ./src/auto_harvest.cr:17: for empty hashes use '{} of KeyType => ValueType'
While this is valid Ruby it seems not to be in Crystal, my suspicion is that it is because of typing. How do I tell compiler I want to default to an empty hash?
Use a default argument (like in Ruby):
def my_method(x = 1, y = 2)
x + y
end
my_method x: 10, y: 20 #=> 30
my_method x: 10 #=> 12
my_method y: 20 #=> 21
Usage of hashes for default/named arguments is totally discouraged in Crystal
(edited to include the sample instead of linking to the docs)
It seems the error has all the information I needed, I need to specify the type for the key and values of the Hash.
def my_method(options = {} of Symbol => String)
# ...
end
It is quite clearly in the docs too.

Print Value to File

i was not able to print an a value (float) to a file with the OCaml lenguage.
How can i do?
If you know how, can you show me a little example?
Thank you advance and have a good day!
Printf.fprintf allows direction to an out_channel, in your case a file. Reasonably, you'd open the file for writing first, and pass around that channel.
Printf.fprintf (open_out "file.txt") "Float Value of %f" 1.0
If you want to print the textual representation of a float to a file, perhaps the simplest thing to do is:
output_string outf (string_of_float myfloat)
If you want to print the float to the console, you can use
print_string (string_of_float myfloat)
Of course, Printf.printf can also do that and more, so it is worth knowing it.
If you want to output the binary representation of a float, things are more complicated. Since a float value is represented as an IEEE 754 double, it is 8 bytes long which can be written in different orders depending on the platform. In the case of little-endian order, as is normal in X86, you can use the following:
let output_float_le otch fv =
let bits = ref (Int64.bits_of_float fv) in
for i = 0 to 7 do
let byte = Int64.to_int (Int64.logand !bits 0xffL) in
bits := Int64.shift_right_logical !bits 8;
output_byte otch byte
done
The float value so written can be read back with the following:
let input_float_le inch =
let bits = ref 0L in
for i = 0 to 7 do
let byte = input_byte inch in
bits := Int64.logor !bits (Int64.shift_left (Int64.of_int byte) (8 * i))
done;
Int64.float_of_bits !bits
This has the advantage of being a very compact way to exactly preserve floats in a file, that is, what you write will be read back exactly as it originally was. For example, I did this in the interactive top-level:
# let otch = open_out_bin "Desktop/foo.bin" ;;
val otch : out_channel = <abstr>
# output_float_le otch 0.5 ;;
- : unit = ()
# output_float_le otch 1.5 ;;
- : unit = ()
# output_float_le otch (1. /. 3.) ;;
- : unit = ()
# close_out otch ;;
- : unit = ()
# let inch = open_in_bin "Desktop/foo.bin" ;;
val inch : in_channel = <abstr>
# input_float_le inch ;;
- : float = 0.5
# input_float_le inch ;;
- : float = 1.5
# input_float_le inch ;;
- : float = 0.333333333333333315
# close_in inch ;;
- : unit = ()
and as you can see I got back exactly what I put in the file. The disadvantage of this form of writing floats to files is that the result is not human-readable (indeed, the file is binary by definition) and you lose the possibility to interoperate with other programs, like Excel for instance, which in general exchange data in human-readable textual form (CSV, XML, etc.).
Did you try printf?
let a = 4.0
printf "my float value: %f" a
Cf the doc inria and don't forget to open the module