Is it possible to support higher-kinded types in Standard ML? - sml

I have read in this post that ML dialects do not allow type variables of non-ground kind. E.g. the last statement is not representable:
-- Haskell code
type Ground = Int
type FirstOrder a = Maybe a
type SecondOrder c = c Int -- ML do not allow :c
OCaml has support of higher-kinded only at the level of modules. There are some explanations (here and author's comment here) about which features of OCaml clash with higher-kinded types opportunity.
If I understood it correctly, the main problem is in the following facts:
OCaml does not follow a "freshness" restriction for type definitions: construct type can define both an alias (an the type will remain the same) and a new fresh type
type alias definition can be hidden
AFAIK, Standard ML has different constructs for type definition and aliases: type for aliases and datatype for new fresh types introduction.
Unfortunatelly, I do not know SML well enough -- is it possible to export type aliases with its definition hidden? And can someone please show me if there are any other SML features that still do not go well with an opportunity of higher-kinded types?
Probably there will be some problems with functors -- Could one be so kind to show a code example of it? I've heard several times about such cases but still have not found a complete example of it.

Yes, SML can express the equivalent of higher-kinded types through functors, and can also make them abstract. Useless example:
functor F (type 'a t) :> sig type 'a u end =
struct
type 'a u = ('a t) t
end
However, unlike OCaml, SML does not (officially) have higher-order functors, so per the standard, you can only express second-order type constructors this way.
FWIW, OCaml may use the same keyword for type aliases and generative types (type vs datatype in SML), but they are still distinguished syntactically, by their right-hand side. So that's no real difference to SML.In both languages, an abstract occurring in a signature can be implemented as either a type alias or a generative type. So the problem for type inference that Leo is alluding to exists equally in both. Haskell can get away without that problem because it does not have the same expressiveness regarding type abstraction (i.e., no "sealing" operator for modules).

Related

OCaml - Why is Either not a Monad

I'm new to OCaml, but have worked with Rust, Haskell, etc, and was very surprised when I was trying to implement bind on Either, and it doesn't appear that any of the general implementations have bind implemented.
JaneStreet's Base is missing it
What I assume is the standard library is missing it
bind was the first function I reached for... even before match, and the implementation seems quite easy:
let bind_either (m: ('e, 'a) Either.t) (f: 'a -> ('e, 'b) Either.t): ('e, 'b) Either.t =
match m with
| Right r -> f r
| Left l -> Left l
Am I missing something?
It is because we prefer a more specific Result.t, which has clear names for the ok state and for the exceptional state. And, in general, Either.t is not extremely popular amongst OCaml programmers as usually, a more specialized type could be used with the variant names that better communicate the domain-specific purpose of either branch. It is also worth mentioning that Either was introduced to the OCaml standard very recently, just 4.12, so it might become more popular.
As mentioned by #ivg, Either is relatively new to the standard library and generally one would prefer to use types that make more sense. For example, Result for error handling.
There is also another point of view, which also applies to Result. Monads act on types parameterised by one type.
In Haskell, this is much less obvious because it is possible to partially apply type constructors. Hence; bind:: (a -> b) -> Either a -> Either b allows you to go from Either a c to Either b c.
In trying to generalise the behaviour of a monad via parameterised modules (functors in the ML sense of the term), one would have to "trick" oneself into standardising, for example, the treatment of option (a type of arity 1) and either (or result) which are of arity 2.
There are several approaches. For example, expressing multiple interfaces to describe a monad. For example describing Monad2 and describing Monad in terms of Monad2 as is done in the Base library (https://ocaml.janestreet.com/ocaml-core/latest/doc/base/Base/Monad/index.html)
In Preface we used a rather different (and perhaps less generic) approach. We leave it to the user to set the left parameter of Either (via a functor) (and the right parameter for Result): https://github.com/xvw/preface/blob/master/lib/preface_stdlib/either.mli
However, we do not lose the ability to change the left-hand type of the calculation because Either also has a Bifunctor module that allows us to change the type of both parameters. The conversation is broadly described in this thread: https://discuss.ocaml.org/t/instance-modules-for-more-parametrized-types/5356/2

What's the difference between C++ "type deduction" and Haskell "type inference"?

In English semantics, does "type deduction" equal to "type inferring"?
I'm not sure if
this is just an idiom preference chosen by different language designers, or
there's computer science that tells a strict "type deduction" definition,
which is not "type inference"?
Thanks.
The C++ specification and working drafts use 'type deduce' extensively in reference to the type of expressions that don't have an type declaration as reference; for example this working draft on concepts uses it when talking about auto-declared variables and I remember lots of books using it when talking about templates way way back when I had to learn – and then subsequently forget most of – C++. Type inference, however, has its own Wikipedia page and is also the name of a significant field of study in programming-language theory. If you say type inference, people will immediately think of modern typed functional programming languages. You can even use it as a ruler to compare languages; some might say that their language X or their library Y is easier to type inference and is therefore better or friendlier.
I would say that type inference is the more specific, more precise, and more widely-used term. Type deduction as a phrase probably only holds cachet in the C++ community. The terms are close cousins, but the context they've been used in have given them dictional shades of color.
As mentioned, type inference is studied for years in theory. It is also a common feature provided by several programming languages, not only Haskell.
On the other hand, type deduction is the general name of several processes defined in the C++ specification, including return type deduction, placeholder type deduction, and the origin of them, template (type) argument deduction. It is about to identify the type implied by a term (within a C++ simple-type-specifier or template-argument) with yet unknown to-be-deduced type. It is similar to type inference, which is about typing -- determining the type of a term, but more restricted.
The major differences are:
Type deduction is the process to get the type of terms with some restricted form. Normally a C++ expression is typed without type deduction rules. In most cases the static type of an expression is determined by specific semantic rules forming the expression. Type inference can be used instead as the one truly general method of static typing.
As the term "type" has more restricted meaning in C++ compared to that in type theory, programming language theory and many contemporary programming languages, the domain of the result is also more restricted. Namely, type deduction must deduce a well-formed C++ type, not a higher-order type (which is not expressible as a C++ type).
These restrictions would not likely change without a massive redesigning of the type system of C++, so they can be essential.

What's the difference (if any) between Standard ML's module system and OCaml module system?

My question is if there is any difference between Standard ML's module system and OCaml module system? Has OCaml all the support of functors , ascriptions etc... that SML has?
There are some differences feature-wise, as well as semantically.
Features SML supports but not OCaml:
transparent signature ascription
module-level let
symmetric sharing constraints
syntactic sugar for functors over types and values
Features OCaml 4 has but not SML:
higher-order functors
recursive modules
local modules
nested signatures
modules as first-class values
general module sharing (sig with module A = M)
module type of
Several SML implementations provide some of these as extensions, however: e.g. higher-order functors (SML/NJ, Moscow ML, Alice ML), local and first-class modules (Moscow ML, Alice ML), module sharing (SML/NJ, Alice ML), nested signatures (Moscow ML, Alice ML), and recursive modules (Moscow ML).
Semantics-wise, the biggest difference is in the treatment of type equivalence, especially with respect to functors:
In SML, functors are generative, which means that applying the same functor twice to the same argument always yields fresh types.
In OCaml, functors are applicative, which means that applying the same functor twice to the exact same argument (plus additional syntactic restrictions) reproduces equivalent types. This semantics is more flexible, but can also break abstraction (see e.g. the examples we give in this paper, Section 8).
Edit: OCaml 4 added the ability to optionally make functors generative.
OCaml has a purely syntactic notion of signatures, which means that certain type equivalences cannot be expressed by the type system, and are silently dropped.
Edit: Consider this example:
module F (X : sig type t end) = struct type u = X.t -> unit type v = X.t end
module M = F (struct type t = int end : sig type t end)
The type of M is simply sig type u type v end and has thus lost any information about the relation between its types u and v, because that cannot generally be expressed in the surface syntax.
Another notable difference is that OCaml's module type system is undecidable (i.e, type checking may not terminate), due to its permission of abstract signatures, which SML does not allow.
As for semantics, a much better and elaborate answer is given by Andreas Rossberg above. However, concerning syntax this site might be what you are looking for.
There is also the abstype facility in SML which is like the datatype facility, except that it hides the structure of the datatype. OCaml relies on module abstraction to do all the hiding that is necessary. Notice that this site does not mention this facility in SML.

What does `B means?

In toplevel, i get the following output:
#`B
- : [> `B ] = `B
then what does `B mean ? Why do we need it ?
Sincerely!
An identifier prefixed with a backquote like `B is a constructor of a polymorphic variant type. It's similar to the constructor of an algebraic type:
type abc = A | B | C
However, you can use polymorphic variant values without declaring them, and in general they're much more flexible than the usual algebraic types. The tradeoff is that they're also quite a bit trickier to use.
One thing people use them for is as simple named values, like enum values in C. Or, more precisely, like atoms in Lisp. You can use ordinary algebraic types for this, but you need to carefully maintain your definitions of them and guard against duplication. With polymorphic variants, you don't need to do either of these. You can use them without declaring them, and the constructors aren't required to be unique (two different types can have the same constructor).
Polymorphic variant constructors can also take parameters, as algebraic constructors can. So you can also write (`B 77), a constructor with a single int parameter.
This is a pretty big topic--see the above linked section of the OCaml manual for more details.
It's a polymorphic variant. From the documentation:
Variants as presented in section 1.4 are a powerful tool to build data structures and algorithms. However they sometimes lack flexibility when used in modular programming. This is due to the fact every constructor reserves a name to be used with a unique type. One cannot use the same name in another type, or consider a value of some type to belong to some other type with more constructors.
With polymorphic variants, this original assumption is removed. That is, a variant tag does not belong to any type in particular, the type system will just check that it is an admissible value according to its use. You need not define a type before using a variant tag. A variant type will be inferred independently for each of its uses.

Explain ML type inference to a C++ programmer

How does ML perform the type inference in the following function definition:
let add a b = a + b
Is it like C++ templates where no type-checking is performed until the point of template instantiation after which if the type supports the necessary operations, the function works or else a compilation error is thrown ?
i.e. for example, the following function template
template <typename NumType>
NumType add(NumType a, NumType b) {
return a + b;
}
will work for
add<int>(23, 11);
but won't work for
add<ostream>(cout, fout);
Is what I am guessing is correct or ML type inference works differently?
PS: Sorry for my poor English; it's not my native language.
I suggest you have a look at this article: What is Hindley-Milner? (and why is it cool)
Here is the simplest example they use to explain type inference (it's not ML, but the idea is the same):
def foo(s: String) = s.length
// note: no explicit types
def bar(x, y) = foo(x) + y
Just looking at the definition of bar, we can easily see that its type must be (String, Int)=>Int. That's type inference in a nutshell. Read the whole article for more information and examples.
I'm not a C++ expert, but I think templates are something else that is closer to genericity/parametricity, which is something different.
I think trying to relate ML type inference to almost anything in C++ is more likely to lead to confusion than understanding. C++ just doesn't have anything that's much like type inference at all.
The only part of C++ that doesn't making typing explicit is templates, but (for the most part) they support generic programming. A C++ function template like you've given might apply equally to an unbounded set of types -- just for example, the code you have uses NumType as the template parameter, but would work with strings. A single program could instantiate your add to add two strings in one place, and two numbers in another place.
ML type inference isn't for generic programming. In C or C++, you explicitly define the type of a parameter, and then the compiler checks that everything you try to do with that parameter is allowed by that type. ML reverses that: it looks at the things you do with the parameter, and figures out what the type has to be for you to be able to do those things. If you've tried to do things that contradict each other, it'll tell you there is no type that can satisfy the constraints.
This would be pretty close to impossible in C or C++, largely because of all the implicit type conversions that are allowed. Just for example, if I have something like a + b in ML, it can immediately conclude that a and b must be ints -- but in C or C++, they could be almost any combination of integer or pointer or floating point types (with the constraint that they can't both be pointers) or used defined types that overload operator+ (e.g., std::string). In ML, finding types can be exponential in the worst case, but is almost always pretty fast. In C++, I'd estimate it being exponential much more often, and in a lot of cases would probably be under-constrained, so a given function could have any of a number of different signatures.
ML uses Hindley-Milner type inference. In this simple case all it has to do is look at the body of the function and see that it uses + with the arguments and returns that. Thus it can infer that the arguments must be the type of arguments that + accepts (i.e. ints) and the function returns the type that + returns (also int). Thus the inferred type of add is int -> int -> int.
Note that in SML (but not CAML) + is also defined for other types than int, but it will still infer int when there are multiple possibilities (i.e. the add function you defined can not be used to add two floats).
F# and ML are somewhat similar with regards to type inference, so you might find
Overview of type inference in F#
helpful.