• No results found

Sard: An Object-Functional Programming Language

N/A
N/A
Protected

Academic year: 2021

Share "Sard: An Object-Functional Programming Language"

Copied!
61
0
0

Loading.... (view fulltext now)

Full text

(1)

Sard: An Object-Functional Programming Language

Hugo Svallfors

June 20, 2011

Master’s Thesis in Computing Science, 15 credits Supervisor at CS-UmU: Frank Drewes

Examiner: Pedher Johansson

Ume˚ a University

Department of Computing Science SE-901 87 UME˚ A

SWEDEN

(2)
(3)

Abstract

This bachelors thesis concerns the specification of a new programming language.

This language, called Sard, is similar to, but incompatible with, the existing language Scala. Sard, like Scala, is a high-level object-functional language.

Unlike Scala, it is not very closely tied to the JVM or Java, eliminating some constraints on the languages design. Since this necessitates breaking backwards compatibility with Scala, the opportunity to disregard it is used to fix some of the authors irritations with the language. This thesis mostly focuses on deciding on the exact changes to Scala, and on the overall design, rather than on implementing a compiler for the language. A reason for this is that there is insufficient time to properly implement and debug a compiler. Another is the desirability of pushing changes to the language as early into the design process as possible. Preferably, almost all changes to the language should occur before any compiler code has been written.

The design eventually produced gets rid of some known issues stemming from the JVM, like null pointers, non-reified generics and single inheritance.

Several features of Scala, like self-type annotations and infix syntax for methods are scrapped. Others, like pattern matching, are generalized. Some changes to the syntax are also made, particularly in the areas of closures, pattern matching and object construction.

As of yet, this language has no implementation, and in future work, this

must be rectified. Sard also requires calling compatibility with another pro-

gramming language, but this remains to be specified. Nevertheless, Sard con-

stitutes a promising refinement of an already great programming language, and

it is hoped that Sard will fix the few remaining issues with Scalas design.

(4)
(5)

Contents

1 Introduction 1

1.1 Why make a new language? . . . . 2

1.2 Problem Description . . . . 3

1.3 Overall Vision . . . . 4

2 Feature Set 5 2.1 Type Inference . . . . 5

2.2 Trait Inheritance . . . . 5

2.2.1 Symmetric Inheritance . . . . 6

2.2.2 Removal of Single Inheritance . . . . 6

2.3 Compound Types . . . . 7

2.4 Subclassable Closures . . . . 7

2.5 Underscore Closures . . . . 8

2.6 Tuples . . . . 9

2.7 Rectified Generics . . . . 9

2.8 Type Members . . . . 9

2.9 ML-Like Pattern Matching . . . . 10

2.10 Null Pointers . . . . 12

2.11 Syntactic Sugar for Constructors . . . . 13

2.12 All statements return a value . . . . 13

2.13 Default Values for Parameters . . . . 14

2.14 Method Overloading . . . . 15

2.15 Companion Objects . . . . 15

2.16 Infix Syntax for Methods . . . . 15

2.17 Structural Subtyping . . . . 16

2.18 Call-by-value, -by-need and -by-name . . . . 17

2.19 Module and Import System . . . . 17

iii

(6)

2.20 Variable Arguments . . . . 18

2.21 Local Methods . . . . 18

2.22 Syntactic sugar for Array/Table Mutation . . . . 18

2.23 For-Each Loops and Enumerations . . . . 19

2.24 C-Style Loops and their jump instructions . . . . 19

2.25 Checked Exceptions . . . . 19

2.26 Switch Instructions . . . . 20

2.27 XML-Literals . . . . 20

2.28 Tail-Call Optimization . . . . 21

2.29 Annotations . . . . 21

2.30 Uniform Access . . . . 21

2.31 Symbols . . . . 22

2.32 Variance Annotations . . . . 22

2.33 Multi-Line Strings . . . . 23

2.34 Named Arguments . . . . 23

2.35 Optional Dynamic Typing . . . . 23

2.36 Existential Types . . . . 24

2.37 Self-Type Annotations . . . . 24

2.38 Higher-Kinded Generics . . . . 24

2.39 Implicit Parameters and Conversions . . . . 25

3 Syntax 27 3.1 Changes relative to Scala . . . . 27

3.1.1 New Closure Syntax . . . . 27

3.1.2 The Removal Of ”new” Around Object Creation . . . . 28

3.2 Syntax Specification . . . . 28

4 Type System 35 4.1 Special Types . . . . 36

4.1.1 trait Any . . . . 36

4.1.2 sealed trait Nothing . . . . 36

4.2 Primitive Traits . . . . 37

4.2.1 trait Cloneable[A] . . . . 37

4.2.2 trait Showable . . . . 37

4.2.3 trait Binary . . . . 37

4.2.4 trait Equality[A] . . . . 37

4.2.5 trait Hashable . . . . 37

4.2.6 trait Ordered[A] . . . . 38

(7)

CONTENTS v

4.2.7 trait Enumerable[A] . . . . 38

4.2.8 trait Exception . . . . 38

4.2.9 trait Numeric[A] . . . . 38

4.2.10 trait Integral[A] . . . . 39

4.2.11 trait Fractional[A] . . . . 39

4.2.12 trait NumberConvertible . . . . 39

4.2.13 trait Bitwise[A] . . . . 39

4.2.14 trait Floating[A] . . . . 40

4.2.15 trait FunctionN[-A,-B,-C..,-N,+R] . . . . 40

4.2.16 trait Lazy[+A] . . . . 40

4.2.17 trait PartialFunction[-A,+B] . . . . 40

4.2.18 trait Updateable[+K,+V] . . . . 41

4.3 Primitive Types . . . . 41

4.3.1 object System . . . . 41

4.3.2 sealed trait Ordering . . . . 41

4.3.3 class Lock . . . . 41

4.3.4 class Thread . . . . 42

4.3.5 class Array[Elem] . . . . 42

4.3.6 class TupleN[+A,+B,..+N] . . . . 42

4.3.7 sealed trait Bool . . . . 43

4.3.8 class Char . . . . 43

4.3.9 class String . . . . 44

4.3.10 sealed trait UnicodeCategory . . . . 44

4.3.11 class SByte . . . . 45

4.3.12 class Byte . . . . 45

4.3.13 class Short . . . . 46

4.3.14 class UShort . . . . 46

4.3.15 class Int . . . . 47

4.3.16 class UInt . . . . 47

4.3.17 class Long . . . . 47

4.3.18 class ULong . . . . 48

4.3.19 class Float . . . . 48

4.3.20 class Double . . . . 48

4.3.21 class LongDouble . . . . 49

4.3.22 class IndexOutOfBounds . . . . 49

4.3.23 class IndexUndefined . . . . 49

4.3.24 class Overflow/class Underflow . . . . 49

(8)

4.3.25 class DivideByZero . . . . 49

4.3.26 class IllegalArgument . . . . 49

4.3.27 class OutOfMemory . . . . 49

4.3.28 class StackOverflow . . . . 49

4.3.29 class RefutedPattern[A] . . . . 50

5 Conclusions 51 5.1 Acknowledgments . . . . 51

5.2 Limitations . . . . 51

5.3 Alternate Solutions . . . . 51

5.3.1 Keeping Higher-Ranked Generics . . . . 51

5.3.2 Keeping Type Members . . . . 52

5.4 Conclusion . . . . 52

References 53

(9)

Chapter 1

Introduction

This bachelors thesis concerns the specification of a new programming language.

This language, called Sard, is similar to, but incompatible with, the existing language Scala. Scala is a so-called object-functional language on the Java virtual machine. The language is an attempt of combining object orientation with functional programming.

According to Scalas creator, Martin Odersky, functional programming makes it easier to build things from simple parts, whereas object orientation makes it simple to extend complex systems [Odersky(2007a)]. To paraphrase this ob- servation slightly, functional programming works best when programming in the small, and object orientation works best when programming in the large.

When phrased like so, the potential synergy between the two becomes obvious:

Why not make programming in the large and small easy by combining them?

Functional programming has been characterized as an idiom of program- ming which makes heavy use of first-class functions, and seeks to minimize side-effects. Object-orientation on the other hand is an idiom which revolves around the encapsulation of behavior and state in objects. The idiom of object- functional programming can thus be summarized as encapsulating state and behavior in immutable objects, some of which are closures.

For a more in-depth look at Scala, the O’Reilly book ”Programming Scala”

[Wampler(2008)] is recommended. There will also be some examples during the discussion of Scalas feature set. The reader may also wish to peruse the Scala specification [Odersky(2009)] for some grasp of Scalas syntax and type system, although this is not strictly necessary.

1

(10)

1.1 Why make a new language?

Scala has from the outset been very closely tied to Java and the JVM. Already at its inception Scala was envisioned as a JVM language with full Java calling compatibility. However, Odersky thinks that doing so was a ”double-edged sword” [Odersky(2006)].

In doing so, they gained the strength of Javas VM implementations, and vast amounts of library code that they would otherwise have had to write themselves. But they also had to make several compromises with their design to preserve calling compatibility with Java. Odersky lists the lack of true virtual classes, the adoption of null references and limited tail recursion among these [Odersky(2006)].

Although the above are certainly problematic, arguably there are more problems that crop up because of Java: The presence of dynamic types and the non-presence of unsigned integers.

As the reader may know, Java is about to have its first version update in quite some time. One item that is to be added to the language is optional dynamic typing. Since Scala itself is an advanced statically typed language, having dynamic types would only weaken its guarantees of compile-time cor- rectness. Indeed, any static verification of any property goes right out the window as soon as dynamic types are introduced. As a direct consequence, previously catchable bugs will become run-time crashes instead, wasting the programmers time with trivial errors that could, in a proper type system, be discovered automatically.

In addition, any program using dynamic types will be one to two orders of a magnitude slower than one without. Nearly all optimizations rely on having advance information on types. Without that, only a few runtime optimizations like JIT can be applied. And those can be applied in static languages as well.

In conclusion, dynamic types will make software that uses them both buggy and slow, wasting the time of both man and machine at the same time.

Unfortunately, since Scala has to have calling compatibility with Java, they are bound to implement any feature that Java does. Hence Scala will have dynamic types, in spite of the fact that it clashes with the languages general emphasis on safety.

Java has also been lacking any form of unsigned integer. This means that it is impossible to efficiently express the invariant that one has a whole number that is not negative. This is a pattern that crops up quite a lot in many different problems. It also makes binary I/O inordinately difficult, when binary data is almost always interpreted as unsigned.

In summary, while the foundational idiom of Scala is sound, maybe even

brilliant, it is limited by its implementation on the JVM. Close dependence on

Java is holding Scalas design back in a number of key areas. And perhaps most

worryingly, Java is currently changing in ways incompatible with Scala. The

Scala team has little to no recourse in reacting to these changes; As long as

Scala wishes to maintain calling compatibility with Java, all of these problems

(11)

1.2. Problem Description 3

are intractable.

Odersky finishes his list of Java compromises by saying this:

”It would be interesting to see another unification of FP and OOP that is less determined by existing technology.”

This is the goal of this thesis.

1.2 Problem Description

To solve the aforementioned problems with Scala, a new programming language is called for. One based on the object-functional idiom but removed from close dependence on the JVM. This language is called Sard.

Sard will re-implement a well-behaved subset of Scala on the Mono .NET virtual machine. The .NET platform was chosen due to its very well maintained virtual machines as well as having some features that Java lacks. An effort will be made to reduce dependence on .NET, so that the option of having other implementations on per example LLVM remains open. This will also ensure that .NET-isms like null pointers and single inheritance do not make it into the language.

The goal of this bachelors thesis is to identify the exact subset to implement, and to create a specification for it. This specification should at a very minimum contain the following:

– An acceptation or rejection of every Scala feature, with a cogent motiva- tion for why/why not it should be in the language.

– A complete grammar/syntax for the language.

– A fully specified type system with sound semantics.

Unfortunately, there is not sufficient time to implement and debug a com-

piler. Therefore, this work will not concern itself with the implementation of

this specification. It is also desirable to receive feedback on what to implement

before much code has been written. This will make it easier to change the

specification, should the need arise.

(12)

1.3 Overall Vision

The overall vision for Sard is that of a safe and expressive language. One where code is terse, and has relatively few ways to fail. One might ask why expressiveness and type-safety are considered the most important qualities, as opposed to a host of others like speed, readability e.t.c. This is because these are the qualities that have the greatest effect on development time.

Expressiveness obviously cuts down on development time, by allowing more to be done with the same amount of typing. This will make implementing a program more simple. A more controversial opinion is that static type-safety can also cut down on development time. In the authors anecdotal opinion, debugging any given program takes much longer time than writing it. So while cutting down on time to write an application is certainly positive, cutting down the time to debug it will have greater overall effect on time to complete a project.

In languages without static type-safety and many side effects, it is not possible to statically guarantee much about any program being written. As a result, testing must to a greater extent be done manually. This has in certain programming communities gone so far that they advocate not writing anything until there is a unit test which covers said code. They are, in the authors opinion, expending great time and effort doing manually what can and should be done automatically. If even 10% of the time these people spend writing tests could instead go to other tasks, they would greatly benefit.

Thus, Sard is designed for rapid application development, but takes the ap- proach that this is best done in (expressive) statically typed languages. Having type safety enables one to guarantee some properties automatically without writing unit tests for them, thus ultimately saving the time of everyone in- volved.

Many companies and research projects are greatly constrained in the time- frame they have in completing their goals. And even those projects that do not have a strict time-frame (like open-source ones), will find that the additional time can be reinvested in additional features or more thorough testing.

Sard should thus be widely applicable, but especially useful in situations

where programmer time is more expensive than computer time.

(13)

Chapter 2

Feature Set

In this chapter, every feature of Scala will be accepted or rejected, with a short description of the feature and motivation of why it should/should not be in the language. To save the time of the author and reader, any feature present in Java may be assumed to be present unless explicitly mentioned otherwise. Any code examples will be illustrated with Scala, and thus some grasp of Scalas basic syntax [Odersky(2009)] will be needed to understand this chapter. For further reading on how Scala works, refer to the excellent O’Reilly book on Scala [Wampler(2008)], which is freely available online.

2.1 Type Inference

In Scala, any type signatures in an expression context (i.e. on fields and in- side methods) are optional. If not specified, the compiler will infer types for expressions. This is one of the greatest accomplishments of the ML family of languages, enabling one to drastically cut down on the amount of unneeded typing, while at the same time keeping the safety and efficiency of static typing.

As such, not including this in Sard would be a crime.

2.2 Trait Inheritance

Traits may be viewed as a compromise between full multiple inheritance and Javas single inheritance with interfaces. A trait is basically an interface, but may contain concrete methods, concrete and abstract fields, as well as type members (more on that later). The only thing that can be in a class that cannot also be in a trait is a constructor.

1 //A d r a s t i c a l l y s i m p l i f i e d v e r s i o n o f t h e I t e r a t o r t r a i t i n S c a l a . t r a i t I t e r a t o r [ A ] {

3 def hasNext : B o o l e a n def n e x t : A

5

(14)

5

def map( f : A => B) : I t e r a t o r [ B ] = {

7 val s e l f = t h i s

new I t e r a t o r [ B ] {

9 def hasNext = s e l f . hasNext

11 def n e x t : B = f ( s e l f . n e x t ) }

13 }

}

In this example we can see approximately how the Iterator pattern is im- plemented in Scala. It is implemented as a trait with two methods next and hasNext. Using these two abstract methods, a variety of concrete methods can be constructed. These will be available to subtypes of Iterator with no extra work. In languages like C# or Java, there is no way to inherit concrete methods from multiple sources. Hence, their Iterator interfaces generally just implement next and hasNext, since it would be too odious for implementers of Iterator to define various advanced operations like map. In Scala, there is no such tension between thin-but-easy-to-implement and feature-rich-but-cumbersome interfaces. Implementing the Iterator trait is a matter of defining two methods, and yet that means one can get 10+ other methods defined for free.

Given the above, it is considered a given that traits have to be in Sard.

However, there are two ways in which it has been changed relative to Scala;

Inheritance is now symmetric and single inheritance has been removed.

2.2.1 Symmetric Inheritance

When one is able to inherit methods from multiple sources, one is faced with the problem of what to do when two identical methods are inherited from multiple sources. In Scala, this is solved by the last-inherited method silently overriding the others. This way of resolving conflicts is called linearization. In most cases, conflicts like this are rare. But when they occur, making them explicit would aid in debugging them. On occurrence of the error, the programmer can then take steps to resolve it.

2.2.2 Removal of Single Inheritance

Single inheritance from a concrete class is already avoided by convention in OO languages. This is because the binding between parent and child classes become too strong, making the class hierarchy brittle in the face of changing requirements, and making refactoring harder. In addition, we already have trait inheritance in Sard, which is much more expressive.

That means that in Sard, it is only possible to inherit from traits. Classes

which are possible to instantiate are all considered final.

(15)

2.3. Compound Types 7

2.3 Compound Types

Since Sard has trait inheritance, it is possible to subtype multiple interesting types. Therefore, we need a syntactic way of expressing multiple subtyping.

t r a i t H a s h a b l e {

2 def hash : UInt

} 4

t r a i t E q u a l i t y [ A ] {

6 def e q u a l s ( t h a t : A) : Bool }

8

def f o o [ A <: E q u a l i t y [ A ] with H a s h a b l e ] ( x : A) : U n i t

In the above example, A in foo is a parametric type that is a subtype to both Equality[A] and Hashable. This is very simple to implement, as the type system must already track multiple subtyping anyway. Also, it makes it easier to have many small traits, and only depend on mixes of them instead of concrete types. This leads to more loosely coupled and expressive code.

2.4 Subclassable Closures

Scala, being a object-functional language, has a form of closures. A closure is a form of first-class function that can access variables defined in lexical scope from where the closure is defined. Being a first-class function of course means that functions in Scala can be bound to variables, created at runtime and returned from other functions. This enables one to create so-called higher order functions, which may be familiar if one has previous experience with functional languages.

Higher order functions are functions that take parameters that are them- selves functions. For example, there is a well known function called filter that, given a closure that takes a list element and returns a Bool, selects the elements from a list for which the closure returns true.

That describes closures in general. Scalas closures in particular are also objects. That is, there is a trait which makes the inheriting object a closure.

If one inherits from one of the FunctionN types, the inheriting object will be a function of N input parameters. For example, an object subclassing Func- tion2[A,B,C] will be a function that takes two parameters of type A and B and returns a type C.

1 def i s E v e n ( x : I n t ) : B o o l e a n = x % 2 == 0

( 1 t o 1 0 ) . f i l t e r ( i s E v e n ) . t o L i s t // r e t u r n s L i s t ( 2 , 4 , 6 , 8 , 1 0 ) 3 // L i k e above , e x c e p t t h a t we c r e a t e d i s E v e n on t h e f l y .

( 1 t o 1 0 ) . f i l t e r ( x => x % 2 == 0 ) . t o L i s t 5

var n r E l e m e n t s : I n t = 0

7 ( 1 t o 1 0 ) . f o r e a c h ( ( x ) => n r E l e m e n t s = n r E l e m e n t s + 1 ) n r E l e m e n t s == 10 // t r u e

(16)

9 /∗

11 T h i s i s how c l o s u r e s w i t h one p a r a m e t e r a r e d e f i n e d . Any i n s t a n c e o f t h i s t y p e can b e i n v o k e d w i t h t h e s y n t a x 13 ” i n s t a n c e ( a ) ” i n s t e a d o f ” i n s t a n c e . a p p l y ( a ) ” .

∗/

15 t r a i t F u n c t i o n 1 [+A,+B ] { def a p p l y ( a : A) : B 17 }

19 /∗

A F u n c t i o n 1 s u b c l a s s where one can a s k i f t h e f u n c t i o n 21 i s d e f i n e d f o r t h i s v a l u e .

For example , an Array i s a P a r t i a l F u n c t i o n [ I n t , Element ] . 23 ∗/

t r a i t P a r t i a l F u n c t i o n [+A,+B ] extends F u n c t i o n 1 [ A, B ] { 25 def i s D e f i n e d A t ( a : A) : B o o l e a n

}

Closures in general enable one to do vastly more with less code. Consider for example filter. Every time a Java programmer wants to select a part of a list that conforms to some condition, they have to do it manually with a for-each loop that constructs a new list. In Scala, on the other hand, one can factor out that pattern into a method, and then simply invoke it with a closure that gives the condition. To this, Scala also adds the ability of any object to be a closure, and the possibility of specializing closures that perform something special, like the PartialFunction example above. In summary, this is an enormous positive, and will definitely be in Sard.

2.5 Underscore Closures

A form of syntactical sugar for closures, in which underscores are used to anony- mously use the variables of the closure. This is best illustrated with an example.

// e q u i v a l e n t t o p r e v i o u s d e f i n i t i o n s 2 ( 1 t o 1 0 ) . f i l t e r ( % 2 == 0 ) . t o L i s t

val i s E v e n = % 2 == 0 // means ” x => x % 2 == 0”

Besides being a shorter syntax for closures, underscore closures can also be used for partial application. That is, the practice of only applying a closure to some of its parameters, thus defining a new function.

1 def isMod ( x : I n t , d i v : I n t ) : B o o l e a n = x % d i v == 0 /∗

3 P a r t i a l a p p l i c a t i o n o f isMod t h a t c h e c k s d i v i s i b i l i t y w i t h two . 5 ∗/

val i s E v e n = isMod ( , 2 )

While having a shorter syntax for closures is somewhat useful, the greater

motivation for keeping underscore closures is the partial application. This is

(17)

2.6. Tuples 9

somewhat of a core concept in functional programming, and one that again saves lines of code.

2.6 Tuples

Tuples, as seen in many languages lately, are general pairs/triples e.t.c. of values. These are convenient when one wishes to return multiple values from a function.

In Scala and Sard, tuples are instances of the TupleN types.

c l a s s Tuple2 [+A,+B ] ( val 1 : A, val 2 : B)

2 c l a s s Tuple3 [+A,+B,+C ] ( val 1 : A, val 2 : B , val 3 : C) // And s o on . .

4

// These a r e e q u i v a l e n t

6 val a P a i r : Tuple2 [ I n t , I n t ] = Tuple2 ( 5 , 2 ) val e q P a i r : ( I n t , I n t ) = ( 5 , 2 )

Sard defines the tuple types and tuple expressions as shorthand for the appropriate version of TupleN.

2.7 Rectified Generics

One unfortunate influence of Java on Scala is that generics are not saved at runtime. This makes any type of reflection involving type parameters unneces- sarily difficult. Fortunately, Sard rectifies this by implementing itself on .NET, which does save type parameters. As we will see in the next section, this makes some parts of Sard design much easier.

2.8 Type Members

A type member in Scala is a slot in an object which, instead of a field or method, contains a generic type parameter. Much like any slot, it can be concrete (i.e bound to an existing type, like Int, or another type parameter) or abstract, left unspecified.

1 t r a i t A b s C e l l { type T

3 val c e l l : T

} 5

c l a s s I n t C e l l ( var c e l l : I n t ) extends A b s C e l l {

7 type T = I n t

} 9

val t e s t : A b s C e l l = new I n t C e l l ( 1 0 ) 11 t e s t . c e l l // 10

t e s t . c e l l = 12

(18)

Some might ask what, if anything, this adds to the existing ways to have generics. One of these things is that, as mentioned above, types can be abstract and then specified in subclasses. Another is that type members can be limited without being specified.

// L i m i t T t o s u b t y p e s o f Numeric w i t h o u t s p e c i f y i n g what T i s . 2 t r a i t N u m e r i c C e l l extends A b s C e l l {

type T <: Numeric 4 }

Taken together, this means that type members are much better suited than generics to when one has a hierarchy of types and one or more of the base types have generic parameters. If one wishes to inherit a type without specifying it, one must make it a parameter to the subclass as well, and so on for however many steps one must inherit it.

/∗With parameterized t y p e s . ∗/

2 t r a i t Base [ T <: Numeric [ T ] ]

t r a i t Type1 [ T <: Numeric [ T ] ] extends Base [ T ] 4 t r a i t Type2 [ T <: Numeric [ T ] ] extends Type1 [ T ]

t r a i t Type3 extends Type2 [ I n t ] 6

/∗With type members∗/

8 t r a i t Base {type T <: Numeric ; } t r a i t Type1 extends Base

10 t r a i t Type2 extends Type1

t r a i t Type3 extends Type2 {type T = I n t ; }

That is not to say that type parameters have no place however. For type members are not accessible in subtyping checks. They do not count for sub- typing purposes, and if one wishes them to do so, they must be parameters.

2.9 ML-Like Pattern Matching

Another feature Scala borrows from ML is pattern matching. What pattern matching in Scala provides is a fast way of deconstructing an object. Pattern matching is essentially a quick way of selecting fields from an object, if that object satisfies an expression.

1 /∗

T h i s i s a c a s e c l a s s h i e r a r c h y . 3 An Option can b e e i t h e r a v a l u e

Some , o r l a c k o f v a l u e , None . 5 ∗/

abstract c l a s s Option [ A ]

7 case c l a s s Some [ A ] ( val some : A) extends Option [ A ] case object None extends Option [ N o t h i n g ]

9

val o p t i o n : Option [ I n t ] = new Some ( 5 ) 11 /∗

T h i s i s a match e x p r e s s i o n . I t t r i e s t o match 13 o p t i o n t o one o f t h e s e p a t t e r n s .

(19)

2.9. ML-Like Pattern Matching 11

The v a l u e o f t h i s e x p r e s s i o n i s t h a t o f t h e m a t c h i n g p a t t e r n . 15 I f no e x p r e s s i o n matches , an e x c e p t i o n i s thrown .

∗/

17 o p t i o n match {

case Some [ I n t ] ( 5 ) => −5 19 case Some [ I n t ] ( x ) => x

case None => 0 21 }

The astute reader may have noticed that nothing about pattern matching cannot be done with if-else and type checks. However, the resulting code would be much longer and more verbose. Observe:

1 // T h i s i s e q u i v a l e n t t o t h e match e x p r e s s i o n a b o v e . i f ( o p t i o n . i s A [ Some [ I n t ] ] {

3 i f ( o p t i o n . asA [ Some [ I n t ] ] . some == 5 ) {

−5

5 } else option . asA [ Some [ I n t ] ] . some } else {

7 0

}

One may notice that, although pattern matching is exactly equivalent to doing a series of ifs with type checks, the if-else code is twice as long. Since this type of operation is rather common, it makes sense to have a fast way of completing it. In conclusion, it should be kept in Sard.

However, there is a somewhat idiosyncratic detail about Scalas pattern matching, namely that it only works on objects specified in advance. One may have noticed that we declared Option[A] in the last example to be a case class. This tells the Scala compiler that it should save the type and generic parameters of the object so that it can be pattern matched against. Doing so is a necessity on the JVM, where generics are not normally saved at runtime.

Due to the fact that Sard is not similarly constrained, we can generalize that to any slot of any object.

This is Sards pattern matching:

sealed t r a i t Option [ A ]

2 c l a s s Some [ A ] ( val some : A) with Option [ A ] object None with Option [ N o t h i n g ]

4

val o p t i o n : Option [ I n t ] = Some ( 5 ) 6 match ( o p t i o n ) {

case Some [ I n t ] ( some=5) =>−5 8 case Some [ I n t ] ( some=x ) => x

case None => 0 10 }

One may notice that the ”case classes” of the past have now become just regular classes. One may also notice that the patterns indicate what slot they are matching on. This is because unlike Scalas pattern matching we can match on any slot of any object, not just the construction parameters of a case class.

One may per example select methods with patterns.

(20)

val h a s h f i n d : I n t => S t r i n g =

2 match ( HashMap [ I n t , S t r i n g ] ( ( 1 , ” one ” ) , ( 2 , ” two ” ) , ( 3 , ” t h r e e ” ) ) ) { case HashMap ( g e t=h a s h f i n d ) => h a s h f i n d

4 }

h a s h f i n d ( 2 ) // ” two ”

There is also a special pattern for tuples.

1 val ( x , y ) : ( I n t , I n t ) = ( 3 , 3 )

3 // De−sugared v e r s i o n o f the above .

val tmp : Tuple2 [ I n t , I n t ] = Tuple2 [ I n t , I n t ] ( 3 , 3 ) 5 val x : I n t = tmp . 1

val y : I n t = tmp . 2

As previously mentioned in the tuple section, this is only a shorthand for a TupleN pattern. A tuple pattern with only identifiers is also the only kind of pattern which can show up in a variable declaration. The idea being that one can quickly bind the result of a returned tuple.

2.10 Null Pointers

Null or uninitialized pointers are one of the leading causes of errors worldwide.

They are the billion dollar mistake [Hoare(2009)]. One of the most important design goals of Sard is to make this type of error impossible. In practice, this turns out to be surprisingly easy.

The first step is to force all variables and fields to be initialized on creation.

When declaring a variable/field, one must set it to a value immediately. This solves the problem for variables. But what if the object in which the field is created has multiple constructors? Or what if the field is to be abstract?

The answer to the second question is that not initializing a field is leaving it abstract, and is used instead of an ”abstract” keyword on fields. The answer to the first one is a bit more complex.

In Scala and Sard, there is a ”main” constructor which consists of the class body. The formal parameters to this main constructors are given after the class name.

c l a s s Example ( f o o : I n t ) { 2 p u b l i c val b a r : I n t = f o o

}

Since one must initialize fields on creation or leave them abstract, this means that any object created with the main constructor is fully initialized. Now we can solve the problem of adding more constructors.

Since any object constructed with the main constructor is fully initialized,

the problem can be solved if we could guarantee that all constructors called the

main one. We can do this by forcing every constructors first statement to be a

call to a constructor that has already been defined. Since the main constructor

(21)

2.11. Syntactic Sugar for Constructors 13

is defined first, every constructor call goes through it eventually. And so all objects are fully initialized.

Thus having guaranteed reference safety in Sard, the problem facing us now is how to model a value that can possibly be missing? For this, the Option[A]

type we introduced earlier does the trick. Using that, we can easily model the lack of value as None.

The author can take no credit for any of the above. All of these solutions are already implemented in Scala. However, due to being constrained by Java backwards compatibility, they need the null pointer to be able to pass nulls in and out of Java methods. Remove the need for Java compatibility, and removing it becomes just a matter of removing the null literal.

2.11 Syntactic Sugar for Constructors

There is a rather simple bit of syntactic sugar in Scala for object construction.

It defines a shortcut for when the main constructor of an object directly saves a constructor parameter as a field.

1 c l a s s Example ( p u b l i c val f o o : S t r i n g ) new Example ( ” H e l l o ” ) . f o o // ” H e l l o ”

Note in the above example foo is both the name of the formal parameter and of the field it sets. Doing this eliminates having to come up with a temporary name for a constructor parameter when one is just saving it in a field anyway.

This is a common case, and as such should be optimized.

2.12 All statements return a value

One of the better features of ML that Scala borrows is the notion that every- thing has or returns a value. In Scala, all statements return a value. If the statement in question is one that works entirely through side effects, the special value ”Unit” is returned. The Unit can be considered a sort of sentinel value indicating that the statement produced no (useful) value.

The if-statement returns the value produced by the branch taken. Hence, the true- and false-branch of the if-statement must have the same type, and a single-branch-if must have type unit.

val x = i f (−1 < 0) −1 else 1 // Binds x to −1.

2 /∗

4 I l l e g a l ! The b r a n c h e s o f t h i s i f h a v e d i f f e r e n t t y p e s ( I n t and U n i t ) . 6 ∗/

i f (−1 < 0) 1 else p r i n t l n ( ” p o s i t i v e ” ) 8

// F i x o f t h e a b o v e . 10 val y =

(22)

i f (−1 < 0) {

12 1

} else {

14 p r i n t l n ( ” p o s i t i v e ” ) 1

16 }

As one may have noticed in the last example, the fact that we printed something before returning 1 did not matter. That is because blocks too have a value, and that is the value of the last statement they execute. Since the last statement above is an Int, the block has type Int, and this typechecks.

Finally, try-catch-finally also has a value. That is the value of its try- or catch block. Hence, the try and catch blocks must again have the same type, and the finally block must have type unit.

val e x i t C o d e =

2 try {

val f i l e = o p e n F i l e ( ” f o o . t x t ” ) 4 f i l e . f p r i n t l n ( ” Foo ” )

0

6 } catch {

/∗

8 E x c e p t i o n i n S c a l a i s a p a t t e r n m a t c h i n g . Any e x c e p t i o n n o t matched i s thrown f u r t h e r . 10 As p e r a l l p a t t e r n matching , one can h a v e a

d e f a u l t c a s e t h a t m atches a n y t h i n g .

12 ∗/

case F i l e N o t F o u n d => 1 14 case P e r m i s s i o n E r r o r => 2

}

2.13 Default Values for Parameters

Default values are, as the name implies, a way to specify a default value for a method parameter. This parameter can then be left out of the method invocation, in which case it will receive its default value.

1 def add ( a r g : I n t , amt : I n t = 1 ) : I n t = a r g + amt add ( 0 ) // 1

3 add ( 1 , 2 ) // 3

This is occasionally useful when there are more than one default parameter.

When there is just one, one may just as easily replace it with two methods,

one which gives default values to the other. When there are three or more

default values however, this becomes somewhat unwieldy. Therefore, it would

probably be best to keep them.

(23)

2.14. Method Overloading 15

2.14 Method Overloading

As the reader surely knows, this is the practice of having several methods with identical names in the same class. If they then have different types, the compiler can decide which method to invoke for any given object.

However, there is nothing to stop one from simply giving the overloaded methods different names. Very little of value would be lost doing that instead.

In addition, method overloading complicates the implementation of some other features, like type inference. Therefore, methods in Sard must be uniquely named in their class.

2.15 Companion Objects

Scala does not have static members. What it has instead is a feature whereby a static class (”object” declaration in Scala) can share namespace with a class.

1 c l a s s Foo object Foo

So if one defines a class and an object, in the same file, with the same name, they have the same namespace. This is more or less semantically identical to having static members.

It was decided to keep them, as we still need some way of representing static functions that belong to a class. It is a pattern that comes up quite often.

2.16 Infix Syntax for Methods

In Scala, methods with only one parameter can be written in infix syntax.

c l a s s Example extends I n t { 2 def mod( d : I n t ) = t h i s % d

}

4 // The two l i n e s b e l o w a r e s e m a n t i c a l l y i d e n t i c a l . new Example ( 5 ) mod 3

6 new Example ( 5 ) . mod ( 3 )

Scala uses this to implement operator overloading. Basically, one can use any ASCII symbols in a method name, so + is just a one-argument method like any other. Instead of writing ”x + y”, one could write ”x.+(y)” and it would have the same semantics.

This means one can do two things which are wholly unnecessary:

1. Invent new operators like -*-, the meaning of which can only be guessed at.

2. Call regularly named methods in infix or regular postfix syntax.

(24)

It is a well established practice to name things in a somewhat informative way. However, there is no series of English phrases that is as uninformative as a series of ASCII punctuation symbols. The only time when an operator is the best variable name is when dealing with mathematics and comparison. For in the case of symbols like + and !=, they actually have a known meaning.

Neither is there much point having two method call syntaxes. Why not just one? They are semantically identical, so why complicate matters?

Due to the above issues, infix syntax from Sard is being removed, along with the ability to have anything other than ASCII letters, underscores and numbers in method names.

This does remove the ability to overload the numerical and comparison operators. To reintroduce that functionality, a somewhat C#-like way of over- loading operators is offered.

In Sard, there are some primitive traits that have operators defined on them.

For example this one:

t r a i t E q u a l i t y [ A ] {

2 def e q u a l s ( t h a t : A) : Bool }

Anything that implements this trait and defines equals can be used with the == and != operators. The numerical traits are similarly defined a in trait called Numeric[A]. In all cases, the basic pattern is that operators cannot be created or overloaded directly. One can however mix in a trait that defines operators in terms of some methods. Methods which one may implement. This has the advantage that the useful and well-behaved overloading of numerical operators remains, whereas operators named *!!* do not.

2.17 Structural Subtyping

In most statically typed OO languages, subtyping is always nominative. That is, a type A is considered a subtype of type B if and only if the programmer explicitly says it is. This is what ”extends” in languages like Java means. It means declaring this type to be a subtype of the supertype. There is however, another way of doing subtyping, found in O’Caml and Python, where subtyping is structural.

This means that we consider A to be a subtype of B if and only if A has all the methods that B has. If B has one method with the signature def foo:Unit, then any object with a method named foo that takes and returns no value would be considered a B.

Having structural subtyping alone presents a number of problems. The

most thorny one being that the pattern in nominatively typed languages where

one defines required functionality in an abstract superclass or trait, and then

relies on subclasses to still have that functionality would be impossible. To

keep with the A and B example; Just because A has a method foo, does not

imply A does what a B expects it to do. Perhaps one defined B.foo to be a

(25)

2.18. Call-by-value, -by-need and -by-name 17

sealed method which logs something, whereas A.foo does something completely different. In summary, if one had structural subtyping only, using subtyping to guarantee some behavior would no longer work.

The path that Scala took is to instead have both structural and nomina- tive subtyping. A somewhat recurring pattern in Scalas design is that, when faced with a decision between two things, they elect to pick both. For some things, like calling strategies, this approach worked well enough. Arguably, this is not the case here. Having two ways to implement subtyping is a bit gratuitous. Mostly because both subtyping strategies overlap to a rather large degree. Structural subtyping can use trait-like functionality without defining traits (by defining a required structure for parameters to methods) and nom- inative subtyping can guarantee invariants by subtyping. But the benefits of having both are, in the authors opinion, too slim to motivate the complexity of doing so. Given that we should aim for one kind of subtyping, nominative would be preferable, to avoid the issues with only having structural subtyping.

2.18 Call-by-value, -by-need and -by-name

The three most common ways of passing parameters to a method are call-by- value, call-by-need and call-by-name. Scala implements all of them. By-name is the most common behavior, and so is the default. In this variant, parameters are fully evaluated before being passed to methods, and the resulting value is copied into the method. In by-name, the formal parameter is effectively replaced by the bound expression. Which effectively means the parameter is not evaluated on call, and is evaluated once per every time it is used in the method. By-Need is much like By-Need, except it is only evaluated once, the first time it is used.

In Scala, as previously mentioned, by-value is the default, since that is what the JVM does. This is good, most code is after all strictly evaluated. The other two are implemented by closures. By-Name is a no-argument closure, whose expression is the one the formal parameter is set to. Whenever the formal parameter is mentioned, the closure is called. By-Need, in a supremely elegant implementation, is a subtype of a no-argument closure that saves its result the first time its called, and is thereafter not evaluated again.

I am all in favor of keeping this feature, partly because its very elegance, but also because implementing lazy evaluation over a strict host language is otherwise very difficult.

2.19 Module and Import System

In Sard, one can switch between imports being relative (from the current folder)

and absolute (from the core library folder), depending on whether one prefixes

the classpath with ”sard.” or not.

(26)

Unlike Scala, there is no concept of a package except as a folder. That is, a class shares a package with all classes in the same folder, and that is the only way to specify packages.

Another thing that has been simplified from Scala is that the ability to rename classes when importing them is removed. Instead, one can stick to using path-qualification in the few instances where its necessary.

In Sard and Scala both, one can import multiple or all classes from a pack- age, like so:

1 /∗ Absolute import with a s e t . ∗/

import s a r d . c o l l e c t i o n .{ List , Set } 3 /∗ Absolute import with a w i l d c a r d . ∗/

import s a r d . i o .

5 /∗ R e l a t i v e import with one c l a s s . ∗/

import mymodule . Example

2.20 Variable Arguments

In Scala and Sard both its possible to have a variable number of arguments to methods and constructors. There may only be one variable argument at the end. The variable arguments are packed into an Array.

def sum ( i n t s : I n t∗) : I n t = i n t s . sum 2

sum ( 1 , 2 , 3 , 4 , 5 ) // 15

This is highly useful because constructors with variable arguments can be used instead of collection literals. E.g ”List(1,2,3)” is just a constructor call with 3 arguments. If variable arguments where removed, one would have to replace them with some other form of collection literal. Therefore, it appears that they hold an important role in Sard.

2.21 Local Methods

In Scala, it is possible to have local methods, i.e., methods defined solely within another method. They are of course easily replaceable with private methods defined at the same level as the parent method. Also, the potential complex- ity of having every method potentially be a module makes implementing the compiler unnecessarily difficult. Therefore, they will be removed.

2.22 Syntactic sugar for Array/Table Mutation

We have previously seen in ”Subclassable Closures” that an Array can be seen

as a function from Int to element. There is also some corresponding syntactic

sugar for setting the values of an Array or Table.

(27)

2.23. For-Each Loops and Enumerations 19

1 val t e s t = HashMap [ S t r i n g , I n t ] t e s t . u p d a t e ( ” one ” , 1 )

3 t e s t ( ” one ” ) // 1 t e s t ( ” two ” ) = 2 5 t e s t ( ” two ” ) // 2

The syntax test(”two”) = 2 is syntactic sugar for test.update(”two”,2). This ensures one can use mutable arrays with the same ease as in other languages.

However, Sard does not prioritize Arrays over other collection types, so apply is extensible to any collection that is in-place mutable.

2.23 For-Each Loops and Enumerations

Both for-each loops and enumerations are very familiar to OO programmers everywhere. They are also, in a language like Scala, unnecessary. The for-each loop is basically a very simple closure, and the enumeration is a very simple sealed class hierarchy. There is little point to having multiple ways of doing the same thing.

2.24 C-Style Loops and their jump instructions

Java retains most of the loop instructions from C, along with their jump in- structions break and continue. A strange decision, as Java already has a for- each loop, and removed goto claiming that unconditional jumps are unintuitive.

Why then did they not apply the same logic to break and continue?

Fortunately, Sard provides an excellent opportunity to get rid of this bag- gage. As previously mentioned, the for-each loop was removed for being a (very simple) closure. Using similar logic, the do-while- and for-loops where removed.

The for-loop is only really applicable to arrays, which are not as important in Sard. And most of the imperative loops are intended to be rarely used, hence the removal of the somewhat specialized do-while. While-loops however where retained, to simplify highly imperative loops when or if they occur.

2.25 Checked Exceptions

Checked exceptions are a Java feature where one may force the caller of certain methods to check for exceptions. While this helps moderately with type-safety, the cost of having a try-catch block at every call site is frankly unacceptable.

Additionally, Scala and other ML-related languages have a much better way of modeling this called Either types. Here exemplified in Sard source code.

1 sealed t r a i t E i t h e r [ A, B ]

c l a s s L e f t [ A ] ( val l e f t : A) with E i t h e r [ A, N o t h i n g ] 3 c l a s s R i g h t [ B ] ( val r i g h t : B) with E i t h e r [ Nothing , B ]

(28)

Using this, one could represent checked exceptions by returning an Either from ones method, representing success as Right and failure as Left. Like so:

1 def f a i l I f O d d ( x : I n t ) : E i t h e r [ S t r i n g , S t r i n g ] = i f ( x % 2 != 0 ) {

3 L e f t ( ”The i n t e g e r was odd ! ” ) } else {

5 R i g h t ( ”The i n t e g e r was e v e n . ” ) }

7

/∗

9 I d i d n o t d e f i n e i s L e f t b e l o w , b u t i t s b e h a v i o r s h o u l d b e o b v i o u s

11 ∗/

i f ( f a i l I f O d d ( 3 ) . i s L e f t ) throw new E x c e p t i o n

Thus, one is forced to check the return value of the method if one wants to use it. This avoids an extra feature, and is also less verbose.

2.26 Switch Instructions

The switch instruction is present in Java. It is however redundant when the full might of pattern matching is present, for which the switch instruction is only the most trivial sub-case.

2.27 XML-Literals

Scala has a number of features which are, on balance, more of a negative than a positive. But XML-literals is the only one which genuinely lacks merit. As the name implies, this is a way of writing literate XML inside Scala code. There are two major problems with this:

1. This is better solved with libraries.

2. XML!

First and most importantly, high-level tasks like serialization to text are better handled by libraries. There is no reason, no reason at all, why XML reading/writing could not have been implemented in a library. On the other hand, there are a number of reasons why it should not have been done that way.

1. One can switch to different XML libraries.

2. One can switch serialization formats more easily.

3. The language is not tied to any one specific serialization format.

4. XML is not any more special than any other data format. Why make

this primitive instead of JSON,Markdown or CSV?

(29)

2.28. Tail-Call Optimization 21

Then again, XML-literals may make it easier to work with XML. But this was not a problem that needed solving. XML is one of many serialization formats. And many have argued that XML is one of the least preferable ones [Atwood(2008)][Browne(2003)]. In summary, the best course of action would be to limit any XML parsing to libraries.

2.28 Tail-Call Optimization

Sard is an at least half functional language. As such, many loops will be replaced with recursion. To make sure that it is possible to recurse indefinitely, tail call optimization is necessary. Tail call optimization is an optimization which makes sure that no function call that ends on another function call consumes stack space. As such, one can recurse forever instead of just until the call stack is full. Fortunately, TCO is already present in .NET, so no additional work from me is necessary.

2.29 Annotations

Annotations are a sort of ”first-class comment”. That is, a special type of comment which is actually stored on the object/method in question, and in- spectable by meta-programming.

One can, per example, use annotations to indicate that an object is serial- izable, or unboxed, or a number of other things. The problem with all of them is that they really are not compatible with static verification; None of these are checked until runtime, and even then only by reflection. I would therefore advocate replacing annotations with regular documentation.

2.30 Uniform Access

An old problem in object orientation is how to replace a field with getter/setter methods. One often finds that a getter/setter must do something else besides just getting/setting the field. This could include logging, error checking or any number of things. Due to the inherent difficulty of doing this without rewriting all code using the object in question, some Java coders have taken to avoiding public fields entirely, starting with getters/setters even in the trivial cases when they are not really needed.

Scala solves this problem in two ways: For accessors, field selection and call- ing a no-argument method looks exactly the same syntactically. For mutators, any method whose name ends with ’ =’ can be called like so:

c l a s s Foo {

2 def f o o =(x : I n t ) : U n i t }

4

// These two e x p r e s s i o n s a r e e q u i v a l e n t .

(30)

6 new Foo . f o o = 5 new Foo . f o o =(5)

This means that there is no penalty to using public fields, as it is simple to replace them with getters/setters later.

2.31 Symbols

A symbol is a interned string. Meaning that two symbols with the same name will always refer to the same object.

1 valexampleMap = new HashMap [ Symbol , I n t ] exampleMap += ( ’ one , 1 )

3 exampleMap += ( ’ two , 2 ) exampleMap += ( ’ t h r e e , 3 ) 5 exampleMap ( ’ two )

Symbols are mostly intended to be an alternative to String for map keys.

Frankly, no compelling reason has been offered why one could not have stuck with strings instead.

2.32 Variance Annotations

Variance annotations are a way of specifying how type parameters behave w.r.t.

subtyping. For example, if we have a type List[T], should List[String] be con- sidered a subtype of List[Any]? There are three kinds of variance: Covariant, contravariant and invariant.

In invariant subtyping, only the same type parameter is allowed. So only a List[T] is considered a subtype of List[T]. This is the behavior present in Java. In covariant subtyping, List[A] is a subtype of List[T] if and only if A is a subtype of T. In contravariant subtyping, it is the other way around; List[A]

is a subtype of List[T] if and only if A is a supertype of T.

In Scala syntax, variance of a class type parameter is noted in the class header like so:

1 c l a s s I n v a r i a n t L i s t [ T ] c l a s s C o v a r i a n t L i s t [+T ] 3 c l a s s C o n t r a v a r i a n t L i s t [−T]

Note that each type parameter of a class has its own variance behavior. So it is possible to combine covariance and contravariance if a class has several type parameters.

Control over variance is necessary in Sard, because in certain corner cases lack of control over variance can render code type-unsafe. Consider the below example:

1 val a r r 1 : Array [ S t r i n g ] = Array ( ” T e s t ” ) val a r r 2 : Array [ Any ] = a r r 1

(31)

2.33. Multi-Line Strings 23

3 val e r r o r : I n t = 5

a r r 2 ( 1 ) = e r r o r // We j u s t added an I n t t o an Array [ S t r i n g ] !

In order to prevent this sort of thing, Javas design team decided that all class type parameters should be invariant. A solution which can only be described as somewhat drastic. Scala solved this much more elegantly by letting the programmer control variance.

2.33 Multi-Line Strings

In Scala, strings which begin with triple quotes can span multiple lines. In order to simplify this, it was decided to instead just keep the regular single- quote string, and let that one span multiple lines.

2.34 Named Arguments

One may invoke methods with named parameters in Scala, like so:

c l a s s Box ( val wi dt h : I n t , val h e i g h t : I n t ) 2 new Box ( h e i g h t =5 , w id t h =4)

This has some marginal utility to catch argument transposition bugs; That is, when one orders similarly-typed arguments wrong, like exchanging width and height in the example above. However, in order to use this, one must consult documentation or source to find the argument names, at which point one knows their position already. And of course, using named arguments is optional, so one is still quite capable of transposing ones arguments in spite of this existing. In summary, the author is slightly in disfavor of implementing this feature.

2.35 Optional Dynamic Typing

One of the more important reasons it was decided to create Sard is Scalas recent introduction of dynamic typing. The new version of Java, Java 7, arriving this summer is introducing dynamic (non-type-checked) types. Since Scala has to maintain calling compatibility with Java, they have already introduced them into the language.

There are two main reasons this is a bad idea. The first being that nearly all optimizations depend on the ability of the compiler to know what type objects have. Remove that, and performance drops by an order of a magnitude. Only a few optimizations can be applied dynamically at runtime; And furthermore they all share the characteristic that they can be applied just as easily in static languages.

While this is certainly bad enough, the more important problem is that

dynamic typing implies more buggy programs. In static typing, one has the

(32)

ability to detect bugs automatically. There are no convincing arguments on why one would not want that.

2.36 Existential Types

An existential type is much like a generic type parameter, except that it is not bound to a name. What does this mean? Observe:

/∗Type o f r e v e r s e method wi thout e x i s t e n t i a l type . ∗/

2 def r e v e r s e [ B ] ( x s : L i s t [ B ] ) : L i s t [ B ]

/∗Type o f r e v e r s e method with e x i s t e n t i a l type . ∗/

4 def r e v e r s e ( x s : L i s t [ ] ) : L i s t [ ]

The difference between the two is that in the first method, the element type of the list is bound to a name - B. In that method, one can then define variables of type B. If B had some known supertype, one could invoke methods on Bs too. An existential type however is not bound to a name, and so cannot be manipulated in any such interesting ways. It is mostly useful when the type in question is irrelevant. However, any existential type can, as the above example shows, be replaced with a regular type parameter. This leads one to believe that there is little benefit to keeping them.

2.37 Self-Type Annotations

In most other object-oriented languages, the type of ”this” inside any class is always the same as the enclosing type. However, eliminating this need is actually both possible and type safe. And doing so can reduce dependencies in code, making for a convenient way of doing dependency injection. Basically, a self-type annotation means that a concrete subclass of this (abstract) class has to subtype the annotation type. This is best explained by example.

t r a i t A

2 t r a i t B extends A

/∗A t r a i t , with a s e l f −type annotation t h a t r e q u i r e s A. / 4 t r a i t C { s e l f :A => }

/∗We j u s t mixed B i n t o C without ever mentioning B in C. ∗/

6 t r a i t D e x t e n d s C w i t h B

By doing the above, one can see that self-type annotations enables one to leave some subtyping abstract, thus defining the exact inheritances later.

However, this concept has a considerable overlap with abstract inheritance, to the point that its difficult to justify having it as a separate feature.

2.38 Higher-Kinded Generics

Most modern statically typed languages have generics. Scala however also has

generics of a higher kind; One whose generic parameters can themselves have

(33)

2.39. Implicit Parameters and Conversions 25

parameters.

c l a s s Type [ A [ B ] ]

The above is a legal type in Scala, and means that the class Type takes one type parameter A, which itself must have one type parameter B. Using this weird and wonderful feature it is possible to define traits like this one:

1 /∗

T h i s t r a i t t a k e s a f u n c t i o n o f A t o B,

3 and t u r n s i t i n t o a F u n c t i o n from F [A] t o F [ B ] f o r any t y p e F .

5 ∗/

t r a i t F u n c t o r [ F [ A ] ] {

7 def fmap [ B ] ( f : A => B) : F [ B ] }

9 /∗

11 T h i s i s an i n s t a n c e o f a F u n c t o r where F == L i s t and fmap == map .

13 ∗/

t r a i t L i s t [+Elem ] with F u n c t o r [ L i s t [ Elem ] ] {

15 def fmap [ B ] ( f : A => B) : L i s t [ B ] = t h i s . map( f ) }

The above example simply cannot be written in languages without higher- kinded generics. A type system with them is strictly more powerful than one without. And so they will still be in Sard.

2.39 Implicit Parameters and Conversions

Implicit parameters, being a somewhat advanced topic, is hard to give simple examples of. If the reader does not already know what implicit parameters are, please refer to ”Programming Scala” [Wampler(2008)], which does a much better job of explaining them . Martin Odersky also goes into quite some depth discussing their correspondence with Haskell type classes in this paper [Odersky(2007b)].

As for the authors views on implicit parameters: Haskell Type classes where originally introduced as a way of representing ad-hoc polymorphism in func- tional languages [Philip Wadler(1989)]. Hence most of the terminology around type classes; The choice of terms like ”class” and ”instance” is not a coinci- dence - they where representing object-oriented programming! In a language like Sard, which already has a class system, the utility of type classes is dimin- ished.

Another point where this concept fits poorly into the OO paradigm is encap- sulation; Type classes are not encapsulated in any way, shape or form. When looking up an implicit, the compiler looks in the following area:

1. Within lexical scope from calling site of method with implicit parameter.

(34)

2. Within lexical scope of definition site of the same method.

3. Within all superclasses of the object containing the call site.

4. Within all superclasses of the method definition site.

When one wants to answer what type classes are present at any point, the answer is hard to come by without some kind of graph search algorithm! This is an important problem, as what implicit values are in scope affects the behavior of the methods in question. Consider for example this method:

def map [ B , That ] ( f : A => B) ( i m p l i c i t b f : CanBuildFrom [ Repr , B , That ] ) : That

This is the definition of map on Scala collections. What makes it unique is that it uses an implicit, CanBuildFrom, to determine what collections can be built from what source. This enables things like the following:

1 val s e t 1 : B i t S e t = B i t S e t ( 1 , 2 , 3 , 4 , 5 )

val s e t 2 : S e t [ S t r i n g ] = s e t 1 . map( . t o S t r i n g )

We can build a Set[String] from a BitSet because somewhere there is a CanBuildFrom[BitSet,B,Set[B]] which indicates this is possible. But where is it? And how many more instances of CanBuildFrom are there at this point?

Just what kinds of collections can be built from a BitSet anyway? To answer these questions, we have to do the aforementioned graph search. This seems to be an indication that implicit values break all semblance of encapsulating behavior.

The fact that theoretically this could be solved with documentation and/or

better file organization is irrelevant. Everything becomes safe, provided one

works hard enough at keeping it that way. There should not even be an op-

portunity to have such a gratuitously large scope in the first place.

(35)

Chapter 3

Syntax

In this chapter, the syntax of Sard is presented, and compared to that of Scala where interesting. The reader is assumed to be familiar with Scalas existing syntax [Odersky(2009)], as well as Extended Backus-Naur Form notation for syntax.

3.1 Changes relative to Scala

There are a number of removed features of Scala, most of which affect the Syntax. They will not be specified again. There are also some minor changes to Scalas syntax that were not accompanied by any features being removed.

These are discussed here.

3.1.1 New Closure Syntax

The syntax for creating and using closures have received several changes in Sard. While Scalas underscore closures remain unchanged, the regular closures have a new syntax. Scalas syntax is an argument list for the closure, followed by a left arrow, then an expression. Sards syntax is the keyword ”do”, and argument list, and an expression (no separator). The reason for the new syntax is that it is easier to parse, and more appealing to the author.

Scalas syntax:

( 1 t o 1 0 ) . f i l t e r ( x => i s E v e n ( x ) ) 2 ( 1 t o 1 0 ) . f i l t e r ( i s E v e n ( ) )

( 1 t o 1 0 ) . f i l t e r ( ( x ) => { isEven ( x ) ; } )

Sards syntax:

1 Range ( 1 , 1 0 ) . f i l t e r ( do ( x ) i s E v e n ( x ) ) Range ( 1 , 1 0 ) . f i l t e r ( i s E v e n ( ) )

3 Range ( 1 , 1 0 ) . f i l t e r ( do ( x ) { isEven ( x ) ; } )

27

References

Related documents

The aim of this essay is to compare the narrative perspective in two novels, ​Stim ​ (2013) by Kevin Berry and ​The curious incident of the dog in the nighttime ​ (2003) by

The analysis of verbosity shows that functional code is generally less verbose than the imperative equivalent, especially with regards to programmatic lines, but also in terms of

persuasive design triggers within an e-grocery setting to reduce food waste, secondly, to develop guidelines (presented in a framework with example criteria and implementation) for

public abstract boolean drawnPosition(Position p) public abstract boolean wonPosition(Position p, boolean player) public abstract float positionEvaluation(Position p,

Article Title: Chapter 2: Understanding Neural Networks Category: Artificial Intelligence Most Popular From Series: Programming Neural Networks in Java Posted: Wednesday,

According to Lo (2012), in the same sense “it points to the starting point of the learning journey rather than to the end of the learning process”. In this study the object

In summary, we have in the appended papers shown that teaching problem- solving strategies could be integrated in the mathematics teaching practice to improve students

Theoretically, the article is based on the international and national literature on strategic communication and public relations as an academic discipline, profession and practice