#254 Fan's Type System Philosophy

brian Sun 22 Jun 2008

There has been lots of great discussion since we got the website up and running. It seems a lot of discussions take a turn towards the type system and how to enhance it. So I want to share my thoughts on type systems, and how I would like to shepherd Fan's development.

I think it is important to note that Fan is not a research language. I'd prefer to let the academics do the hard thinking and do the ground breaking features. Fan is designed as a working language for writing production systems. As such it doesn't include anything new and that is deliberate.

I personally believe there is real value in static type systems - especially for large scale projects. I really like old school Java before 1.5 and generics. There are lots and lots of warts, but an incredible amount of real software has been successfully written in Java. Yes Java's type system is pretty simple and doesn't solve many problems. Languages like Scala seem intent on building more sophisticated type systems to cover more and more cases. But at some point you have to give up and accept your type system can't cover every case.

But I also really like dynamic languages like Python and Ruby. And it sure seems like a lot of real world software is successfully developed in those languages. In general they just solve problems and don't spent a lot of time on type system debates. But at the same time I think dynamic languages have thrown the baby out with the bath water.

It seems like most people treat the issue of static and dynamic typing as an either/or decision. To me this is about engineering tradeoffs. This is why Fan supports static typing and dynamic typing - I embrace both of them. If you are first learning Fan you mostly see the static typing aspect. This is natural because programming in the small tends to be static - working with primitives, IO, etc. But programming in the large and building sophisticated applications and data models is where dynamic typing can really shine.

In the end I accept this simple premise: no type system can ever fully express every case of contracts and constraints. If you accept this then discussions can be framed about tradeoffs: does an enhancement to the type system justify the extra complexity compared to getting the job done with dynamic typing?

In my book, there is nothing wrong with solving a problem with dynamic typing. And so my goal is to deliberately to keep Fan's type system simple. A simple type system is a tradeoff - we aren't going to be able to solve complex problems with the type system. But that only works if you accept that many problems should be solved with dynamic typing.

So that is my basic philosophy. That doesn't mean I don't want to enhance the type system or don't love debating about it. But I do want to everyone to understand that I don't necessarily consider a hole in the type system a problem to be fixed.

tompalmer Tue 24 Jun 2008

My thoughts: I almost always prefer to know the (basic) type before calling an operation. I think it makes code easier to understand for both humans and tools. I prefer structural typing (like in Scala) better than ad hoc duck typing. I see duck calls (->) as having one of these main purposes:

  1. One-off needs. I just happen to need to treat things duck-like in one spot of code. Defining a structural type would be overkill.
  2. Quick prototyping. I'll replace my duck calls with structural typing or explicit mixin inheritance later.
  3. Quick-connect to external String-based services, such as SOAP calls and such like. This relates to both cases 2 and 4, I think, depending on the circumstances.
  4. ActiveRecord method name parsing. This is the most interesting case to me. If my method name really is a parsed language, then the number of predefined methods goes through the roof. And saying person->findByLastNameIgnoreCase("jones") is nicer looking than person.trap("findByLastNameIgnoreCase")("jones"), or however that would look. (I haven't double-check the API or syntax just now.)

Anyway, I don't want to debate too far. Just mentioning some of my own views. I also think Fan in its present form on this subject is close enough to my own interests. (Even structural type wrapping can be emulated by APIs if wanted. No need for language-level changes.)

JohnDG Tue 24 Jun 2008

Fan's type system is fantastic, and one of the things I like most about it. You have static typing for normal usage, but dynamic typing when you really need additional power (which for me, is a lot -- whenever I'm working with a dynamic language).

Scala is an example of static typing gone wrong. It's not terrible, it's just far more than it needs to be, and may do more harm for the language (and to developers and tool writers) than a much simpler type system.

tompalmer Wed 25 Jun 2008

I agree that Scala is too complicated and that Fan is better. I was just referring to structural typing. And still I'm quite fine with it not being part of language.

cbeust Wed 25 Jun 2008

Agree, I think that structural typing is not that useful (details here: http://beust.com/weblog/archives/000476.html) so I'm happy to not see it supported in Fan.

-- Cedric

helium Wed 25 Jun 2008

OK, just read your blog post and found it a bit ... strange. Why didn't you simply define

type HasName = { def getName : String }

? That way you wouldn't have to reapeat yourself.

cbeust Wed 25 Jun 2008

That's my point: if you define a type for it, it stops being structural typing.

helium Wed 25 Jun 2008

Nope. It still is.

tompalmer Wed 25 Jun 2008

And my thoughts are just that any interface/mixin definition can be used as a structural type if you can create types at runtime. I think it's useful but not a central use case, so I'd rather just have an API for it (built in if small or third party are both fine with me).

JamesIry Tue 8 Jul 2008

cbeust,

You don't have to like Scala. I can appreciate that. But you keep getting facts wrong about the language (and languages with similar features like SML).

A type alias is just that - an alias for a type, an alternative name if you will. The same type can have many aliases. In the above example by helium things are still structurally typed.

type HasName = { def getName : String }
type AlsoHasName = {def getName : String }

def doSomething(x: {def getName : String}) = println(x.getName)
def doSomethingElse(x:HasName) = println(x.getName)
def doAThirdThing(x:AlsoHasName) = println(x.getName)

class Foo {def getName = "I'm a Foo!"}

val foo = new Foo()
doSomething(foo)
doSomethingElse(foo)
doAThirdThing(foo)

Note that Foo type does not depend on the HasName or the AlsoHasName alias. They are both aliases for the same structural type. The first function, "doSomething," doesn't use any of the aliases for that type.

Aliases are just aliases. Here's an example where I create an alias for a nominative type.

type ListOfMapFromStringToInt = List[Map[String,Int]]
def doItToIt(x: ListOfMapFromStringToInt) = ...

val maps : List[Map[String,Int]] = ...
doItToIt(maps)

JamesIry Tue 8 Jul 2008

Argh! My formatting was eaten. Sprinkle line returns in there generously if you want that code to compile.

Edit: Fixed thanks to Andy's link

cbeust Tue 8 Jul 2008

James,

Can we please keep this forum focused on Fan?

Thanks.

-- Cedric

JamesIry Tue 8 Jul 2008

Hmmm, every comment but one in this thread has been about Scala. Why is mine the one that gets the negative vote?

My point here isn't to antagonize. Fan looks like a great language and it's on my list to study. I'm just clarifying a misunderstanding in this thread. Maybe structural types and type aliases don't have a place in Fan's type system. Or maybe they do. But it would be hard to make that determination if people didn't understand how they actually work in languages like SML and Scala.

Personally, I think type aliases are essential once a type system gets "interesting" - i.e. once you can compose types in some way. In Fan's case perhaps the const qualifier and the limited use of parametric types ("generics") for list, map, and func already push it to that level.

IMHO, structural types probably don't fit Fan's philosophy, quite as well.

cbeust Tue 8 Jul 2008

Sorry, it felt to me your post was mostly about picking on me for my Scala views, so I am inviting you to do this on my blog, not here.

This thread, so far, has been discussing structural typing and whether it should be included in Fan, and I just want to make sure that we do not stray from that goal.

andy Tue 8 Jul 2008

James - FYI - you can view the rules for post formatting here

helium Tue 8 Jul 2008

Personally, I think type aliases are essential once a type system gets "interesting"

Definitly, but IMO Fan's type system is way to "boring" to make type aliases usefull and I don't believe it will become much more powerfull. At least at the moment this would only introduce an unneeded keyword without much use.

JamesIry Tue 8 Jul 2008

Definitly, but IMO Fan's type system is way to "boring" to make type aliases usefull

I'm not entirely sure of that. Unless I'm misreading something in the language spec, this is a valid Fan type:

|[Str]->[Int:Str]|

That seems "interesting" enough that if I had to use it twice I'd want an alias.

brian Tue 8 Jul 2008

We seem to be talking about a couple different things - I think there is a difference between type aliasing and structural typing. To me aliasing just means that we are assigning an identifier to a type signature. We've talked about enhancing the using statement to do this:

using |[Str]->[Int:Str]| as Callback

I'm not sure we really need that yet, but I can definitely see us deciding to do that after more practical experience.

To me structural typing is a way to do "statically checked duck typing" by declaring a type alias which requires a specific slot or slots. I think structural typing is cool, but in Fan's case we just use real duck typing with the "->" operator.

As Helium said - Fan type system is deliberately simple and boring because by design we fallback to dynamic/duck typing (which was the point of the original post).

cbeust Wed 9 Jul 2008

|[Str]->[Int:Str]|

That seems "interesting" enough that if I had to use it twice I'd want an alias.

Agreed. Some time ago, I asked if it would be possible to create a class that extends this kind of type, which is the usual way of simulating typedef in Java (which works reasonably well):

class MyMap : [Str:[Str:Int]]

I'm not sure what this would mean if the type in question is a closure, though, which might be another mild argument in favor of some type of aliasing.

-- Cedric

JamesIry Wed 9 Jul 2008

Brian, I didn't mean to conflate the two. The discussion just sort of turned into one about both things. Type aliases are useful with or without structural typing (both Haskell and C have type aliases, but neither has structural types). My original point was just that type aliases don't change the nature of structural typing, they just cut down on the repetitive boilerplate that Cedric was complaining about.

As to whether structural typing is really statically typed duck typing - some dynamic typing fans will tell you that structural typing is not since true blue duck typing would require dependent types as well as structural types. For instance, with Scala structural types you must specify all the message signatures that a method MIGHT call on a parameter object in any path. With duck typing you can dynamically decide to call the A1 method or the B2 method and arrange things so that objects passed in might only implement one or the other as appropriate for the conditions. It takes a very sophisticated static type system to express that. Even then, I'm not sure any decidable type system can deal with duck types once recursive duck types get involved.

Cedric, re: using a subclass to simulate type aliasing in Fan or Java. Java desperately needs type aliasing, too. And a bit of local type inference wouldn't be amiss either. Fan...maybe. My glance at the type system says so, but I can see why others might disagree.

Subclassing isn't really a substitute for type aliasing because you can't express "the following four methods all take maps from stings to maps from string to ints" using subclassing. Subclassing makes you over-constrain your parameters - you can only say "the following four methods take MyMap, which is my own subtype of maps from yada yada". To make that useful you end up having to write a manual translation function/constructor from [Str:[Str:Int]] -> MyMap (probably just a decorator/delegation kind of thing), and at that point you have created so much boilerplate you'll wonder why you didn't just copy and paste the type.

tompalmer Wed 9 Jul 2008

I think the overconstraint of subtyping or wrapping is often good enough, if any interfaces are small enough to make adapters easy. Well, I say that, but there are tons of times in Java, I've seen people use String in unclear ways instead of wrapping a String in another type, just because it's so heavy-weight in terms of potential heap space (depending on the use case) and/or syntax bother. Probably the bother issue outweighs the memory issue in most cases.

But anyway, the general concern for String is that common types which can't be subclassed can become unclear.

Then again, if it's just a type alias, the type is erased at runtime (and usually not enforced at compile time, either), so it's not always clear what the intent was if the object escapes a clear context.

If any of that ramble made sense, ...

Anyway, I'm somewhat OK without type aliases, even though I think there are use cases.

Login or Signup to reply.