I'm new to ML, but in other languages that use type inference, I have learned the habit of omitting the type of a thing whenever the inference on the right hand side is obvious to a human reader, and explicitly declaring the type of a thing whenever the inference is not obvious to a human. I like this convention, and would like to continue with it in my ML code.
I have the following example function declarations, that are equivalent:
fun hasFour [] = false
| hasFour (x::xs) = (x = 4) orelse hasFour xs
is equivalent to
val rec hasFour: int list -> bool =
fn [] => false
| (x::xs) => (x = 4) orelse hasFour xs
I like the latter form not only because it's easier for me to figure out what type the function is when I read it, but also because it explicitly declares the type of the function, and hence, if I screw something up in my implementation, there is no chance of accidentally declaring something that's syntactically valid but the wrong type, which is harder to debug later.
My question is: I want to use fun instead of val rec, because anonymous fn's can only take one parameter. So the latter technique is not syntactically valid for a function like int -> int -> bool. Is there a way to explicitly declare the type in fun? Or have I named all the preferred alternatives in this post, and should simply follow one of these patterns? The only way I could find to use fun with explicit type declaration was to add a type declaration to each parameter in the pattern, which is quite ugly and horrible, like so:
fun hasFour ([]:int list):bool = false
| hasFour (x::xs) = (x = 4) orelse hasFour xs
A colleague showed me some code following a pattern like this:
fun hasFour [] = false
| hasFour (x::xs) = (x = 4) orelse hasFour xs
val _ = op hasFour: int list -> bool
By declaring an unnamed variable and setting it to an instance of the function with a forced type, we effectively achieve the desired result, but the val _ must appear below the fully defined function, where it's less obvious to a human reader, unless you simply get used to this pattern and learn to expect it.
I asked a very similar question, Can I annotate the complete type of a fun declaration?, recently.
Your current solution would have been a nice answer to that.
You can have multiple curried arguments with multiple fn, e.g. like:
val rec member : ''a -> ''a list -> bool =
fn x => fn [] => false
| y::ys => x = y orelse member x ys
Or you can do as you currently do, or as matt suggests:
local
fun member _ [] = false
| member x (y::ys) = x = y orelse member x ys
in
val member = member : ''a -> ''a list -> bool
end
But the combination of using fun and having the complete type signature listed first is yet elusive.
For production-like code, the norm is to collect type signatures in a module signature. See ML for the Working Programmer, ch. 7: Signatures and abstraction, pp. 267-268. Although I imagine you'd want to use Ocaml then.
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 meet an error about subtyping.
For this code, List.map (fun ((String goal_feat):> Basic.t) -> goal_feat) (goal_feats_json:> Basic.t list).
I meet the following error in vscode:
This expression cannot be coerced to type
Yojson.Basic.t =
[ Assoc of (string * Yojson.Basic.t) list
| Bool of bool
| Float of float
| Int of int
| List of Yojson.Basic.t list
| Null
| String of string ];
it has type [< String of 'a ] -> 'b but is here used with type
[< Yojson.Basic.t ].
While compiling, I meet the following error.
Error: Syntax error: ')' expected.
If I change the code to List.map (fun ((String goal_feat): Basic.t) -> goal_feat) (goal_feats_json:> Basic.t list), which useq explicit type cast instead of subtyping, then the error disappeared. I can not understand what is the problem with my code when i use subtyping. Much appreciation to anyone who could give me some help.
First of all, most likely the answer that you're looking for is
let to_strings xs =
List.map (function `String x -> x | _ -> assert false) (xs :> t list)
The compiler is telling you that your function is handling only one case and you're passing it a list that may contain many other things, so there is a possibility for runtime error. So it is better to indicate to the compiler that you know that only the variants tagged with String are expected. This is what we did in the example above. Now our function has type [> Yojson.Basic.t].
Now back to your direct question. The syntax for coercion is (expr : typeexpr), however in the fun ((String goal_feat):> Basic.t) -> goal_feat snippet, String goal_feat is a pattern, and you cannot coerce a pattern, so we shall use parenthesized pattern here it to give it the right, more general, type1, e.g.,
let exp xs =
List.map (fun (`String x : t) -> x ) (xs :> t list)
This will tell the compiler that the parameter of your function shall belong to a wider type and immediately turn the error into warning 8,
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
(`Bool _|`Null|`Assoc _|`List _|`Float _|`Int _)
which says what I was saying in the first part of the post. It is usually a bad idea to leave warning 8 unattended, so I would suggest you to use the first solution, or, otherwise, find a way to prove to the compiler that your list doesn't have any other variants, e.g., you can use List.filter_map for that:
let collect_strings : t list -> [`String of string] list = fun xs ->
List.filter_map (function
| `String s -> Some (`String s)
| _ -> None) xs
And a more natural solution would be to return untagged strings (unless you really need the to be tagged, e.g., when you need to pass this list to a function that is polymorphic over [> t] (Besides, I am using t for Yojson.Basic.t to make the post shorter, but you should use the right name in your code). So here is the solution that will extract strings and make everyone happy (it will throw away values with other tags),
let collect_strings : t list -> string list = fun xs ->
List.filter_map (function
| `String s -> Some s
| _ -> None) xs
Note, that there is no need for type annotations here, and we can easily remove them to get the most general polymoprhic type:
let collect_strings xs =
List.filter_map (function
| `String s -> Some s
| _ -> None) xs
It will get the type
[> `String a] list -> 'a list
which means, a list of polymorphic variants with any tags, returning a list of objects that were tagged with the String tag.
1)It is not a limitation that coercion doesn't work on patterns, moreover it wouldn't make any sense to coerce a pattern. The coercion takes an expression with an existing type and upcasts (weakens) it to a supertype. A function parameter is not an expression, so there is nothing here to coerce. You can just annotate it with the type, e.g., fun (x : #t) -> x will say that our function expects values of type [< t] which is less general than the unannotated type 'a. To summarize, coercion is needed when you have a function that accepts an value that have a object or polymorphic variant type, and in you would like at some expressions to use it with a weakened (upcasted type) for example
type a = [`A]
type b = [`B]
type t = [a | b]
let f : t -> unit = fun _ -> ()
let example : a -> unit = fun x -> f (x :> t)
Here we have type t with two subtypes a and b. Our function f is accepting the base type t, but example is specific to a. In order to be able to use f on an object of type a we need an explicit type coercion to weaken (we lose the type information here) its type to t. Notice that, we do not change the type of x per se, so the following example still type checks:
let rec example : a -> unit = fun x -> f (x :> t); example x
I.e., we weakened the type of the argument to f but the variable x is still having the stronger type a, so we can still use it as a value of type a.
signature MAPPABLE = sig
type 'a mappable
val fmap : ('a -> 'b) -> 'a mappable -> 'b mappable
end
structure Option : MAPPABLE = struct
type 'a mappable = 'a option
fun fmap f v =
case v of
(SOME x) => SOME (f x)
| NONE => NONE;
end
structure List : MAPPABLE = struct
type 'a mappable = 'a list
fun fmap f v = map f v
end
fun incByFive x = x + 5
Really just to have a function that does stuff with fmap
fun mapToPair f x =
let val b = List.fmap f x
in (b,b)
end
val lst = mapToPair incByFive [1,2,3];
Suppose you want to make a generic implementation, that works for
all instances of MAPPABLE. The following does not work
fun mapToPair f x =
let val b = MAPPABLE.fmap f x
in (b,b)
end
It seems, that SML people point to Functors, if that needs to be done.
I tried implementing one, for a generic implementation of mapToPair
functor FMAPFUNCTOR (structure Q : MAPPABLE)
= struct
fun mapToPair f x =
let val b = Q.fmap f x
in (b,b)
end
end;
However, to use it with what in Haskell I'd call a functor
instance, I need to instantiate the functor (this reminds me of C++
templates for some reason)
structure MAPPABLE_TO_PAIR = FMAPFUNCTOR (structure Q = List);
val lstByPoly = MAPPABLE_TO_PAIR.mapToPair incByFive [1,2,3]
I would have to repeat that instantiation for every MAPPABLE
instance I want to use. I guess Haskell performs something like this,
too. Just implicitly.
Now I wonder if there is any shortcut / sugar for a better "user
experience" in SML that I have missed. Because really, it seems kind
of a lot of boilerplate in the way in order to use this in a code
base.
I guess Haskell performs something like this, too. Just implicitly.
The Haskell standard library defines and imports a ton of type class instances. Given a sufficient set of ML functors, their applications and their implicit compile-time imports, you could achieve something quite convenient.
But Haskell does let you automate type class instance declarations in ways that SML doesn't.
For example, instance Foo t => Bar t where ... is comparable to SML's higher-order functors, but in SML you explicitly have to generate a module that corresponds to Bar t for each concrete Foo t. Haskell also lets you derive instances syntactically.
OCaml had modular implicits from 2014 (example), but they mainly give you syntax sugar to refer to defined functor instances, rather than generate them.
I suspect that the reason ML module systems are still more explicit than Haskell's is because of things like overlapping instances.
Andreas Rossberg contributed 1ML in 2014 in which modules are first-class citizens. That means a function could take a module as an argument, e.g. like this:
;; Higher-kinded polymorphism
type MONAD (m : type => type) =
{
return 'a : a -> m a;
bind 'a 'b : m a -> (a -> m b) -> m b;
};
map 'a 'b (m : type => type) (M : MONAD m) (f : a -> b) mx =
M.bind mx (fun x => M.return (f x));
do map :
'a => 'b => (m : type => type) => (M : MONAD m) => (a -> b) -> m a -> m b;
This is still research-y in the sense that the compiler has "TOY" in its name, but it'd be an example of an ML (although not Standard ML) that does something comparably generic with modules.
I am writing an OCaml function that accepts a function type, such as (fun _ -> true) and a list. This is what I currently have:
let drop_until_boolean (x: 'a -> bool) lst =
match lst with
| x -> true
Currently that written statement does not work properly, as it always evaluates to true.
When I call drop_until_boolean (fun _ -> true) [] I want it to return true, and when I call drop_until_boolean (fun _ -> true) ["a"] I want it to return false.
Question Summary: How do I make a function such that drop_until_boolean (fun _ -> true) [] evaluates to true.
Another example: drop_until_boolean (fun s -> s.[0]='z') ["z"] evaluates to true and drop_until_boolean (fun s -> s.[0]='z') ["y"] evaluates to false.
I managed to figure out what I wanted to do, probably did a terrible job explaining it. This is what I wanted.
let drop_until_boolean (x: 'a -> bool) lst = if (x lst) then true else false
Your current function says the following in English:
Take a function, call it x, and a second value of any type. Examine the second value. In all cases, no matter what the value, return true.
The variable x that appears in your match is a new variable that is matched against the second argument. Since it's just a simple variable, it always matches successfully. It has no relationship to the first parameter (which happens to be named x also).
It shouldn't be surprising that this function always returns true.
I'm not at all sure what you want the function to do. The name suggests it will return some trailing portion of the list that you give it. But you seem to be saying that it should return a boolean.
Let's assume that you want to do something reasonably simple with the second argument. You say the second argument is a list. The most common structure for a simple list-processing function is like this:
let rec my_function list =
match list with
| [] ->
(* Handle case of empty list *)
| head :: rest ->
(* Handle case of non-empty list,
probably with recursive call *)
Maybe you could think about this general structure as a possible solution to your problem. I hope it is helpful.
Let's say I have a list of options:
let opts = [Some 1; None; Some 4]
I'd like to convert these into an option of list, such that:
If the list contains None, the result is None
Otherwise, the various ints are collected.
It's relatively straightforward to write this for this specific case (using Core and the Monad module):
let sequence foo =
let open Option in
let open Monad_infix in
List.fold ~init:(return []) ~f:(fun acc x ->
acc >>= fun acc' ->
x >>= fun x' ->
return (x' :: acc')
) foo;;
However, as the question title suggests, I'd really like to abstract over the type constructor rather than specialising to Option. Core seems to use a functor to give the effect of a higher kinded type, but I'm not clear how I can write the function to be abstracted over the module. In Scala, I'd use an implicit context bound to require the availability of some Monad[M[_]]. I'm expecting that there's no way of implicitly passing in the module, but how would I do it explicitly? In other words, can I write something approximating this:
let sequence (module M : Monad.S) foo =
let open M in
let open M.Monad_infix in
List.fold ~init:(return []) ~f:(fun acc x ->
acc >>= fun acc' ->
x >>= fun x' ->
return (x' :: acc')
) foo;;
Is this something that can be done with first class modules?
Edit: Okay, so it didn't actually occur to me to try using that specific code, and it appears it's closer to working than I'd anticipated! Seems the syntax is in fact valid, but I get this result:
Error: This expression has type 'a M.t but an expression was expected of type 'a M.t
The type constructor M.t would escape its scope
The first part of the error seems confusing, since they match, so I'm guessing the problem is with the second - Is the problem here that the return type doesn't seem to be determined? I suppose it's dependent on the module which is passed in - is this a problem? Is there a way to fix this implementation?
First, here is a self-contained version of your code (using the legacy
List.fold_left of the standard library) for people that don't have
Core under hand and still want to try to compile your example.
module type MonadSig = sig
type 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
val return : 'a -> 'a t
end
let sequence (module M : MonadSig) foo =
let open M in
let (>>=) = bind in
List.fold_left (fun acc x ->
acc >>= fun acc' ->
x >>= fun x' ->
return (x' :: acc')
) (return []) foo;;
The error message that you get means (the confusing first line can
be ignored) that the M.t definition is local to the M module, and
must not escape its scope, which it would do with what you're trying
to write.
This is because you are using first-class modules, that allow to
abstract on modules, but not to have dependent-looking types such as
the return type depends on the argument's module value, or at least
path (here M).
Consider this example:
module type Type = sig
type t
end
let identity (module T : Type) (x : T.t) = x
This is wrong. The error messages points on (x : T.t) and says:
Error: This pattern matches values of type T.t
but a pattern was expected which matches values of type T.t
The type constructor T.t would escape its scope
What you can do is abstract on the desired type before you abstract on the first-class module T, so that there is no escape anymore.
let identity (type a) (module T : Type with type t = a) (x : a) = x
This relies on the ability to explicitly abstract over the type variable a. Unfortunately, this feature has not been extended to abstraction over higher-kinded variables. You currently cannot write:
let sequence (type 'a m) (module M : MonadSig with 'a t = 'a m) (foo : 'a m list) =
...
The solution is to use a functor: instead of working at value level, you work at the module level, which has a richer kind language.
module MonadOps (M : MonadSig) = struct
open M
let (>>=) = bind
let sequence foo =
List.fold_left (fun acc x ->
acc >>= fun acc' ->
x >>= fun x' ->
return (x' :: acc')
) (return []) foo;;
end
Instead of having each monadic operation (sequence, map, etc.) abstract over the monad, you do a module-wide abstraction.