In regards to model driven software development.
As far as I know does the static semantic define the criteria for a model to be well-formed.
However I can't think of any real examples. So what are some real-world examples to help me better understand it?
Static Semantics can indeed be regarded as 'the criteria for a model to be well-formed'. What those criteria are completely depends on the (modelling) language it is describing.
Static Semantics is closely related to type-checking. With the phrase 'Static Semantics' we usually mean a formal description of the criteria that make a program/model well-formed, while a type-checking is an executable implementation of such a description, which can be used to validate actual models.
To provide an example, imagine a language with a syntactic constructs Expr.Plus = Expr "+" Expr. A possible rule (written informally) validating the well-formedness could be:
If (e1 is well-formed with type Num()) and (e2 is well-formed with type Num()), then Plus(e1, e2) is well-formed with type Num().
A more complex example of such a rule is: If (c is well-formed with type Bool()) and (e1 is well-formed with type T (where T is a type variable, not a concrete type)) and (e2 is well-formed with type T), then If(c, e1, e2) is well-typed with type T.
For a better introduction to this style of writing semantics (including formallish notation), see e.g. these slides (especially from 35)
However, static semantics can be much broader than only checking addition expressions. Imagine a modelling language that models the heating installation of a building. A rule in its semantics could specify that a model is well-formed only if all valve ends are connected (so no leaks are possible).
Moreover, static semantics differs from a grammar by the fact that it usually includes non-local/context-sensitive constraints/checks (e.g. name resolution).
Finally static semantics is different from dynamic semantics in the way that the latter describes how to compute a value from a (well-formed) model.
Related
Recently I have read a lot about formal verification and I am fascinated by the topic. Yet I cannot figure out the following:
Formal verification requires a formal specification so how can be abstract interpretation used on any source code in compilers, when there is no formal specification of the program?
Translation from a text in a foreign language that seems correct (and again does not seem to require a formal spec.)
If a program is represented by its control flow chart, then each
branch represents a program state (can be more than one - e.g. in a
loop a branch is traversed multiple times) and abstract intepretation
creates static semantics that approximates the set of this states.
Here an article about abstract interpretation as a formal verification technique: http://www.di.ens.fr/~cousot/publications.www/Cousot-NFM2012.pdf
One can do "concrete interpretation" by building an interpreter for a language, that computes results according to the language specification manual. (The fact that the language manuals are informal leaves the correctness of such an interpreter in permanent doubt).
Abstract interpretation only requires that one have a good understanding of the program semantics, and an idea about what is worthwhile abstracting. You can get this by taking the interpreter above, and replacing actual computation with an abstraction of the computation, e.g., you can decide to represent all integer values as "positive", "zero", "negative", or "unknown". You can still compute with this, now producing qualitative results. More importantly, you can compute with this without necessarily having actual program inputs (perhaps just abstracted values). I note that the abstract interpreter is also completely untrustworthy in a formal sense, because you are still computing using the informal reference manual as a guide as to what the language will do.
Now, by running such an abstract program, you may discover that it makes mistakes (e.g., dereferences a null value) with the variables controlling that null value not being "undefined". In that case, you can suggest there is a bug in the program; you may not be right, but this might produce useful results.
Nowhere does abstract interpretation in practice tell you what the program computes formally because you are still using an informal reference manual. If the manual were to become a formal document, and your abstract interpreter is derived from by provably correct steps, then we might agree that an abstract interpretation of the program is a kind of specification modulo the abstraction for what the program actually does.
Nowhere does abstract interpretation provide with access to a formal specification of the intention for the program. So you cannot use it by itself to prove the program "correct" with respect to the specification.
Abstract interpretation is a method used to statically gain understanding on how a software will behave. It never comes alone - there's always some goal which includes abstract interpretation as one of its components.
An example of such a goal is formal verification, in which a static technique (for example, abstract interpretation) is used to obtain information about the code and then compare it to a provided specification. This is why verification requires specifications - you need something to compare it to.
In Isabelle's NEWS file, I found
Command 'typedef' now works within a local theory context -- without
introducing dependencies on parameters or assumptions, which is not
possible in Isabelle/Pure/HOL. Note that the logical environment may
contain multiple interpretations of local typedefs (with different
non-emptiness proofs), even in a global theory context.
(which dates back to Isabelle2009-2). Is this the latest news with respect to typedef and local theory contexts? Further, what does the restriction "without introducing dependencies on parameters or assumptions" actually mean?
If it would mean that I cannot use locale parameters in the defining set of a typedef, then I would not consider typedef to be localized at all (since the only allowed instances can easily be moved outside the local context, or am I missing something?).
Is it (or should it, or will it ever be) possible to do something along the lines (where the set used for a typedef depends on the locale parameter V):
datatype ('a, 'b) "term" = Var 'b | Fun 'a "('a, 'b) term list"
locale term_algebra =
fixes F :: "'a set"
and V :: "'b set"
begin
definition "domain α = {x : V. α x ~= Var x}"
typedef ('a, 'b) subst =
"{α :: 'b => ('a, 'b) term. finite (domain α)}"
end
for which I currently obtain:
Locally fixed type arguments "'a", "'b" in type declaration "subst"
A few more notes on this:
The local theory infrastructure merely organizes existing module concepts (locale, class etc.) such that definitional mechanisms (definition, theorem, inductive, function etc.) can work uniformly in a variety of contexts. This does not change the logical foundations, so specification elements that cannot depend on term parameters (fixes) or premises (assumes) won't become fundamentally better. They are merely retrofitted into the greater framework, which is already an extra-logical benefit.
The canonical local theory targets are locale and its derivatives like class. These work within the logic according to the principles sketched above: lambda-lifting via some context of fixes and assumes. Other targets with more ambition can be imagined, but need to be implemented by some brave and heroic guys. For example, one could wrap up the AWE theory interpretation mechanism as another local theory target, and then get parametrization of types/consts/axioms --- at the usual cost of going through explicit proof terms to implement admissible inferences within an LCF prover (or at the cost of giving up LCF-ness and do it via some oracle).
Plain typedef as sketched above (and its derivatives like the localized codatatype and datatype of recent HOL-BNF) could be slightly improved in its dependecy type parameters, but this would mean some implementation efforts that don't justify the meagre outcome right now. It would merely allow to write polymorphic type constructions with implicit arguments like this:
context fixes type 'a
begin
datatype list = Nil | Cons 'a list
end
After export you would get 'a list as usual.
Further complication: fixes type 'a does not exist. Isabelle/Pure handles type parameters implicitly via Hindley-Milner.
In the meanwhile, this question was answered on the Isabelle mailing list.
The short version is that what I tried to do is simply impossible. What follows is an explanation by #makarius:
[Without introducing dependencies on parameters or assumptions] means you cannot refer to the fixes/assumes of context in the type specification -- this is logically impossible in Isabelle/Pure/HOL. Only the non-emptyness proof lives within the context, and the resulting theorems are local. (Actual dependence of the proof on logical content of the context is hard to get in practice, though.)
'typedef' is formally localized within the range of what is possible. Localization means to work with the local theory infrastructure and the context in the usual ways. For typedef this means extra-logical things like name spaces, syntax, derived declarations etc.
Historically, due to the impossibility to make typedef depend on the logical part of the context, it was not localized at all, and many tool implementations suffer from that until today.
As for my specific example:
You would have to evade the scope clash [...] by using different names for the parameters of type subst.
Nonetheless, this does not work from a logical standpoint: the dependency on term parameter V cannot be used in HOL typedef. The local theory concept does not provide magic ways to augment the logic -- it is merely infrastructure for an existing logical framework.
Both concepts allow new data types to be created.
The only difference I can see is that in functional languages, one can perform pattern matching on algebraic data types. But there is no comparable succinct feature for OO languages. Is this an accurate statement ?
Algebraic data types are so named because they form an "initial algebra",
+ represents sum types (disjoint unions, e.g. Either).
• represents product types (e.g. structs or tuples)
X for the singleton type (e.g. data X a = X a)
1 for the unit type ()
and μ for the least fixed point (e.g. recursive types), usually implicit.
from these operators all regular data types can be constructed.
Algebraic data types also support parametric polymophism -- meaning they can be used as constainers for any underlying type, with static guarantees of safety. Additionally, ADTs are provided with uniform syntax for introducing and eliminating data types (via constructors and pattern matching). E.g.
-- this defines a tree
data Tree a = Empty | Node a (Tree a) (Tree a)
-- this constructs a tree
let x = Node 1 (Node 2 Empty) Empty
-- this deconstructs a tree
f (Node a l r) = a + (f l) + (f r)
The richness and uniformity of algebraic data types, along with the fact they're immutable, distinguish them from OO objects, which largely:
only represent product types (so no recursive or sum-types)
do not support pattern matching
are mutable
do not support parametric polymorphism
I can see three major differences between algebraic data types and OO-style classes, not counting (im)mutablility because that varies.
Algebraic data types allows sums as well as products, whereas OO-style classes only allow products.
OO-style classes allow you to bundle a complex data item with it's accepted operations, whereas algebraic data types don't.
Algebraic data types don't distinguish between the data passed to the constructor and the data stored in the resulting value, whereas OO-style classes do (or can).
One thing I deliberately left out of that list was subtyping. While the vast majority of OO languages allow you to subclass (non-final, non-sealed, currently accessible) classes, and the vast majority of generally ML-family functional languages do not, it is clearly possible to forbid inheritance completely in a hypothetical OO (or at least OO-like) language, and it is likewise possible to produce subtyping and supertyping in algebraic data types; for a limited example of the latter, see this page on O'Haskell, which has been succeeded by Timber
A class is more than just a type definition -- classes in most OO languages are really kitchen sink features that provide all sorts of loosely related functionality.
In particular, classes act as a kind of module, giving you data abstraction and namespacing. Algebraic data types don't have this built in, modularity is usually provided as a separate, orthogonal feature (usually modules).
In some sense one can see it this way. Every language has only so many mechanisms to create user defined types. In functional (ML, Haskell style) languages, the only one is creation of an ADT. (Haskell's newtype can be seen as a degenerate case of an ADT). In OO languages, it's classes. In procedural languages it is struct or record.
It goes without saying that the semantics of a user defined data type vary from language to language, and much more so from language in paradigm#1 to language in paradigm#2. #Pharien's Flame has already outlined typical differences.
Is the concept of Algebraic Data Type akin to Class definitions in OO languages?
in functional languages, one can perform pattern matching on algebraic data types. But there is no comparable succinct feature for OO languages. Is this an accurate statement ?
That is a part of it.
As Andreas said, classes are a kitchen sink feature in statically-typed object oriented languages derived from Simula like C++, Java and C#. Classes are a jack of all trades but master of none feature in this respect: they solve many problems badly.
Comparing vanilla ML and Haskell with OO as seen in C++, Java and C# you find:
Classes can contain other classes whereas algebraic datatypes can refer to each other but cannot contain the definitions of each other.
Class hierarchies can be arbitrarily deep whereas algebraic datatypes are one-level deep: the type contains its type constructors and that is it.
New classes can be derived from old classes so classes are extensible types whereas algebraic datatypes are usually (but not always) closed.
So ADTs are not really "akin" to classes because they only solve one specific problem: class hierarchies that are one level deep. In this sense we can see two approximate observations:
ADTs require composition over inheritance.
Classes make it easy to extend a type but hard to extend the set of member functions whereas ADTs make it easy to extend functions over the type but hard to extend the type.
You might also look at the GoF design patterns. They have been expressed using classes in C++. The functional equivalents are not always ADTs but, rather, things like lambda functions instead of the command pattern and higher-order functions like map and fold instead of the visitor pattern and so on.
Since side-effects break referential transparency, don't they go against the point of functional languages?
There are two techniques that are used by purely functional programming languages to model side effects:
1) A world type that represents external state, where each value of that type is guaranteed by the type system to be used only once.
In a language that uses this approach the function print and read might have the types (string, world) -> world and world -> (string, world) respectively.
They might be used like this:
let main w =
let w1 = print ("What's your name?", w) in
let (name, w2) = read w1 in
let w3 = print ("Your name is " ^ name, w2) in
w3
But not like this:
let main w =
let w1 = print ("What's your name?", w) in
let (name, w2) = read w in
let w3 = print ("Your name is " ^ name, w2) in
w3
(because w is used twice)
All built-in functions with side-effects would take and return a world value. Since all functions with side-effects are either built-ins or call other functions with side-effects, this means that all functions with side-effects need to take and return a world.
This way it is not possible to call a function with side-effects twice with the same arguments and referential transparency is not violated.
2) An IO monad where all operations with side effects have to be executed inside that monad.
With this approach all operations with side effects would have type io something. For example print would be a function with type string -> io unit and read would have type io string.
The only way to access the value of performing operation would be to use the "monadic bind" operation (called >>= in haskell for example) with the IO operation as one argument and a function describing what to do with the result as the other operand.
The example from above would look like this with monadic IO:
let main =
(print "What's your name?") >>=
(lambda () -> read >>=
(lambda name -> print ("Your name is " ^ name)))
There are several options available to handle I/O in a functional language.
Don't be pure. Many functional languages aren't purely functional. It's more that they support
functional programming rather than enforcing it. This is by far the most common solution to the problem
of I/O in functional programming. (Examples: Lisp, Scheme, Standard ML, Erlang, etc.)
Stream transformation. Early Haskell I/O was done this way. Check my link below for details if you
want more information. (Hint: you probably don't.)
Continuation-passing I/O (the "world-passing" mentioned in other answers). In this one you pass a token
of data around with your I/O that acts as the necessary "different value" to keep referential integrity
alive. This is used by several ML dialects if memory serves.
The "continuation" or "world" thing above can be wrapped in various data types, the most (in)famous
being the use of monads in this role in Haskell. Note that this is, notionally, the same thing under
the covers, but the tedium of keeping track of "world"/"continuation" state variables is removed.
There's a research dissertation that exhaustively analyses these.
Functional I/O is an ongoing field of research and there are other languages which address this issue in interesting and mind-mangling ways. Hoare logic is put to use in some research languages. Others (like Mercury) use uniqueness typing. Still others (like Clean) use effect systems. Of these I have a very, very limited exposure to Mercury only, so I can't really comment on details. There's a paper that details Clean's I/O system in depth, however, if you're interested in that direction.
To the best of my understanding, if you want to have side effects in a functional language, you have to code them explicitly.
Since side-effects break referential transparency, don't they go against the point of functional languages?
It depends on the functional language:
Standard ML allows the liberal use of side-effects like most procedural languages e.g. Fortran, Algol, Pascal, C, etc.
Haskell restricts side-effects through the use of abstract data types like IO, ST and STM, which helps to preserve referential transparency.
Clean also restricts side-effects, but does this with its extended type system.
The functional language Coq uses - Gallina - provides no access to side-effects at all.
How do functional languages model side-effects?
One approach which isn't regularly mentioned relies on pseudo-data: individual single-use abstract values conveyed in an accessible structured value (commonly a tree), with the side effects only occuring when each abstract value is initially used. For more information, see F. Warren Burton's Nondeterminism with Referential Transparency in Functional Programming Language. An working example can also be found in GHC: its Unique name-supply type.
But if the extra parameters needed to make pseudo-data work is just too annoying, it is actually possible to usefully combine I/O and its observable effects with non-strict semantics...if you don't really need referential transparency.
Hindley-Milner is a type system that is the basis of the type systems of many well known functional programming languages. Damas-Milner is an algorithm that infers (deduces?) types in a Hindley-Milner type system.
Wikipedia gives a description of the algorithm which, as far as I can tell, amounts to a single word: "unification." Is that all there is to it? If so, that means that the interesting part is the type system itself not the type inference system.
If Damas-Milner is more than unification, I would like a description of Damas-Milner that includes a simple example and, ideally, some code.
Also, this algorithm is often said to do type inference. Is it really an inference system? I thought it was only deducing the types.
Related questions:
What is Hindley Miller?
Type inference to unification problem
Damas Milner is just a structured use of unification.
When it recurses into a lambda expression it makes up a new variable name. When you, in a sub-term, find that variable used in a way that would require a given type, it records the unification of that variable and that type. If it ever tries to unify two types that don't make sense, like saying that an individual variable is both an Int and a function from a -> b, then it yells at you for doing something bad.
Also, this algorithm is often said to do type inference. Is it really an inference system? I thought it was only deducing the types.
Deducing the types is type inference. Checking to see that type annotations are valid for a given term is type checking. They are different problems.
If so, that means that the interesting part is the type system itself not the type inference system.
It is commonly said that Hindley-Milner style type systems are balanced on a cusp. If you add much more functionality it becomes impossible to infer the types. So type system extensions that you can layer on top of a Hindley-Milner style type system without destroying its inference properties are really the interesting parts of modern functional languages. In some cases, we mix type inference and type checking, for instance in Haskell a lot of modern extensions can't be inferred, but can be checked, so they require type annotations for advanced features, like polymorphic recursion.
Wikipedia gives a description of the algorithm which, as far as I can tell, amounts to a single word: "unification." Is that all there is to it? If so, that means that the interesting part is the type system itself not the type inference system.
IIRC, the interesting part of the Damas-Milner type inference Algorithm W is that it infers the most general types possible.