39
Introduction to ML Introduction to ML A Quasi-Functional A Quasi-Functional Language Language With Strong Typing With Strong Typing And Type Inference And Type Inference

Introduction to ML A Quasi-Functional Language With Strong Typing And Type Inference

  • View
    220

  • Download
    0

Embed Size (px)

Citation preview

Introduction to MLIntroduction to ML

A Quasi-Functional LanguageA Quasi-Functional Language

With Strong TypingWith Strong Typing

And Type InferenceAnd Type Inference

Example of Interactive Example of Interactive SessionSession

Conventional syntax:Conventional syntax: val x = 5; val x = 5; (*user input *)(*user input *)

>> val x = 5: intval x = 5: int (*system response*) (*system response*)

fun len lis = if (null lis) then 0 else 1 + len (tl lis);fun len lis = if (null lis) then 0 else 1 + len (tl lis);

>> val len = fn : ‘a list -> intval len = fn : ‘a list -> int

Type inference for local entitiesType inference for local entities x * x * x;x * x * x;

> > val it = 125: intval it = 125: int (* (* itit denotes last denotes last computation*)computation*)

Operations on listsOperations on lists Similar to LISP, with (hd, tl, ::) instead of (car, cdr, cons)Similar to LISP, with (hd, tl, ::) instead of (car, cdr, cons) - fun append (x, y) = if null (x) then y- fun append (x, y) = if null (x) then y else hd (x) :: append (tl (x), y);else hd (x) :: append (tl (x), y);

> > val append = fn: ‘a list * ‘a list -> ‘a listval append = fn: ‘a list * ‘a list -> ‘a list

(* a function that takes a pair of lists and yields a list *)(* a function that takes a pair of lists and yields a list *)

- fun upto (m, n) =- fun upto (m, n) = if m > n then [ ] else m :: upto (m+1, n);if m > n then [ ] else m :: upto (m+1, n);

>> val upto = fn : int * int -> int listval upto = fn : int * int -> int list

(* a function that takes two numbers and yields a list *)(* a function that takes two numbers and yields a list *)

PatternsPatterns

A simple mechanism to describe and name A simple mechanism to describe and name parts of a structureparts of a structure

fun prod [ ] = 1 fun prod [ ] = 1 (* if list is empty *)(* if list is empty *)

| prod (n::ns) = n * prod (ns); | prod (n::ns) = n * prod (ns); (* n is first element *)(* n is first element *)

A list can be described by its head and its A list can be described by its head and its tailtail

fun maxl [m] : int = mfun maxl [m] : int = m

| maxl (m::n::ns) if m > n then maxl (m ::ns) | maxl (m::n::ns) if m > n then maxl (m ::ns)

else maxl (n :: ns)else maxl (n :: ns)

Recursion and iterationRecursion and iteration

A function that returns the first i elements of a list:A function that returns the first i elements of a list:

fun take ([ ], I) = [ ]fun take ([ ], I) = [ ]

| take (x::xs, I) = if I > 0 then x::take (xs, I - 1) else [ ];| take (x::xs, I) = if I > 0 then x::take (xs, I - 1) else [ ];

Non-recursive version: introduce an accumulator argument:Non-recursive version: introduce an accumulator argument:

fun itake ([ ], _, taken) = taken (* _ is pattern for fun itake ([ ], _, taken) = taken (* _ is pattern for anything *)anything *)

| itake (x::xs, I, taken) = | itake (x::xs, I, taken) =

if I > 0 then itake (xs, I - 1, x::taken) if I > 0 then itake (xs, I - 1, x::taken)

else taken;else taken;

>> var rtake = fn: ‘a list * int * ‘a list -> ‘a list var rtake = fn: ‘a list * int * ‘a list -> ‘a list

A classic in functional form: A classic in functional form: quicksortquicksort

fun quick [ ] = [ ]fun quick [ ] = [ ] | quick [x:real] = [x]| quick [x:real] = [x] | quick (a::bs) =| quick (a::bs) = let fun partition (left, right, [ ]) = let fun partition (left, right, [ ]) = (quick left) @ (quick right)(quick left) @ (quick right) | partition (left, right, x::xs) = | partition (left, right, x::xs) = if x <= a then partition (x::left, right, xs)if x <= a then partition (x::left, right, xs) else partition (left, x:: right, xs)else partition (left, x:: right, xs) inin partition ([a], [ ], bs)partition ([a], [ ], bs)

end; end;

> > val quick : fn : real list -> real list;val quick : fn : real list -> real list;

A single formal is sufficientA single formal is sufficient

If function takes more than one argument, If function takes more than one argument, say that it takes a single composite one:say that it takes a single composite one:

fun exp (x, n) = if n = 0 then 1fun exp (x, n) = if n = 0 then 1

else x * exp (x, n-1);else x * exp (x, n-1);

> > val exp = fn : int * int -> intval exp = fn : int * int -> int

Single argument is a tuple (int, int)Single argument is a tuple (int, int) Can also define records with named Can also define records with named

componentscomponents

The type systemThe type system

Primitive types: bool, int, char, real, stringPrimitive types: bool, int, char, real, string Constructors: list, array, product (record), Constructors: list, array, product (record),

functionfunction an an expressionexpression has a corresponding has a corresponding type type

expressionexpression the interpreter builds the type expression for the interpreter builds the type expression for

each inputeach input type checking requires that type expression of type checking requires that type expression of

functions and their arguments match, and that functions and their arguments match, and that type expression of context match result of type expression of context match result of functionfunction

Boolean ValuesBoolean Values

Two valuesTwo valuestrue and falsetrue and falseThe usual set of logical operators etcThe usual set of logical operators etc

Integers (type int)Integers (type int)

Can notate in either decimal or hex Can notate in either decimal or hex (0xaa)(0xaa)

Negative sign uses ~Negative sign uses ~This is because unary minus is an operatorThis is because unary minus is an operatorBut ~ is part of the numberBut ~ is part of the numberMost languages don’t have negative literalsMost languages don’t have negative literalsWhich is an odd cornerWhich is an odd corner

All the usual arithmetic operatorsAll the usual arithmetic operators

Unsigned (type word)Unsigned (type word)

A distinct type from intA distinct type from intNotated as 123w or 0wxffNotated as 123w or 0wxffMust be able to tell type of literalMust be able to tell type of literal

From the literal itselfFrom the literal itselfWithout any contextWithout any context

Usual arithmetic operatorsUsual arithmetic operators(but not abs, unary minus)(but not abs, unary minus)

Floating-point Type (real)Floating-point Type (real)

Notated with either a decimal point and a Notated with either a decimal point and a fractional part or an exponent or bothfractional part or an exponent or both3.453.453E43E4

Usual arithmetic operatorsUsual arithmetic operatorsNote that usual arithmetic operators are Note that usual arithmetic operators are

overloaded, but must be able to tell type!overloaded, but must be able to tell type!No implicit conversions between numeric No implicit conversions between numeric

types, functions in library used instead.types, functions in library used instead.

String TypeString Type

String is a primitiveString is a primitiveNot a vector of charactersNot a vector of charactersUsual notation (like Ada or C)Usual notation (like Ada or C)Carret (^) is concatenationCarret (^) is concatenation

There is also a type char for single There is also a type char for single charschars

Function str makes char into a stringFunction str makes char into a stringNote everything is case sensitiveNote everything is case sensitiveAll keywords lower caseAll keywords lower case

Example of Simple ArithmeticExample of Simple Arithmetic

Zeller function for day of the weekZeller function for day of the weekvalval floor = Real.floor floor = Real.floor

valval real = Real.fromInt real = Real.fromIntvalval zc = zc = fnfn (d, m, y, c) => (d, m, y, c) => (floor (2.61 * real (m) - 0.2) + d + y + (floor (2.61 * real (m) - 0.2) + d + y + yy div 4 + c div 4 - 2 * c) mod 7; div 4 + c div 4 - 2 * c) mod 7;

Note that the following are equivalentNote that the following are equivalentvalval zc = fn … zc = fn …

funfun zc … zc …

Local VariablesLocal Variables

Zeller function rewritten (floor, real Zeller function rewritten (floor, real local)local)locallocal

val val floor = Real.floor floor = Real.floor valval real = Real.fromInt real = Real.fromIntinin valval zc = zc = fnfn (d, m, y, c) => (d, m, y, c) => (floor (2.61 * real (m) - 0.2) + d + y + y (floor (2.61 * real (m) - 0.2) + d + y + y div 4 + c div 4 - 2 * c) mod 7; div 4 + c div 4 - 2 * c) mod 7;endend; ;

Use of letUse of let

Local allows decls used in other declsLocal allows decls used in other declsLet allows decls used in expressionsLet allows decls used in expressions

(*(* Radix for non-negative nums Radix for non-negative nums *)*) valval recrec radix = radix = fnfn (n, base) => (n, base) => letlet valval b = size base b = size base valval digit = digit = fnfn n => str (String.sub (base, n)) n => str (String.sub (base, n)) valval radix' = radix' = fnfn (true, n) = digit n (true, n) = digit n | (false, n) => radix (n div b, base) ^ digit (n mod b) | (false, n) => radix (n div b, base) ^ digit (n mod b) inin radix' (n < b, n) radix' (n < b, n) endend; ;

Block structure and nestingBlock structure and nesting

Example of nesting:Example of nesting: funfun findroot (a, x, acc) = findroot (a, x, acc) =

(* standard Newton-Raphson *)(* standard Newton-Raphson *)

letlet val nextx = (a / x +x) / 2.0 (*next approximation val nextx = (a / x +x) / 2.0 (*next approximation *)*)

inin

if abs (x - nextx) < acc*x then nextxif abs (x - nextx) < acc*x then nextx

else findroot (a, nextx, accelse findroot (a, nextx, acc)) endend;;

(*tail recursion rather than iteration *)(*tail recursion rather than iteration *)

What is an ML ProgramWhat is an ML Program

So far we have seen functions and So far we have seen functions and expressions, but what is an ML expressions, but what is an ML program?program?

Answer, a bunch of definitions and an Answer, a bunch of definitions and an expression to evaluate, typically:expression to evaluate, typically: letlet

declarations declarations inin expression expression endend;;

FunctionalsFunctionals

fun exists pred [ ] = falsefun exists pred [ ] = false | exists pred (x::xs) = (pred x) orelse exists pred xs;| exists pred (x::xs) = (pred x) orelse exists pred xs;

(* pred is a predicate : a function that delivers a boolean *)(* pred is a predicate : a function that delivers a boolean *) >> val exists : fn : (‘a -> bool) -> ‘a list -> bool val exists : fn : (‘a -> bool) -> ‘a list -> bool

fun all pred [ ] = truefun all pred [ ] = true | all pred (x::xs) = (pred x) andalso all pred xs;| all pred (x::xs) = (pred x) andalso all pred xs;

>> val all : fn : (‘a -> bool) -> ‘a list -> bool val all : fn : (‘a -> bool) -> ‘a list -> bool

fun filter pred [ ] = [ ]fun filter pred [ ] = [ ] | filter (x::xs) =| filter (x::xs) = if pred x then x :: filter pred xsif pred x then x :: filter pred xs else filter pred xs;else filter pred xs; > > val filter = fn : (‘a -> bool) -> ‘a list –> ‘a listval filter = fn : (‘a -> bool) -> ‘a list –> ‘a list

Currying: partial bindingsCurrying: partial bindings

a b ca b c means means (a b) c:(a b) c: (a b) yields a function that is applied (a b) yields a function that is applied to cto c

fun app2 x y = if null x then yfun app2 x y = if null x then y

else (hd x) :: app2 (tl x) y;else (hd x) :: app2 (tl x) y;

>> val app2 = fn : ‘a list -> ‘a list -> a’listval app2 = fn : ‘a list -> ‘a list -> a’list

(*a function that given a list yields a function that takes a (*a function that given a list yields a function that takes a list and yields a list *)list and yields a list *)

val ap123 = app2 [1, 2, 3];val ap123 = app2 [1, 2, 3];

>> val ap123 = fn : int list -> int listval ap123 = fn : int list -> int list

ap123 [10, 20, 30];ap123 [10, 20, 30];

> > val it = [1, 2, 3, 10, 20, 30]val it = [1, 2, 3, 10, 20, 30]

Type inferenceType inference

fun incr x = x + 1;fun incr x = x + 1;

>> val incr = fn : int -> intval incr = fn : int -> int because of its appearance in (x+1), x must be because of its appearance in (x+1), x must be

integerinteger

fun add2 x = incr (incr x);fun add2 x = incr (incr x);

>> val add2 = fn : int -> intval add2 = fn : int -> int incr returns an integer, so add2 does as wellincr returns an integer, so add2 does as well x is argument of incr, so must be integerx is argument of incr, so must be integer

val wrong = 10.5 + incr 7;val wrong = 10.5 + incr 7;

> > Error: operator and operand don’t agreeError: operator and operand don’t agree

PolymorphismPolymorphism

fun len x = if null x then 0 fun len x = if null x then 0

else 1 + len (tl x);else 1 + len (tl x); Works for any kind of list. What is its type Works for any kind of list. What is its type

expression?expression?

>> val len : fn = ‘a list -> intval len : fn = ‘a list -> int

‘‘a denotes a type variable.a denotes a type variable. Implicit universal quantification: Implicit universal quantification:

for any a, len applies to a list of a’s.for any a, len applies to a list of a’s.

fun copy lis = if null lis then nilfun copy lis = if null lis then nil

else hd (lis) :: copy (tl lis);else hd (lis) :: copy (tl lis);

Type inference and Type inference and unificationunification

Type expressions are built by solving a set of equationsType expressions are built by solving a set of equations

fun reduce f lis init = if null lis then initfun reduce f lis init = if null lis then init

else f ((hd lis), reduce f (tl lis) init))else f ((hd lis), reduce f (tl lis) init)) null : ‘a list -> boolnull : ‘a list -> bool hd : ‘b list -> ‘bhd : ‘b list -> ‘b tl : ‘c list -> ‘c listtl : ‘c list -> ‘c list apply : (‘d -> e’) * ‘d -> ‘e (*function application*)apply : (‘d -> e’) * ‘d -> ‘e (*function application*) let let bb be the type of init. Let be the type of init. Let aa be the element type of lis. be the element type of lis. Then f takes an Then f takes an aa and a and a bb and returns a and returns a bb..

> > val reduce = fn : (‘a -> ‘b -> ‘b) -> (‘a list) -> ‘b -> val reduce = fn : (‘a -> ‘b -> ‘b) -> (‘a list) -> ‘b -> ‘b ‘b

Unification algorithmUnification algorithm A type variable can be unified with another variableA type variable can be unified with another variable ‘‘a unifies with ‘ba unifies with ‘b => ‘a and ‘b are => ‘a and ‘b are the samethe same A type variable can be unified with a constantA type variable can be unified with a constant ‘ ‘a unifies with int => all occurences of ‘a unifies with int => all occurences of ‘aa mean mean intint A type variable can be unified with an expressionA type variable can be unified with an expression ‘ ‘a unifies with ‘b lista unifies with ‘b list ‘‘a does not unify with ‘a lista does not unify with ‘a list A constant can be unified with itself A constant can be unified with itself int is intint is int An expression can be unified with another expression if the An expression can be unified with another expression if the

constructors are identical and if the arguments can be constructors are identical and if the arguments can be unified (unified (recursiverecursive):):

(int -> int) list(int -> int) list unifies with unifies with ‘a list‘a list, ‘a is a function on , ‘a is a function on integersintegers

Type system does not handle Type system does not handle overloading welloverloading well

fun plus x y = x + y;fun plus x y = x + y;

operator is overloaded, cannot be resolved from operator is overloaded, cannot be resolved from context (error in some versions, defaults to int in context (error in some versions, defaults to int in others)others)

The type system can decide that a function takes The type system can decide that a function takes ‘a but not that it takes an int or real‘a but not that it takes an int or real

Can use explicit typing to select interpretation:Can use explicit typing to select interpretation: fun mix (x, y ,z) = x * y + z:real;fun mix (x, y ,z) = x * y + z:real;

> > mix : (real * real * real) -> realmix : (real * real * real) -> real

Parametric polymorphism vs. Parametric polymorphism vs. genericsgenerics

A function whose type expression has type A function whose type expression has type variables applies to an infinite set of types.variables applies to an infinite set of types.

Equality of type expressions means structural Equality of type expressions means structural equivalence.equivalence.

All applications of a polymorphic function use the All applications of a polymorphic function use the same body: no need to instantiate.same body: no need to instantiate.

let val ints = [1, 2, 3]; let val ints = [1, 2, 3];

val strs = [“this”, “that”];val strs = [“this”, “that”];

inin

len ints + len strs (* int list -> int, string list -> int *)len ints + len strs (* int list -> int, string list -> int *)

end;end;

> > val it = 5: intval it = 5: int

User-defined types and User-defined types and inferenceinference

A user-defined type introduces constructors:A user-defined type introduces constructors: datatypedatatype tree = leaf tree = leaf ofof int | node int | node ofof tree * tree tree * tree

leaf and node are type constructorsleaf and node are type constructors

can define functions by pattern:can define functions by pattern:

funfun sum (leaf (t)) = t sum (leaf (t)) = t

| sum (node (t1, t2)) = sum t1 + sum t2;| sum (node (t1, t2)) = sum t1 + sum t2;

> > val sum = fn : tree -> intval sum = fn : tree -> int

Parameterized datatypesParameterized datatypes

funfun flatten (leaf (t)) = [t] flatten (leaf (t)) = [t]

| flatten (node (t1, t2)) = flatten (t1) @ flatten | flatten (node (t1, t2)) = flatten (t1) @ flatten (t2);(t2);

> > flatten: tree -> int listflatten: tree -> int list

datatypedatatype ‘a gentree = leaf ‘a gentree = leaf ofof ‘a ‘a

| node | node ofof ‘a gentree * ‘a gentree; ‘a gentree * ‘a gentree;

val names = node (leaf (“this”), leaf (“that”));val names = node (leaf (“this”), leaf (“that”));

Here names is of type string gentreeHere names is of type string gentree

Programming in the large in Programming in the large in MLML

Need mechanisms forNeed mechanisms for ModularizationModularization Information hidingInformation hiding Parametrization of interfacesParametrization of interfaces

While retaining type inferenceWhile retaining type inference ModulesModules: like packages / namespaces: like packages / namespaces SignaturesSignatures: like package specifications : like package specifications

/Java interfaces/Java interfaces FunctorsFunctors: like generics with formal : like generics with formal

packagespackages

StructuresStructuresstructurestructure Complex = Complex =structstruct typetype t = real * real; t = real * real; valval zero = (0.0, 0.0):t; (*qualification may be needed *) zero = (0.0, 0.0):t; (*qualification may be needed *) valval i = (0.0, 1.0); i = (0.0, 1.0); funfun sum ((x, y), (x1, y1)) = (x+x1, y+y1):t; sum ((x, y), (x1, y1)) = (x+x1, y+y1):t; funfun prod ((x, y), (x1, y1)) = (x*x1 – y*y1, x*y1 + y*x1):t; prod ((x, y), (x1, y1)) = (x*x1 – y*y1, x*y1 + y*x1):t; funfun inv ((x, y)) = inv ((x, y)) = letlet

val den = x*x + y+y val den = x*x + y+y inin (x / den, ~ y / den): t(x / den, ~ y / den): t endend; ; funfun quo (z, z1) = prod (z, inv (z1)):t; quo (z, z1) = prod (z, inv (z1)):t;endend;;

Using a structureUsing a structure

use (“complex.ml”);use (“complex.ml”);> signature Complex> signature Complex : :

Complex.prod (Complex.i, Complex.i);Complex.prod (Complex.i, Complex.i); > val it = (~1.0, 0.0);> val it = (~1.0, 0.0); val pi4 = (0.707, 0.707);val pi4 = (0.707, 0.707);> val pi4 … real * real> val pi4 … real * real structural equivalencestructural equivalence

Complex.prod (pi4, pi4);Complex.prod (pi4, pi4);> val it = … : Complex.t> val it = … : Complex.t;;

SignaturesSignatures Interface description without implementationInterface description without implementation:: signature CMPLX =signature CMPLX = sigsig type ttype t val zero : tval zero : t val i : tval i : t val sum : t * t -> tval sum : t * t -> t val diff : t * t -> tval diff : t * t -> t val prod : t * t -> tval prod : t * t -> t val inv : t -> tval inv : t -> t val quo : t * t -> tval quo : t * t -> t endend

Multiple implementationsMultiple implementations

structure complex1 : CMPLX =structure complex1 : CMPLX =structstruct type t = real*real; type t = real*real; (* cartesian representation *)(* cartesian representation *) val zero = (0.0, 0.0);val zero = (0.0, 0.0); val i = (0.0, 1.0);val i = (0.0, 1.0);……Structure ComplexPolar: CMPLX =Structure ComplexPolar: CMPLX =StructStruct type t = real*real type t = real*real (*polar representation*)(*polar representation*) val zero = (0.0, 0.0);val zero = (0.0, 0.0); val pi = 3.141592;val pi = 3.141592; val i := (0.0, pi / 2.0);val i := (0.0, pi / 2.0);

Information HidingInformation Hiding

Only signature should be visibleOnly signature should be visible Declare structure to be opaque:Declare structure to be opaque:

structure polar :> CMPLX = ….structure polar :> CMPLX = ….

(Structure can be opaque or transparent (Structure can be opaque or transparent depending on context).depending on context).

Can export explicit constructors and equality for Can export explicit constructors and equality for type. Otherwise, equivalent to limited private type. Otherwise, equivalent to limited private types in Ada.types in Ada.

Can declare as eqtype to export equalityCan declare as eqtype to export equality

FunctorsFunctors Structures and signatures are not first-class objects.Structures and signatures are not first-class objects. A program (structure) can be parametrized by a signatureA program (structure) can be parametrized by a signature

functor testComplex (C : CMPLX) =functor testComplex (C : CMPLX) = structstruct open C; open C; (*equivalent to use clause*)(*equivalent to use clause*) fun FFT..fun FFT.. end;end;

structure testPolar = testComplex (Complexpolar);structure testPolar = testComplex (Complexpolar);

(* equivalent to instantiation with a package *)(* equivalent to instantiation with a package *)

Imperative Programming in Imperative Programming in MLML

A real language needs operations with side effects, state, A real language needs operations with side effects, state, and variablesand variables

Need to retain type inferenceNeed to retain type inference

- val p = ref 5;- val p = ref 5;

val p = ref 5 : int ref ; val p = ref 5 : int ref ; (* ref is a type constructor*)(* ref is a type constructor*)

- !p * 2; - !p * 2; (* dereference operation *)(* dereference operation *)

val it = 10: int;val it = 10: int;

- p := !p * 3;- p := !p * 3;

val it = ( ) : unit val it = ( ) : unit (* assignment has no value *)(* assignment has no value *)

References are equality types (pointer equality)References are equality types (pointer equality)

The libraryThe library

Lists structure: useful Operations on Lists structure: useful Operations on ListsLists

Listpairs structure, operates on 2 Listpairs structure, operates on 2 lists,e.g.lists,e.g.ListPair.unzip : ('a * 'b) list -> 'a list * 'b ListPair.unzip : ('a * 'b) list -> 'a list * 'b

list ListPair.zip : 'a list * 'b list -> ('a * 'b) list ListPair.zip : 'a list * 'b list -> ('a * 'b) list list

Vector operations (allow indexing)Vector operations (allow indexing)

Lazy EvaluationLazy Evaluation

Delay evaluation till value neededDelay evaluation till value needed Some languages do this by defaultSome languages do this by default

(Haskel and Miranda are examples) (Haskel and Miranda are examples) Advantage is avoiding evaluating stuff that is never Advantage is avoiding evaluating stuff that is never

needed, and aids correctnessneeded, and aids correctness funfun take x y = y take x y = y Are the following equivalent?Are the following equivalent?

expr1expr1 Take expr2 expr1Take expr2 expr1

No, because expr2 might not terminateNo, because expr2 might not terminate ML is strict (but laziness can be simulated using functional ML is strict (but laziness can be simulated using functional

forms that delay evaluation)forms that delay evaluation) There are forms that assist in this approach (e.g. delayed)There are forms that assist in this approach (e.g. delayed)

Abstract Data TypesAbstract Data Types abstypeabstype 'a set = null | ins 'a set = null | ins ofof 'a * 'a set 'a * 'a set

withwith valval emptyset = null emptyset = null valval addset = ins addset = ins funfun memberset (x, null) = false memberset (x, null) = false | memberset (x, ins (v, s)) = x = v | memberset (x, ins (v, s)) = x = v orelseorelse memberset (x, s) memberset (x, s) locallocal funfun subset (null, _) = true subset (null, _) = true | subset (ins (x, s1), s2) = | subset (ins (x, s1), s2) = memberset (x, s1) memberset (x, s1) andalsoandalso subset (s1, s2) subset (s1, s2) inin funfun equalset (s1, s2) = subset (s1, s2) equalset (s1, s2) = subset (s1, s2) andalsoandalso subset (s2, subset (s2, s1) s1) endend endend;;