How do I cast a Hash as an aliased type without getting this error message? - casting

I'm getting an interesting error when I use alias to define a Hash:
alias MyHash = Hash(Symbol, String | Int32)
hash = {:one => 2}.as(MyHash)
If I run this the output is not what I expected:
Error in ffile2.cr:2: can't cast Hash(Symbol, Int32) to Hash(Symbol, Int32 | String)
hash = {:one => 2}.as(MyHash)
^
You can't? Why not? Isn't that what defining a unity type is for?
Note that if I put the type in a method signature, everything is fine:
def foo(h : Hash(Symbol, String | Int32) )
puts h[:bar]?.nil?
end
foo( {:one => 2} )
Update: This works, but it seems more than a little silly:
alias MyHash = Hash(Symbol, String | Int32)
hash = {:one => 2.as(String | Int32)}.as(MyHash)

This has nothing to do with alias. If you replace the alias in your original example with the aliased type, it will fail as well.
.as cannot magically convert a Hash(Symbol, Int32) to a Hash(Symbol, String | Int32). These are different types and behave differently. This might not be obvious at first because when retrieving an entry, both types return a value with a type matching String | Int32. When storing an entry, they don't act the same: Hash(Symbol, String | Int32) can receive a value of type String | Int32, Hash(Symbol, Int32) cannot.
The language design terms for this are covariance and contravariance.
To avoid having to specify the expected value type in the literal, you can also use a generic conversion, like this for example:
literal = {:one => 2}
mapped = literal.map do |key, value|
{key, value.as(String | Int32)}
end.to_h
typeof(mapped) # => Hash(Symbol, Int32 | String)
This will take any hash with matching types and convert it to Hash(Symbol, Int32 | String).
Types in method arguments are not "fine" they just behave differently. They match underspecified types as long as you don't do anything wrong with them. Try to set a value to a string, it will still fail:
def foo(h : Hash(Symbol, String | Int32) )
puts h[:bar] = "bar" # error: no overload matches 'Hash(Symbol, Int32)#[]=' with types Symbol, String
end
These different semantics are obviously not very intuitive but it's the current state of a work in progress. There is an issue which also explains in more detail what this is about: https://github.com/crystal-lang/crystal/issues/3803

Related

Alert deprecated: Stdlib.String.set

The following code returns an error and says that the syntax is deprecated. What is the correct way to change a character in a string?
let hello = "Hello!" ;;
hello.[1] <- 'a' ;;
Alert deprecated: Stdlib.String.set
Use Bytes.set instead.
Error: This expression has type string but an expression was expected of type
bytes
Strings are immutable (or at least soon they will be), so you can't change their contents. You can, of course, create a copy of a string with the one character different, e.g.,
let with_nth_char m c =
String.mapi (fun i b -> if i = m then c else b)
and
# with_nth_char 1 'E' "hello";;
- : string = "hEllo"
But if you need to change characters in an array then you shouldn't use the string data type but instead rely on bytes which is a type for mutable strings. You can use Bytes.of_strings and Bytes.to_string to translate strings to bytes and vice verse.

sml suppress warnings for intentionally nonexhaustive pattern matching

Suppose I have a datatype like:
datatype location = Safe of string | Dangerous of string * int;
And in this hypothetical example, I want to write a function that will only ever be passed a Safe str and never a Dangerous(str, num):
fun send_kids (Safe address) = ...
Is there any way to suppress the warnings? Tell SML I know tis nonexhaustive?
stdIn:1.6-1.29 Warning: match nonexhaustive
Safe s => ...
Not directly. You "tell" SML by making it exhaustive with a failure case:
fun sendKinds (Safe address) = ...
| sendKinds _ = raise Fail "sendKinds"

Converting OCaml strings to format6

The following code does not compile:
let x = "hello" in
Printf.printf x
The error is:
Error: This expression has type string but an expression was expected of type
('a, out_channel, unit) format =
('a, out_channel, unit, unit, unit, unit) format6
1) Can someone give an explanation of the error message?
2) And why would a string cannot be passed to printf ?
The first argument to printf must be of type ('a, out_channel, unit) format not string. String literals can be automatically converted to an appropriate format type, but strings in general can't.
The reason for that is that the exact type of a format string depends on the contents of the string. For example the type of the expression printf "%d-%d" should be int -> int -> () while the type of printf "%s" should be string -> (). Clearly such type checking is impossible when the format string is not known at compile time.
In your case you can just do printf "%s" x.
As sepp2k points out, in OCaml printf formats have a distinct type, and are not simply strings. String literals are converted automatically to printf formats, but x is not a string literal. If you want to give a name to a format, you can convert it explicitly yourself:
> let x = format_of_string "hello" in Printf.printf x
hello- : unit = ()
You can also cause the implicit conversion by specifying a type for x, but the types of formats are so complicated this is quite painful:
# let (x: ('a,'b,'c,'d,'d,'a) format6) = "hello" in Printf.printf x;;
hello- : unit = ()
(I personally don't understand the format6 type.)

Idea working in types

Please see details in My previous question
1) cpf0.ml:
type string = char list
type name = string
type symbol =
| Symbol_name of name
2) problem.ml:
type symbol =
| Ident of Cpf0.string
In this problem.ml it has two definitions for type string, and surely it's giving me an error, but is it posible that I can make them have a same type? I need an idea.
module Str = struct type t = string end;;
module StrOrd = Ord.Make (Str);;
module StrSet = Set.Make (StrOrd);;
module StrMap = Map.Make (StrOrd);;
module SymbSet = Set.Make (SymbOrd);;
let rec ident_of_symbol = function
| Ident s -> s
let idents_of_symbols s =
SymbSet.fold (fun f s -> StrSet.add (ident_of_symbol f) s) s StrSet.empty;;
This expression has type Cpf0.string = char list but an expression was expected of type Util.StrSet.elt = string
You can use the name "string" for different types in different modules if you like, though (as Basile Starynkevitch points out) it's confusing. It would be better to pick a different name. If you really need to reuse the name, you can specify the module every time. If you don't specify a module, you'll get the predefined meaning (or the meaning from the innermost opened module).
It seems to me the problem in your quoted code is that this line:
module Str = struct type t = string end;;
doesn't specify a module name for string, so it refers to the predefined string. It seems possible you wanted to say:
module Str = struct type t = Cpf0.string end;;
It's hard to tell, however. There's not enough context for me to really understand what you're trying to do.
string is a predefined type in Ocaml (ie in the Pervasives module); it is e.g. the type of string literal constants like "this string". Use some other name (otherwise you, and any one reading your code, will be very confused)

OCaml: get value's type name

Is is possible to print value's name in OCaml, for example if I have
type my_type =
| MyType_First of int
| MyType_Second of string
and then do something like:
let my_value = MyType_First 0 in
print_string ("my_value is of type " ^ String.from_type my_value ^ ".\n";
can I get "my_value is of type MyType_First." ?
Thank you.
Monomorphic solution:
let from_type = function
| MyType_First _ -> "MyType_First"
| MyType_Second _ -> "MyType_Second"
Polymorphic solution: none. (AFAIK, lexical tokens corresponding to constructors are not recorded in the bytecode/binary, even when debugging flags are specified. The only thing one could do is to print the integer ‘identifier’ for the constructor, using some dark Obj.magic.)
What you want is a simpler form of generic print and is not available in OCaml as such, but some workarounds exist - e.g. deriving.