Skip to content

thin-languages/more

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

More

Build Status Stories in Ready

A Scala based framework for developing modular Thin-Languages

##Architecture Basics Instead of defining a language as a single monolithic unit which comprises all functional abstractions along with syntax and semanthics, More proposes a modular aproach: Turn every feature of your language into a separate module and compose them on the run!

###Modules You can think of each concept available on a language as a separate feature, that can be model as an independent module (For example, booleans, integers, objects and functions can be isolated modules. More abstract tools like default parameters, inheritance and destructive asignation can be modules too).

Separating the language notions into modules allow for easier testing and feature flexibility.

Instead of picking a langueage based on it's features, you can pick whatever features you want and combine them for making the language you want.

To declare a Language Module on More, simply create a trait and define inside it any structure you need to support the new concepts. All concepts should extend the LanguageConcept trait or another, higher order, concept.

trait YourModule {
	trait YourNewConcept extends LanguageConcept
}

You can then declare a language by just combining any modules you want!

object YourLanguage extends Language
	with OneModule
	with OtherModule

Of course, some modules would need to use or redefine concepts presented by other modules. Scala language already provides an easy way of handling this: just linearize your traits as needed.

trait YourModule extends SomeOtherModule {
	case class YourNewConcept(aField: SomeOtherModuleTypeConcept) extends SomeOtherModuleConcept
}

Remember that, in order to remain true to the Thin ideas, the language concepts themselves should only be the abstract representation of your language ideas. That means that they should refrain from containing syntactic related elements. This way they are easier to combine, and the syntax can be define for each user, or even create new projective view that represent it in a whole different way, maybe without any code at all.

###Views So where IS the syntax? Well, setting aside that Thin Languages don't really NEED to have ONE syntax, most language will need a way to be written into (and read from) text. Of course, some languages may also want to work their concepts using different, crazier interfaces, that could be implemented in all kinds of frontend technologies.

For this More defines the LanguageView trait.

Think of a View as a two-way route between your Language Concepts and some representation (text based or not) that a frontend tool can use to render them and let a user interact with a program definition.

With this in mind, the LanguageView trait should be quite simple to understand:

trait LanguageView[ViewModel, ViewOptions, LanguageRequirement] {
	def encode(target: LanguageConcept)(implicit language: LanguageRequirement, options: ViewOptions): Try[ViewModel]
	def decode(target: ViewModel)(implicit language: LanguageRequirement,  options: ViewOptions): Try[LanguageConcept]
}

Where:

  • ViewModel is the type the view knows how to render. E.g. the ViewModel of a very simple view that renders code could be String, since it would take the object graph that represents your program and turn it into a code string and v.v.
  • ViewOptions is the type of the structure used by the view to contain any preference the user may have for the transformation. E.g. for a source view you may like to tabulate with spaces of tabs, so that should be a field in your view ViewOptions object.
  • LanguageRequirement is a trait that any language that wants to be render by the View should implement. This provides the view with a way to request any logic that may be needed to turn from your language's model to the view's model. E.g. a source view may need a grammar definition to convert your language concepts into a source string. Instead of implementing that itself (which would be very hard to do without deeply coupling it with some modules) it just declares it as a needed piece of info that must be provided. This helps keeping the views simple and short.

Once this is clear, you can see that the encode and decode methods are just transformers that let you go from the Models defined in your modules to a ViewModel instance and back again.

If any of this results a bit overwhelming, don't worry! Unless you want your model to be represented in a completely new way you probably will have no need to define your own views. The good thing about keeping the views completely decoupled from any LanguageConcept is that most common possible views should already be implemented and can be used with any language that meets the requirements. One example of that is More's SourceView which should be able to turn any language into source code, as long as they implement the Sourceable trait.

Remember, Views should only be defined if, for some reason, a frontend tool requires a different representation of your language's programs.

###The Presenter Pattern Let's say you have created some language modules and would like use them along with a view:

// Your Modules
trait FooModule {
  case class Foo(n: Int) extends LanguageConcept
}

trait BarModule {
  case object Bar extends LanguageConcept
}

//Your View
trait FancyView[
  FancyModel, // What the FancyView can turn your model from/into.
  FancyOptions, // Some preferences the view may have.
  Fanciable // The requirements the view impose to the language. This is stuff needed by the view to encode/decode.
] { ... }

trait Fanciable {
  def getTheFancy(target: LanguageConcept): Option[Fancy] = None
}

// Your Language
object YourLanguage extends Language
  with FooModule
  with BarModule

From the code you can tell the FancyView need the language to extend the Fanciable trait in order to work. As in any other view, the LanguageRequirement traits are probably going to be the place where the hard stuff takes place. Of course, you could design your modules to implement those requirements themselves, but that would make very hard to implement a new module (since it would have to provide all logic needed by any views it would be used on). More architecture is design to allow modules and views to be developed independently, completely decoupled from each other. We think it is a good idea to allow modules to be removed or added any time from a language, without having to worry about breaking the views.

Alternatively, you can just add a huge implementation of the language requirement in the language itself:

object YourLanguage extends Language with Fanciable
  with FooModule
  with BarModule {
  
  def getTheFancy(target: LanguageConcept) = target match {
    case Foo(n) => // Some Foo fancy
    case Bar => // Some Bar fancy
    case _ => super.getTheFancy(target)
  }
}

Althought this would kind of solve the problem it would be very repetitive, since every language that wants to render these modules with this view would have to repeat this code (and probably extend it to cover all the modules it wants to use), plus, the code would probably be very long and complex.

The proposal of More's architecture is to apply a Presenter Pattern, creating a small trait that provides whatever a single view needs for each module. That way each module becomes available of being render by a view (independently of others) without becoming coupled with the view itself.

Here is how the final version would be:

trait FanciableFoo extends Fanciable {on: Foo =>
  def getTheFancy(target: LanguageConcept) = target match {
    case Foo(n) => // Some Foo fancy
    case _ => super.getTheFancy(target)
  }
}

trait FanciableBar extends Fanciable { on: Bar =>
  def getTheFancy(target: LanguageConcept) = target match {
    case Bar => // Some Bar fancy
    case _ => super.getTheFancy(target)
  }
}

object YourLanguage extends Language
  with FooModule with FanciableFoo
  with BarModule with FanciableBar

Notice how, if properly implemented, there would be no need for the language to declare any code, but it could be used, as any other type, to solve conflicts between modules or presenters. Also notice that each presenter should only need to be coded once, and be rehused by any language that wants to render with the FancyView, regardless of what other modules it uses.

Presenters for popular views could be included in the modules packages themselves while presenters for new views could be declared anywhere, without affecting languages that won't use them.

##Related Projects

For a growing sanitized set of modules that work well with each other you can check the Less Thin-Language

We are also working on Slimdown a Thin-IDE, to provide desktop support to any More based language.

##Contributions

Sure! Pull requests are always welcome, just try to keep it small and clean. Also feel free to post any question or bug report in the issue tracker.

##License

This code is open source software licensed under the LGPL v3 License by The Uqbar Foundation. Feel free to use it accordingly.