After writing this piece of code
module type TS = sig
type +'a t
end
module T : TS = struct
type 'a t = {info : 'a list}
end
I realised I needed info to be mutable.
I wrote, then :
module type TS = sig
type +'a t
end
module T : TS = struct
type 'a t = {mutable info : 'a list}
end
But, surprise,
Type declarations do not match:
type 'a t = { mutable info : 'a list; }
is not included in
type +'a t
Their variances do not agree.
Oh, I remember hearing about variance. It was something about covariance and contravariance. I'm a brave person, I'll find about my problem alone!
I found these two interesting articles (here and here) and I understood!
I can write
module type TS = sig
type (-'a, +'b) t
end
module T : TS = struct
type ('a, 'b) t = 'a -> 'b
end
But then I wondered. How come that mutable datatypes are invariant and not just covariant?
I mean, I understand that an 'A list can be considered as a subtype of an ('A | 'B) list because my list can't change. Same thing for a function, if I have a function of type 'A | 'B -> 'C it can be considered as a subtype of a function of type 'A -> 'C | 'D because if my function can handle 'A and 'B's it can handle only 'A's and if I only return 'C's I can for sure expect 'C or 'D's (but I'll only get 'C's).
But for an array? If I have an 'A array I can't consider it as a an ('A | 'B) array because if I modify an element in the array putting a 'B then my array type is wrong because it truly is an ('A | 'B) array and not an 'A array anymore. But what about a ('A | 'B) array as an 'A array. Yes, it would be strange because my array can contain 'B but strangely I thought it was the same as a function. Maybe, in the end, I didn't understand everything but I wanted to put my thoughts on it here because it took me long to understand it.
TL;DR :
persistent : +'a
functions : -'a
mutable : invariant ('a) ? Why can't I force it to be -'a ?
I think that the easiest explanation is that a mutable value has two intrinsic operations: getter and setter, that are expressed using field access and field set syntaxes:
type 'a t = {mutable data : 'a}
let x = {data = 42}
(* getter *)
x.data
(* setter *)
x.data <- 56
Getter has a type 'a t -> 'a, where 'a type variable occurs on the right-hand side (so it imposes a covariance constraint), and the setter has type 'a t -> 'a -> unit where the type variable occurs to the left of the arrow, that imposes a contravariant constraint. So, we have a type that is both covariant and contravariant, that means that type variable 'a is invariant.
Your type t basically allows two operations: getting and setting. Informally, getting has type 'a t -> 'a list and setting has type 'a t -> 'a list -> unit. Combined, 'a occurs both in a positive and in a negative position.
[EDIT: The following is a (hopefully) clearer version of what I wrote in the first place. I consider it superior, so I deleted the previous version.]
I will try to make it more explicit. Suppose sub is a proper subtype of super and witness is some value of type super which is not a value of type sub. Now let f : sub -> unit be some function which fails on the value witness. Type safety is there to ensure that witness is never passed to f. I will show by example that type safety fails if one is allowed to either treat sub t as a subtype of super t, or the other way around.
let v_super = ({ info = [witness]; } : super t) in
let v_sub = ( v_super : sub t ) in (* Suppose this was allowed. *)
List.map f v_sub.info (* Equivalent to f witness. Woops. *)
So treating super t as a subtype of sub t cannot be allowed. This shows covariance, which you already knew. Now for contravariance.
let v_sub = ({ info = []; } : sub t) in
let v_super = ( v_sub : super t ) in (* Suppose this was allowed. *)
v_super.info <- [witness];
(* As v_sub and v_super are the same thing,
we have v_sub.info=[witness] once more. *)
List.map f v_sub.info (* Woops again. *)
So, treating sub t as a subtype of super t cannot be allowed either, showing contravariance. Together, 'a t is invariant.
Related
I'm learning SML and trying to make a datatype, called mySet, that can be any list of ints or reals, but with no duplicates and in sequential order. So far, I've made the datatype and some functions that do what I need to a list and then return it within that datatype which work fine. But I realized that the constructor for the datatype could also be used instead which completely bypasses the requirements. For what I need, I can just use the function, but I'd really like to know if there's any way I can patch up that problem? If a list doesn't follow the requirements, most of my functions for the datatype wouldn't work right.
datatype 'a set = Set of 'a list | Empty;
(* takes (item, list) and removes any copies of item from list *)
fun cleanList(a, []) = []
|cleanList(a, b::rest) =
if b = a then cleanList(a, rest)
else
b::cleanList(a, rest);
(*uses cleanList to make a list with all the items, no copies*)
fun removeDup([]) = []
| removeDup(a::rest) =
let
val cleanRest = cleanList(a, rest);
in
a::removeDup(cleanRest)
end;
(*uses above 2 functions, then puts the list in order *)
fun makeSet([]) = Empty
|makeSet(inputList) =
let
val cleanList = removeDup(inputList)
val sortedList = ListMergeSort.sort (fn(x,y) => x > y) cleanList;
in
Set(sortedList)
end;
val testList = [27, 81, 27, 3, 4, 5, 4, 27, 81, 3, 3, 7];
makeSet(testList); (* returns Set [3,4,5,7,27,81] *)
Set([1,1,1,1,1,1]); (*Set [1,1,1,1,1,1] which I don't want to allow *)
I realized that the constructor for the datatype could also be used instead which completely bypasses the requirements. For what I need, I can just use the function, but I'd really like to know if there's any way I can patch up that problem?
There is! Your basic constructor will break your data type's invariants, so you want to hide it and only expose a smart constructor that fails deliberately on certain input and doesn't allow invalid states.
As molbdnilo says, this is called an abstract type because you hide the way it is implemented and expose it through its smart constructor interface, which has whatever behavior you want it to. You can also call it an opaque type.
What each method of achieving this have in common is that you have a local scope in which the datatype is declared, but where only the external interface of the smart constructor leaves. In the interest of exploring how few language features you need, I tried to simply write:
val (fmap, pure) =
let
datatype 'a maybe = Just of 'a | Nothing
fun fmap f Nothing = Nothing
| fmap f (Just x) = Just (f x)
fun pure x = Just x
in (fmap, pure)
end
But my SML compiler actually rejected this program:
! in (fmap, pure)
! ^^^^
! Type clash: expression of type
! ('a -> 'b) -> 'a maybe -> 'b maybe
! cannot have type
! 'c
! because of a scope violation:
! the type constructor maybe is a parameter
! that is declared within the scope of 'c
So we need to whip out one of SML's language features designed specifically for this:
Update: #ruakh pointed out that I had forgotten about local.
Here is an example of the same thing using local-in-end:
local
datatype 'a maybe = Just of 'a | Nothing
in
fun fmap f Nothing = Nothing
| fmap f (Just x) = Just (f x)
fun pure x = Just x
end
The data type is shared between the two functions fmap and pure, but the definition is hidden from the external interface. You can have multiple local definitions including helper functions.
And the constructors are hidden:
> New type names: =maybe
val ('a, 'b) fmap = fn : ('a -> 'b) -> 'a maybe -> 'b maybe
val 'a pure = fn : 'a -> 'a maybe
let and local are discussed further in Difference between "local" and "let" in SML
Here is an example of the same thing using abstype:
abstype 'a maybe = Just of 'a | Nothing
with
fun fmap f Nothing = Nothing
| fmap f (Just x) = Just (f x)
fun pure x = Just x
end
And you can see how Just and Nothing are hidden:
> New type names: maybe
type 'a maybe = 'a maybe
val ('a, 'b) fmap = fn : ('a -> 'b) -> 'a maybe -> 'b maybe
val 'a pure = fn : 'a -> 'a maybe
But you can also use an opaque module. This StackOverflow answer covers precisely how the skeleton for a set functor works. Chapter 7 of ML for the Working Programmer covers how to define a module that takes a module as argument (a functor). This is an alternative to parametric polymorphism.
I understand that you can't do this, but want to understand precisely why.
module M : sig
type 'a t
val call : 'a t -> 'a option
end = struct
type 'a t
let state : ('a t -> 'a option) ref = ref (fun _ -> None)
let call : ('a t -> 'a option) = fun x -> !state x
end
Results in:
Error: Signature mismatch:
Modules do not match:
sig
type 'a t
val state : ('_a t -> '_a option) ref
val call : '_a t -> '_a option
end
is not included in
sig
type 'a t
val call : 'a t -> 'a option
end
Values do not match:
val call : '_a t -> '_a option
is not included in
val call : 'a t -> 'a option
Why are the abstract types not compatible here?
My gut tells me it has everything to do with early vs late binding, but I'm looking for an exact description of what the type system is doing here.
One way to look at it is that your field state can't have the polymorphic value you ascribe to it, because mutable values can't be polymorphic. References are at most monomorphic (as indicated by the '_a notation for the type variable).
If you just try to declare a similar reference in the toplevel, you'll see the same effect:
# let lfr: ('a list -> 'a option) ref = ref (fun x -> None);;
val lfr : ('_a list -> '_a option) ref = {contents = <fun>}
The type variable '_a indicates some single type that hasn't yet been determined.
The reason that references can't be polymorphic is that it's unsound. If you allow references to be generalized (polymorphic) it's easy to produce programs that go horribly wrong. (In practice this usually means a crash and core dump.)
The issue of soundness is discussed near the beginning of this paper: Jacques Garrigue, Relaxing the Value Restriction (which I refer to periodically when I forget how things work).
Update
What I think you want is "rank 2 polymorphism". I.e., you want a field whose type is polymorphic. You can actually get this in OCaml as long as you declare the type. The usual method is to use a record type:
# type lfrec = { mutable f: 'a. 'a list -> 'a option };;
type lfrec = { mutable f : 'a. 'a list -> 'a option; }
# let x = { f = fun x -> None };;
val x : lfrec = {f = <fun>}
# x.f ;;
- : 'a list -> 'a option = <fun>
The following code compiles for me using lfrec instead of a reference:
module M : sig
type 'a t
val call : 'a t -> 'a option
end = struct
type 'a t
type lfrec = { mutable f: 'a. 'a t -> 'a option }
let state: lfrec = { f = fun _ -> None }
let call : ('a t -> 'a option) = fun x -> state.f x
end
I was trying (just out of interest) to do this:
module type CAT = sig
type ('a, 'b) t
val id : ('a, 'a) t
val (#) : ('b, 'c) t -> ('a, 'b) t -> ('a, 'c) t
end
module Lst = struct
type ('a, 'b) t = 'a list constraint 'a = 'b
let id = []
let (#) = (#)
end
module L : CAT = Lst (* (error) *)
But I get:
Type declarations do not match:
type ('b, 'a) t = 'b list constraint 'a = 'b
is not included in
type ('a, 'b) t
Why isn't this safe? Everything that can see the concrete type can also see the constraint, so I don't think you could make something with a wrong type (e.g. call # with a (string, int) t argument).
Update: to those saying that my module doesn't implement the signature because it requires the types to be the same, consider that the following (which just wraps the lists in a List variant) is accepted despite having the same behaviour:
module Lst = struct
type ('a, 'b) t =
List : 'a list -> ('a, 'a) t
let id = List []
let (#) (type a) (type b) (type c) (a:(b, c) t) (b:(a, b) t) : (a, c) t =
match a, b with
| List a, List b -> List (a # b)
end
The example can be reduced to the type definition alone:
module type S =
sig
type ('a, 'b) t
end
module M =
struct
type ('a, 'b) t = 'a list constraint 'a = 'b
end
As Jeffrey already pointed out, M is not of type S, because it allows fewer applications of t: according to signature S, the type (int, string) t would be perfectly legal (it is well-formed), but M does not allow this type ((int, string) M.t is not a legal type, because it violates the explicit constraint).
All that is completely independent from the question whether the type is actually inhabited, i.e., whether you can construct values of the type. In your second example, the module makes the respective type well-formed, though it is uninhabited. Uninhabited types are legal, however, and sometimes even useful (see e.g. the concept of phantom types).
The type signature CAT is more general than the type of the Lst module. You need to put the type constraint on the abstract type too, i.e. type ('a, 'b) t constraint 'a = 'b.
This gives us the following:
module type CAT = sig
type ('a, 'b) t constraint 'a = 'b
val id : ('a, 'a) t
val (#) : ('b, 'c) t -> ('a, 'b) t -> ('a, 'c) t
end
which is printed as follows by the toplevel, showing a single type variable in the signature of (#):
module type CAT =
sig
type ('b, 'a) t constraint 'a = 'b
val id : ('a, 'a) t
val ( # ) : ('c, 'c) t -> ('c, 'c) t -> ('c, 'c) t
end
Error messages of the form "type x is not included in type y" refer to types or module types as specifications of sets of possible values, hence the use of the term "included".
In the case of a module implementation (Lst), we have a module type for it. Applying a signature (module type CAT) to a module is only allowed if that signature is as specialized (equal set) or more specialized (strict subset) than the original signature of the module.
One can write module X : sig val f : unit -> unit end = struct let f x = x end
but not module X : sig val f : 'a -> 'a end = struct let f () = () end. The latter gives the following error:
Error: Signature mismatch:
Modules do not match:
sig val f : unit -> unit end
is not included in
sig val f : 'a -> 'a end
Values do not match:
val f : unit -> unit
is not included in
val f : 'a -> 'a
This is different than placing type constraints on certain expressions, in which case the constraint is a mask to be applied (a set to intersect with) rather than a subset. For example it is fine to write let f : unit -> 'a = fun x -> x even though f's signature ends up being unit -> unit, a strict subset - or subtype - of unit -> 'a.
Your Lst module doesn't seem to me to have the type CAT. CAT allows the two types 'a and 'b to be independent. The Lst module requires them to be the same. If the L module were of type CAT then it should allow me to make something of type (string, int) t but it doesn't.
The error message is a little confusing, at least to me.
Suppose I have the beginning of the definition for a Stack like the following:
signature STACK = sig
type 'a stack
end;
structure Stack :> STACK = struct
type 'a stack = 'a list
end;
Clearly this doesn't work, because I can't cast a list to a stack:
- [5] : int Stack.stack;
stdIn:1.2-1.23 Error: expression doesn't match constraint [tycon mismatch]
expression: int list
constraint: int Stack.stack
in expression:
5 :: nil: int Stack.stack
Which means if I made a Stack.push or Stack.pop function, I couldn't pass in the int list, because it would expect a stack.
Wish I knew more about Standard ML to formulate a real question, I just know this doesn't work and I'm not sure how to approach signatures and structures.
When you're declare your structure, you're doing so using opaque signature matching (:>).
What opaque signature matching means is, the underlying type behind any types declared inside of the structure is hidden.
You can use transparent signature matching (:) if you don't wish for this to be the case.
Example with transparent signature matching:
structure Stack : STACK = struct
type 'a stack = 'a list
end;
Before you do this, consider the advantages of not doing so: If you use opaque signature matching, the underlying implementation is hidden. If you wish to change the underlying implementation (to a tree structure, for instance), you could do so knowing that nothing outside of the structure can use any other functions than the ones you provide.
You may wish to instead provide a toList and fromList function to perform the conversion:
(* in the signature *)
val toList : 'a stack -> 'a list
val fromList : 'a list -> 'a stack
(* in your structure *)
fun toList s = s
fun fromList s = s
If you then change your underlying implementation, you would only have to change these two functions, rather than having to make changes all over your program.
Creating functions toList and fromList as Sebastian suggests is very fine. Alternatively you can create a more restrictive interface that does not allow importing and exporting directly, but only creating by push, pop and empty:
signature STACK =
sig
type 'a stack
val push : 'a -> 'a stack -> 'a stack
val pop : 'a stack -> ('a * 'a stack)
val peek : 'a stack -> 'a
val empty : 'a stack
end
structure LStack :> STACK =
struct
type 'a stack = 'a list
fun push = ...
fun pop = ...
fun peek = ...
val empty = []
end
Write any Ocaml function whose type is ('a -> 'b) list -> 'a -> 'b list
('a -> 'b) list is the part that confuses me the most. I'm new to OCaml and having a hard time understanding how to write a function to get a specific datatype type.
# let int x = x+1;;
# let fcn = [int; int];;
So I'm passing a function a function and a variable. I'm going to take that variable an add it to each element of the list and return the list?
('a -> 'b) means a function which goes from type 'a to type 'b. Basically you need to make a function which takes a list of functions that take 'a and return 'b, plus a specific 'a value, and which returns a list of 'b values (probably by applying each function of the list of functions to the specific 'a value).
As this is homework, I will not provide you with a complete solution. But, as a hint, I would suggest that you take a look at this implementation of the familiar map function:
let rec map f = function
| [] -> []
| x :: xs -> f x :: map f xs
It has type ('a -> 'b) -> 'a list -> 'b list which means that it takes as its first argument a function that takes values of some type 'a to values of some type 'b, as its second argument a list of elements of type 'a, and that it produces a list of elements of type 'b. It proceeds by pattern matching on the argument list and, recursively applying the function (f) to every element x of the list.
Now have a look at the type of the function that you have to write? What does it tell you about the required behaviour of that function? Keeping the implementation of the map function in mind, how would you write your function?
('a -> 'b) list -> 'a -> 'b list
This means that your function has two parameters
A list of ('a -> 'b) which represents a function taking an element of type 'a as a parameter and returning an element of type 'b. As you can see, these types are abstract, so they could be of any types for instance (int -> int) or (int -> float) etc...
An elements of types 'a. Notice that this type must be the same as the parameter of your function.
So you'll build the resulting list with the element you give as a parameter.
Here is a little example:
let action l a =
let rec todo l res =
match l with
| [] -> res
| h :: t -> todo t res#[h a] in
todo l []
so here, any function of type int -> int will be accepted. The same thing goes for any other type as long as you don't mix them with other types.
let rec func f a = match f with (* ( 'a->'b ) list -> 'a -> 'b list *)
|[]->[]
|x::lr -> x a :: func lr a;;
that may help ! it works fine
1 - So as we know , ocaml create the type of our function line by line
2 - in this function we have two arguments f and a
3 - ( 'a->'b ) list : for f
4 - 'a : for a ! how ocaml did that ? listen !
5 - when we matched f with [ ] 'blank list' ocaml release that is a list (****)list but doesn't know what contains yet in the last line of the code he will do ok ? nice !
- here we are in the last line of the code and we have only f of type list -
6 - x :: lr means we sort the first element of the element that is matched before : f and we add a here ocaml gives a type for a and for the list elements which is matched : f as first elements ocaml gives them types from 'a to 'z so here we have ('a->'b) list for f and 'a for a
-here we have f of type : ('a->'b) list , and a of type : 'a
7 - the result of this function 'b list so it's up to you to answer in comment ! :D thank you