Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

datatype Nt = int | string in ML

Tags:

sml

ml

When I only have datatype Nt = int | string, sml doesn't complain. But when I also have val n = 6 : Nt, ml doesn't accept 6 as an Nt. Why is this? I do know that, normally there should be data constructers before int and string, but here I'm having that to define functions that can take either int or string.

like image 680
rem Avatar asked Dec 05 '22 19:12

rem


2 Answers

You are misinterpreting the code. To be clear, you cannot define datatypes without constructors. But ML has different name spaces for types and values. The occurrences of int and string in your example are value identifiers. As such, they just define new nullary constructors, and have absolutely zero to do with the types of the same name. You can now define val n = int : Nt. It is as if you had written datatype Nt = foo | bar.

like image 185
Andreas Rossberg Avatar answered Feb 12 '23 23:02

Andreas Rossberg


Having a function that can either take an int or a string can be interpreted in two ways. You could mean that you want a function that could take anything and do something general with it – that would be a polymorphic function. E.g.

fun id x = x

can take both ints and strings and return them, but not do much with their content in specific. If you want a function that can take either an int or a string and do something different with them, depending on which input you have, you could use a union type, e.g.

datatype Nt = Int of int      (* constructor has the type int -> Nt *)
            | Str of string   (* constructor has the type string -> Nt *)

val sample_1 = Int 42
val sample_2 = Str "Hello"

Here, Int and Str are value constructors that work like functions in that they take a value of type int/string, respectively, as argument and return a value of the union type Nt. I've named them something other than int and string to signify that the value constructors are different from the types int and string. If they did not take an argument, their only use would be to distinguish one from the other (in which case they would be isomorphic to true/false).

A function that takes such a value as input would have to match against the pattern constructors of the same names. Here are some functions that would take this union type as argument:

fun isAnInt (Int i) = true
  | isAnInt (Str s) = false

fun intVal (Int i) = i
  | intVal (Str i) = 0

fun strVal (Int i) = Int.toString i
  | strVal (Str s) = s

fun sumNt [] = 0
  | sumNt (x::xs) = intVal x + sumNt xs

fun concatNt [] = ""
  | concatNt (x::xs) = strVal x ^ concatNt xs

And here these functions are being tested:

val test_isAnInt_1 = isAnInt sample_1 = true
val test_isAnInt_2 = isAnInt sample_2 = false

val test_intVal_1 = intVal sample_1 = 42
val test_intVal_2 = intVal sample_2 = 0

val test_strVal_1 = strVal sample_1 = "42"
val test_strVal_2 = strVal sample_2 = "Hello"

val test_sumNt_1 = sumNt [] = 0
val test_sumNt_2 = sumNt [sample_1, sample_1, sample_2, sample_1] = 126
val test_sumNt_3 = sumNt [sample_2, sample_2, sample_2] = 0

val test_concatNt_1 = concatNt [] = ""
val test_concatNt_2 = concatNt [sample_1, sample_1, sample_1] = "424242"
val test_concatNt_3 = concatNt [sample_1, sample_2, sample_1] = "42Hello42"
like image 29
sshine Avatar answered Feb 13 '23 00:02

sshine