How the Functional Ability of Scala Comparing to Haskell - an Example
About one and half year ago, it was my first time to consider Scala seriously, I wrote a blog about the syntax example of Scala, Erlang and Haskell .
With more experience of Scala, I'd like to know how about the functional ability of Scala comparing to Haskell. I picked up Paul R. Brown's perpubplat blog engine again, which is a Haskell implementation heavily in functional style. I tried to port more code from Haskell to Scala keeping in similar expressions. Here's the code example of Entry.scala in Scala comparing to Brown's original Entry.hs:
Original Haskell code piece
-- | Data structures for an item (post or comment) and the -- overall structure in terms of parents and children. module Blog.Model.Entry where import qualified Blog.FrontEnd.Urls as U import Utilities import qualified Blog.Constants as C import Maybe import List ( sortBy, isPrefixOf, intersperse) import qualified Data.Map as M import Data.Map ( (!) ) type ISO8601DatetimeString = String type XhtmlString = String -- | Overall data model for the runtime. data Model = Model { -- | by_permatitle :: M.Map String Item, by_int_id :: M.Map Int Item, child_map :: M.Map Int [Int], all_items :: [Item], next_id :: Int } empty :: Model empty = Model M.empty M.empty M.empty [] 0 data Kind = Post | Comment | Trackback deriving (Show, Read, Eq) build_model :: [Item] -> Model build_model [] = empty build_model items = Model (map_by permatitle sorted_items) bid (build_child_map sorted_items) (sorted_items) (n+1) where sorted_items = sort_by_created_reverse items bid = (map_by internal_id sorted_items) n = fst . M.findMax $ bid build_child_map :: [Item] -> M.Map Int [Int] build_child_map i = build_child_map_ (M.fromList $ (map (\x -> (internal_id x,[])) i)) i -- Constructed to take advantage of the input being in sorted order. build_child_map_ :: M.Map Int [Int] -> [Item] -> M.Map Int [Int] build_child_map_ m [] = m build_child_map_ m (i:is) = if (parent i == Nothing) then build_child_map_ m is else build_child_map_ (M.insertWith (++) (unwrap $ parent i) [internal_id i] m) is -- | Insert an item, presuming that all of its data other than -- internal identifier have been correctly set. insert :: Model -> Item -> (Item,Model) insert m i = (i', m { by_permatitle = M.insert (permatitle i') i' $ by_permatitle m , by_int_id = M.insert n i' $ by_int_id m , child_map = M.insert (internal_id i') [] $ case parent i of Nothing -> child_map m (Just p_id) -> M.insert p_id (insert_comment_ m (item_by_id m p_id) i') $ child_map m , all_items = insert_ after (all_items m) i' , next_id = n + 1 } ) where n = next_id m i' = i { internal_id = n } insert_comment_ :: Model -> Item -> Item -> [Int] insert_comment_ m p c = map internal_id (insert_ before (children m p) c) insert_ :: (Item -> Item -> Bool) -> [Item] -> Item -> [Item] insert_ _ [] y = [y] insert_ o s@(x:xs) y = if (x `o` y) then (x:(insert_ o xs y)) else (y:s) after :: Item -> Item -> Bool after a b = (created a) > (created b) before :: Item -> Item -> Bool before a b = (created a) < (created b) -- | Apply a structure-preserving function, i.e., one that does not -- change parent/child relationships or ids, to a specific item. alter :: (Item -> Item) -> Model -> Item -> IO Model alter f m i = do { ts <- now ; let i' = (f i) { updated = ts } ; return $ m { by_permatitle = M.insert (permatitle i') i' $ by_permatitle m , by_int_id = M.insert (internal_id i') i' $ by_int_id m , child_map = if (parent i == Nothing) then child_map m else M.insert p_id resort_siblings $ child_map m , all_items = insert_ after all_but i' } } where not_i = \item -> (internal_id item) /= (internal_id i) all_but = filter not_i $ all_items m p_id = unwrap $ parent i p = item_by_id m p_id resort_siblings = map internal_id (insert_ before (filter not_i $ children m p) i) cloak :: Model -> Item -> IO Model cloak = alter (\i -> i { visible = False }) uncloak :: Model -> Item -> IO Model uncloak = alter (\i -> i { visible = True }) permatitle_exists :: Model -> String -> Bool permatitle_exists = (flip M.member) . by_permatitle max_id :: Model -> Int max_id = fst . M.findMax . by_int_id post_by_permatitle :: Model -> String -> Item post_by_permatitle = (!) . by_permatitle maybe_post_by_permatitle :: Model -> String -> Maybe Item maybe_post_by_permatitle = (flip M.lookup) . by_permatitle item_by_id :: Model -> Int -> Item item_by_id = (!) . by_int_id children :: Model -> Item -> [Item] children m i = map (item_by_id m) ((child_map m) ! (internal_id i)) unwrap :: Maybe a -> a unwrap (Just x) = x unwrap Nothing = error "Can't unwrap nothing!" data Author = Author { name :: String, uri :: Maybe String, email :: Maybe String, show_email :: Bool } deriving ( Show,Read,Eq ) -- | General purpose runtime data structure for holding a post or -- comment. For a comment, a number of the fields will be ignored -- (e.g., comments and tags) until/if the presentation and syndication -- system gets fancier. data Item = Item { -- | an internal unique number for this post internal_id :: Int, -- | the kind of item that this represents kind :: Kind, -- | the title of the post, as it should be rendered on -- the web or inserted in an Atom feed; this should be a -- valid XHTML fragment. title :: XhtmlString, -- | the summary of the post, as it should be rendered on -- the web or intersted into an Atom feed; this should be -- a valid XHTML fragment. summary :: Maybe XhtmlString, -- | the body of the post as an XHTML fragment. This -- will be wrapped in an XHTML @<div>@ when rendered on -- the web or in a feed. body :: XhtmlString, -- | tags for the post, if any, expected to be in -- alphabetical order and consisting of letters, digits, -- dashes, and/or underscores. tags :: [String], -- | a generated UID for the post; this is expected to be -- suitable for use as an Atom GUID. The expectation is -- that it will be supplied by the implementation when -- the post is ingested. uid :: String, -- | a permanent title for the item, consisting of only -- lowercase letters, digits, and dashes. permatitle :: String, -- | the timestamp, as an ISO8601 datetime, when the post -- came into being. This is never blank and would be -- supplied by the implementation when the post is -- ingested. created :: ISO8601DatetimeString, -- | the timestamp, as an ISO8601 datetime, when the post -- was updated. Initially, this is equal to the value of -- the 'created' field. updated :: ISO8601DatetimeString, -- | the author of the post, expected to be hardwired to -- the author of the blog author :: Author, -- | whether or not the item is to be displayed. visible :: Bool, -- | this item's parent, if any. parent :: Maybe Int } deriving ( Show, Read, Eq ) -- | Compute a permalink for the item relative to the supplied base URL. permalink :: Model -> Item -- ^ the item -> String permalink m i = U.post (relative_url m i) relative_url :: Model -> Item -> String relative_url m = _form_permalink . (ancestors m) _form_permalink :: [Item] -> String _form_permalink [] = "" _form_permalink [i] = let s = permatitle i in if (kind i == Post) then "/" ++ s else "#" ++ s _form_permalink (i:is) = if (kind i == Post) then ("/" ++ permatitle i) ++ (_form_permalink is) else (_form_permalink is) ancestor_path :: Model -> Item -> String ancestor_path m i = concat . (intersperse "/") . (map permatitle) $ ancestors m i ancestors :: Model -> Item -> [Item] ancestors m i = ancestors_ m [] (Just $ internal_id i) ancestors_ :: Model -> [Item] -> Maybe Int -> [Item] ancestors_ _ is Nothing = is ancestors_ m is (Just i) = ancestors_ m (i':is) (parent i') where i' = item_by_id m i lastUpdated :: [Item] -> ISO8601DatetimeString lastUpdated ps = maximum (map updated ps) drop_invisible :: [Item] -> [Item] drop_invisible = filter visible sort_by_created :: [Item] -> [Item] sort_by_created = sortBy created_sort created_sort :: Item -> Item -> Ordering created_sort a b = compare (created a) (created b) sort_by_created_reverse :: [Item] -> [Item] sort_by_created_reverse = sortBy created_sort_reverse created_sort_reverse :: Item -> Item -> Ordering created_sort_reverse a b = compare (created b) (created a) -- | Filter a list of items according to a date fragment date_fragment_filter_ :: ISO8601DatetimeString -> [Item] -> [Item] date_fragment_filter_ s = filter ((s `isPrefixOf`) . created) -- | Filter a list of posts for those made in a specific year. year_filter :: Int -- ^ year -> [Item] -> [Item] year_filter y = date_fragment_filter_ $ show y -- | Filter a list of posts for those made in a specific month. month_filter :: Int -- ^ year -> Int -- ^ month -> [Item] -> [Item] month_filter y m | (0 < m) && (m < 13) = date_fragment_filter_ ((show y) ++ (pad_ m)) | otherwise = const [] -- | Filter a list of posts for those made on a specific day day_filter :: Int -- ^ year -> Int -- ^ month -> Int -- ^ day -> [Item] -> [Item] day_filter y m d = date_fragment_filter_ ((show y) ++ (pad_ m) ++ (pad_ d)) -- | Utility function to zero pad months and days in date expressions. pad_ :: Int -> String pad_ i | i < 10 = "-0" ++ (show i) | otherwise = ('-':(show i)) -- to do: make this faster using the sortedness. tags_filter :: [String] -> [Item] -> [Item] tags_filter t p = foldl (flip ($)) p (map tag_filter t) tag_filter :: String -> [Item] -> [Item] tag_filter t = filter ((t `elem`) . tags) plink_filterf :: String -> Item -> Bool plink_filterf = flip $ (==) . permatitle plink_filter :: String -> [Item] -> [Item] plink_filter = filter . plink_filterf ymd_plink_finder :: Int -> Int -> Int -> String -> [Item] -> [Item] ymd_plink_finder y m d t = (plink_filter t) . (day_filter y m d) all_posts :: Model -> [Item] all_posts = (filter (\x -> Post == kind x)) . all_items all_comments :: Model -> [Item] all_comments = (filter (\x -> Comment == kind x)) . all_items flatten :: Model -> [Item] -> [Item] flatten m = flatten_ (children m) flatten_ :: (a -> [a]) -> [a] -> [a] flatten_ _ [] = [] flatten_ f (i:is) = (i:(flatten_ f (f i))) ++ (flatten_ f is) concat_comments :: Model -> [Item] -> [Item] concat_comments m = (foldr (++) []) . (map $ children m) (</>) :: String -> String -> String s </> t = s ++ ('/':t) to_string :: Item -> String to_string i = concat [metadata i, "\n", body_block i, "\n", summary_block i] metadata :: Item -> String metadata i = unlines $ apply i [ ("internal_id",show . internal_id), ("parent", show . parent), ("title",title), ("tags",show_no_quotes . tags), ("permatitle",permatitle), ("kind",show . kind), ("uid",uid), ("created",created), ("updated",updated), ("author",show . author), ("visible",show . visible) ] show_no_quotes :: [String] -> String show_no_quotes = concat . (intersperse ", ") apply :: Item -> [(String,(Item -> String))] -> [String] apply _ [] = [] apply i (x:xs) = ((concat [fst x, ": ", (snd x) i]) : (apply i xs)) body_block :: Item -> String body_block i = concat ["--- START BODY ---\n", (body i), "\n--- END BODY ---\n"] summary_block :: Item -> String summary_block i | summary i == Nothing = "" | otherwise = concat ["--- START SUMMARY ---\n", (unwrap $ summary i), "\n--- END SUMMARY ---\n"] default_author :: Author default_author = Author C.author_name C.author_uri C.author_email True
In Scala:
- Formatted and highlighted by NetBeans Scala Plugin, exported via [File] -> [Print to HTML ...]
package org.aiotrade.blog.model import java.util.Calendar import org.aiotrade.blog.{Constants => C} object Entry { type XhtmlString = String class Model ( var by_permatitle: Map[String, Item], var by_int_id: Map[Int, Item], var child_map: Map[Int, List[Int]], var all_items: List[Item], var next_id: Int ) { // call by name def apply(block: => Unit) = {block; this} } abstract class Kind //deriving (Show, Read, Eq) case object Post extends Kind case object Comment extends Kind case object Trackback extends Kind case class Author ( var name: String, var uri : Option[String] = None, var email: Option[String] = None, var show_email: Boolean = false ) /** General purpose runtime data structure for holding a post or * comment. For a comment, a number of the fields will be ignored * (e.g., comments and tags) until/if the presentation and syndication * system gets fancier. */ case class Item ( // an internal unique number for this post var internalId: Int, // the kind of item that this represents var kind: Kind, // the title of the post, as it should be rendered on // the web or inserted in an Atom feed; this should be a // valid XHTML fragment. var title: XhtmlString, // the summary of the post, as it should be rendered on // the web or intersted into an Atom feed; this should be // a valid XHTML fragment. var summary: Option[XhtmlString], // the body of the post as an XHTML fragment. This // will be wrapped in an XHTML @<div>@ when rendered on // the web or in a feed. var body: XhtmlString, // tags for the post, if any, expected to be in // alphabetical order and consisting of letters, digits, // dashes, and/or underscores. var tags: List[String], // a generated UID for the post; this is expected to be // suitable for use as an Atom GUID. The expectation is // that it will be supplied by the implementation when // the post is ingested. var uid: String, // a permanent title for the item, consisting of only // lowercase letters, digits, and dashes. var permatitle: String, // the timestamp, as an ISO8601 datetime, when the post // came into being. This is never blank and would be // supplied by the implementation when the post is // ingested. var created: Long, // the timestamp, as an ISO8601 datetime, when the post // was updated. Initially, this is equal to the value of // the 'created' field. var updated: Long, //the author of the post, expected to be hardwired to // the author of the blog var author: Author, //whether or not the item is to be displayed. var visible: Boolean, //this item's parent, if any. var parent: Option[Int] ) { def apply(block: Item => Unit) = {block(this); this} } def empty = new Model(Map(), Map(), Map(), Nil, 0) def build_model(is: List[Item]) = is match { case Nil => empty case _ => val sortedIs = sort_by_created_reverse(is) val bid = Map() ++ sortedIs.map{x => (x.internalId -> x)} val n = bid.keySet.max new Model(Map() ++ sortedIs.map{x => (x.permatitle -> x)}, bid, buildChildMap(sortedIs), sortedIs, n + 1) } def buildChildMap(is: List[Item]) = buildChildMap_(Map() ++ is.map(_.internalId -> Nil), is) def buildChildMap_(map: Map[Int, List[Int]], is: List[Item]) = map ++ { for (i <- is if i.parent.isDefined) yield { // pid, cids definitions go into body // it's more efficient. val pid = i.parent.get val cids = map.getOrElse(pid, Nil) pid -> (i.internalId :: cids) } } /** Insert an item, presuming that all of its data other than internal identifier have been correctly set. */ def insert(m: Model, i: Item): (Item, Model) = { val n = m.next_id i.internalId = n (i, m { m.by_permatitle += (i.permatitle -> i) m.by_int_id += (n -> i) m.child_map = (i.parent match { case None => m.child_map case Some(p_id) => m.child_map + (p_id -> (insert_comment_(m, item_by_id(m)(p_id), i))) }) m.all_items = insert_(after, m.all_items, i) m.next_id = n + 1 } ) } def insert_comment_(m: Model, p: Item, c: Item): List[Int] = insert_(before, children(m)(p), c) map (_.internalId) def insert_(o: (Item, Item) => Boolean, is: List[Item], y: Item): List[Item] = is match { case Nil => List(y) case x :: xs => if (o(x, y)) x :: insert_(o, xs, y) else (y :: is) } def after (a: Item, b: Item): Boolean = a.created > b.created def before(a: Item, b: Item): Boolean = a.created < b.created /** * Apply a structure-preserving function, i.e., one that does not * change parent/child relationships or ids, to a specific item. */ def alter(f: (Item => Item), m: Model, i: Item): Model = {// -> IO Model val not_i = (item: Item) => item.internalId != i.internalId val all_but = m.all_items filter not_i val p_id = unwrap (i.parent) val p = item_by_id(m)(p_id) val resort_siblings = insert_(before, children(m)(p) filter not_i, i) map (_.internalId) val ts = System.currentTimeMillis val i1 = f(i) {_.updated = ts} m { m.by_permatitle += (i1.permatitle -> i1) m.by_int_id += (i1.internalId -> i1) m.child_map = i.parent match { case None => m.child_map case _ => m.child_map + (p_id -> resort_siblings) } m.all_items = insert_(after, all_but, i1) } } def cloak(m: Model, i: Item): Model = // -> IO Model alter (i => i {_.visible = false}, m, i) def uncloak(m: Model, i: Item): Model = // -> IO Model alter (i => i {_.visible = true}, m, i) def permatitle_exists(m: Model, p: String): Boolean = m.by_permatitle.contains(p) def max_id(m: Model): Int = m.by_int_id.keySet.max def post_by_permatitle(m: Model, p: String): Item = m.by_permatitle(p) def maybe_post_by_permatitle(m: Model, p: String): Option[Item] = m.by_permatitle.get(p) def item_by_id(m: Model)(id: Int): Item = m.by_int_id(id) def children(m: Model)(i: Item): List[Item] = m.child_map(i.internalId) map (item_by_id(m)) def unwrap[T](a: Option[T]): T = a match { case Some(x) => x case None => error("Can't unwrap none!") } def relative_url(m: Model, i: Item): String = _form_permalink(ancestors(m, i)) def _form_permalink(is: List[Item]): String = is match { case Nil => "" case i :: Nil => val s = i.permatitle if (i.kind == Post) "/" + s else "#" + s case i :: is => if (i.kind == Post) ("/" + i.permatitle) + _form_permalink(is) else _form_permalink(is) } def ancestors(m: Model, i: Item): List[Item] = ancestors_(m, Nil, Some(i.internalId)) def ancestors_(m: Model, is: List[Item], i_? : Option[Int]): List[Item] = i_? match { case None => is case Some(i) => val i1 = item_by_id(m)(i) ancestors_(m, i1 :: is, i1.parent) } def lastUpdated(ps: List[Item]): Long = ps map (_.updated) max def drop_invisible(is: List[Item]): List[Item] = is filter (_.visible) def sort_by_created(is: List[Item]): List[Item] = is sortWith created_sort _ def created_sort(a: Item, b: Item) = a.created < b.created def sort_by_created_reverse(is: List[Item]): List[Item] = is sortWith created_sort_reverse _ def created_sort_reverse(a: Item, b: Item) = b.created < a.created def date_fragment_filter_(is: List[Item], ts: Int*) = { val cal = Calendar.getInstance ts match { case Seq(y, m, d) => is filter {i => pad_(cal, i.created) match { case (`y`, `m`, `d`) => true case _ => false } } case Seq(y, m) => is filter {i => pad_(cal, i.created) match { case (`y`, `m`, _) => true case _ => false } } case Seq(y) => is filter {i => pad_(cal, i.created) match { case (`y`, _, _) => true case _ => false } } } } def year_filter(y: Int)(is: List[Item]): List[Item] = date_fragment_filter_(is, y) def month_filter(y: Int, m: Int)(is: List[Item]): List[Item] = date_fragment_filter_(is, y, m) def day_filter(y: Int, m: Int, d: Int)(is: List[Item]): List[Item] = date_fragment_filter_(is, y, m, d) def pad_(cal: Calendar, t: Long): (Int, Int, Int) = { cal.setTimeInMillis(t) import Calendar._ (cal.get(YEAR), cal.get(MONTH) + 1, cal.get(DAY_OF_MONTH)) } def tags_filter(ts: List[String])(is: List[Item]): List[Item] = is filter (i => (false /: ts) {_ || i.tags.contains(_)}) def tag_filter(t: String)(is: List[Item]): List[Item] = is filter (_.tags.contains(t)) def plink_filterf(p: String)(i: Item): Boolean = i.permatitle == p def plink_filter(p: String)(is: List[Item]): List[Item] = is filter plink_filterf(p) def ymd_plink_finder(y: Int, m: Int, d: Int, p: String)(is: List[Item]): List[Item] = plink_filter(p) (day_filter(y, m, d) (is)) def </> (s: String, t: String): String = s + '/' + t def all_posts(m: Model): List[Item] = m.all_items filter (_.kind == Post) def all_comments(m: Model): List[Item] = m.all_items filter (_.kind == Comment) def flatten(m: Model, is: List[Item]): List[Item] = flatten_(children(m), is) def flatten_[T](f: T => List[T], is: List[T]): List[T] = is match { case Nil => Nil case i :: is => i :: flatten_(f, f(i)) ::: flatten_(f, is) } def concat_comments(m: Model, is: List[Item]): List[Item] = ((is map children(m)) :\ List[Item]())(_ ::: _) def metadata(i: Item): String = apply(i, List(("internal_id", show(_.internalId)), ("parent", show(_.parent)), ("title", _.title), ("tags", show_no_quotes(_.tags)), ("permatitle", _.permatitle), ("kind", show(_.kind)), ("uid", _.uid), ("created", show(_.created)), ("updated", show(_.updated)), ("author", show(_.author)), ("visible", show(_.visible))) ) mkString "\n" // curring def show(f: Item => Any)(i: Item): String = f(i) toString def show_no_quotes(f: Item => List[String])(i: Item): String = f(i) mkString ", " def apply(i: Item, xs: List[(String, Item => String)]): List[String] = xs match { case Nil => Nil case x :: xs => (x._1 + ": " + x._2(i)) :: apply(i, xs) } def body_block(i: Item): String = "--- START BODY ---\n" + i.body + "\n--- END BODY ---\n" def summary_block(i: Item): String = i.summary match { case None => "" case Some(x) => "--- START SUMMARY ---\n" + x + "\n--- END SUMMARY ---\n" } val default_author = Author(C.author_name, C.author_uri, C.author_email, true) }
Progress of Migrating AIOTrade to Scala #2
My next step is to push AIOTrade to another level of UE, with hotkey for symbols, indicators etc, with better UI.
So far, my Scala adventure went almost smoothly, I'm thinking about the data exchange between client and data source, maybe the actors from Scala, LiftWeb or AKKA will bring some fresh atmosphere.
Progress of Migrating AIOTrade to Scala
Well, I've done most parts of migrating AIOTrade to Scala, not all features return yet. I gain lots of experiences of inter-op between Scala and Java, since AIOTrade has to be integrated into an existed Java framework NetBeans Platform. And also, whole project is now managed by Maven instead of Ant, which reduces lots of pain of dependencies upon crossing sub-projects.
This project is now hosted on kenai.com http://sf.net/projects/humaitrader, you can check out the code to get an overview of how to integrated Maven + Scala + NetBeans Modules. Of course, all were done with NetBeans Scala plugin.
LOC of this project so far:
$ ./cloc.pl --read-lang-def=lang_defs.txt ~/myprjs/aiotrade.kn/opensource/ 677 text files. 617 unique files. 154 files ignored. http://cloc.sourceforge.net v 1.08 T=3.0 s (167.7 files/s, 21373.7 lines/s) ------------------------------------------------------------------------------- Language files blank comment code scale 3rd gen. equiv ------------------------------------------------------------------------------- Scala 353 7981 16301 27180 x 1.36 = 36964.80 Java 43 1148 833 6946 x 1.36 = 9446.56 XML 104 231 389 2414 x 1.90 = 4586.60 Bourne Shell 2 81 81 488 x 3.81 = 1859.28 HTML 1 7 15 26 x 1.90 = 49.40 ------------------------------------------------------------------------------- SUM: 503 9448 17619 37054 x 1.43 = 52906.64 -------------------------------------------------------------------------------
A screen snapshot:
Scala Plugin for NetBeans - Rewrite in Scala #2: Supports Java/Scala Mixed Project
Java/Scala mixed project was supported partly once before, but I cannot remember when this feature got lost. Anyway, as one progressing of the rewritten NetBeans Scala plugin, I just got this feature working properly halfway: now Java source is visible to Scala one, but not vice versa. To got Scala source also visible for Java source, would pay for efficiency, since the compiler may need to be pushed to a deep phase, and mapped to Java classes/methods, I need more time to think about that.
Below is a screenshot that a JavaDog? is called from a Scala source file:
Scala Corner Case#2: "Nothing" Can not Be Cast and Assigned to a val/var
There is a Java class which takes type parameter T, T is any type:
package dcaoyuan.test; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class AList<T> implements Iterator<T> { List<T> a = new ArrayList<T>(); { a.add((T) "string"); } Iterator<T> itr = a.iterator(); public boolean hasNext() { return itr.hasNext(); } public T next() { return itr.next(); } public void remove() { throw new UnsupportedOperationException("Not supported yet."); } }
Which implemented java.util.Iterator with type parametered T next(). Notice here next() will return T
The above code simulates a case, that an existed Java lib may return an instance that has type parameter erased, on run-time.
Now let's try to call it in Scala:
package dcaoyuan.test object Main { def main(args: Array[String]) :Unit = { testAList_work testAList_unwork } def testAList_work :Unit = { val a :AList[_] = new AList // a is inferred as AList[Any] while (a.hasNext) { val e = a.next println(e) } } def testAList_unwork :Unit = { val a = new AList // a is inferred as AList[Nothing] while (a.hasNext) { // now a.next is Nothing, can not cast to "val e", which should at least be Any val e = a.next // will throw java.lang.ClassCastException println(e) } } }
I wrote two functions: "testAList_work" and "testAList_unwork"
The issue is in testAList_unwork, as a simple "val a = new AList", "a" will be inferred by scalac as AList[Nothing], so, "a.next" will return an instance of type Nothing, then, "val e = a.next" will throw java.lang.ClassCastException
My question is, should this corner case be checked by scalac as an error or warning? So I do not need to dig the clause when it happens on run-time.
Scala Corner Case#1: Implement Method of Java Interface with Type Parameter Omitted by It's Sub-Class
The progress of rewriting Erlang plugin for NetBeans in Scala has reached a phase, that the Editor itself works smooth and better than ErlyBird now, the next step is to integrate an Erlang project management and index the modules/functions of OTP/project to provide smart auto-completion.
Now Scala has been proved that it can be integrated into an existed large Java based framework (NetBeans IDE here) without no much problems, I'll begin to rewrite Scala plugin in Scala soon.
Although in most cases, Scala can call existed Java code or classes smoothly, there are still some corner cases. I'll record these corner cases in blogs. Here's the first one which I also posted on scala-user mailing-list, but have not yet got final answer.
Let's begin with a detailed example:
There is a Java interface A:
public interface A<T extends String> { void run(T t); }
Which has a type parameter <T extends String> and abstract method run(T t)
Then a Java abstract class B extended A. But, B, as it, omitted type parameter from A. This is unsafe but valid in Java:
public abstract class B implements A { public String me() { return "I'm B"; } }
Assume above classes A and B have been compiled under javac, and packed in a jar library, and I can not patch it anymore. Now I need to write a class S in Scala which should extend B:
class S extends B { override def run[T <: String](t:T) = {println(t)} }
scalac will complain as:
/Users/dcaoyuan/NetBeansProjects/ScalaTestCase/src/S.scala:1: error: class S needs to be abstract, since method run in trait A of type (T)Unit is not defined class S extends B { /Users/dcaoyuan/NetBeansProjects/ScalaTestCase/src/S.scala:3: error: method run overrides nothing def run[T <: String](t:T) = {println(t)}
I than tried "forSome" type:
class S extends B { override def run(t:T forSome {type T <: String}) = {println(t)} }
The code still did not work.
It seems that, since B omitted A's type parameter T, I have no way to get what is T, and can not successfully implement "run(T t)" method.
I also tried other forms of "forSome" usages, and always failed.
But I think Scala can always be saved with mixed Java/Scala code in such corner case, that's what I believed. So, I thought about it later when my brain was spare, and finally got a solution:
I wrote another Java abstract class B1 which extends B and pretended to have implemented "run(T t)", but actually called another new abstract method "runImpl(String t)"
public abstract class B1 extends B { public void run(String t) { runImpl(t); } public abstract void runImpl(String t); }
Now I can let Scala S extends B1 and implement "runImpl(String t)" instead of extending B and implementing "run(T t)".
class S extends B1 { override def runImpl(t:String) = {println(t)} }
Yes, scalac won't complain about "runImpl(t:String)" at all, and I got S successfully extends B by bridge class B1.
But I still hope scalac can resolve it directly, with a warning message instead of failing to compile it.
Thinking in Scala vs Erlang
Keeping Erlang in mind, I've coded two months in Scala, I'm thinking something called "Scala vs Erlang", I wrote some benchmark code to prove me (the code and result may be available someday), and I'd like to do some gradually summary on it in practical aspect. These opinions may be or not be correct currently due to lacking of deep experience and understanding, but, anyway, I need to record them now and correct myself with more experiences and understanding got on both Scala and Erlang.
Part I. Syntax
Keeping Erlang in mind, I've coded two months in Scala, I'm thinking something called "Scala vs Erlang", I wrote some benchmark code to prove me (the code and result may be available someday), and I'd like to do some gradually summary on it in practical aspect. These opinions may be or not be correct currently due to lacking of deep experience and understanding, but, anyway, I need to record them now and correct myself with more experiences and understanding got on both Scala and Erlang.
Part I. Syntax
List comprehension
Erlang:
Lst = [1,2,3,4], [X + 1 || X <- Lst], lists:map(fun(X) -> X + 1 end, Lst)
Scala:
val lst = List(1,2,3,4) for (x <- lst) yield x + 1 lst.map{x => x + 1} lst.map{_ + 1} // or place holder
Pattern match
Erlang:
case X of {A, B} when is_integer(A), A > 1 -> ok; _ -> error end, {ok, [{A, B} = H|T]} = my_function(X)
Scala:
x match { case (a:Int, b:_) if a > 1 => OK // can match type case _ => ERROR } val ("ok", (h@(a, b)) :: t) = my_function(x)
List, Tuple, Array, Map, Binary, Bit
Erlang:
Lst = [1, 2, 3] %% List [0 | Lst] %% List concat {1, 2, 3} %% Tuple <<1, 2, "abc">> %% Binary %% no Array, Map syntax
Scala:
val lst = List(1, 2, 3) // List 0 :: lst // List concat (1, 2, 3) // Tuple Array(1, 2, 3) // Array Map("a" -> 1, "b" -> 2) // Map // no Binary, Bit syntax
Process, Actor
Erlang:
the_actor(X) -> receive ok -> io:format("~p~n", [X]); I -> the_actor(X + I) %% needs to explicitly continue loop end. P = spawn(mymodule, the_actor, [0]) P ! 1 P ! ok
Scala I:
class TheActor(x:Int) extends Actor { def act = loop { react { case "ok" => println(x); exit // needs to explicitly exit loop case i:Int => x += i } } } val a = new TheActor(0) a ! 1 a ! "ok"
Scala II:
val a = actor { def loop(x:Int) = { react { case "ok" => println(x) case i:Int => loop(x + i) } } loop(0) } a ! 1 a ! "ok"
Part II. Processes vs Actors
Something I
Erlang:
- Lightweight processes
- You can always (almost) create a new process for each new comer
- Scheduler treats all processes fairly
- Share nothing between processes
- Lightweight context switch between processes
- IO has been carefully delegated to independent processes
Scala:
- Active actor is delegated to JVM thread, actor /= thread
- You can create a new actor for each new comer
- But the amount of real workers (threads) is dynamically adjusted according to the processing time
- The later comers may be in wait list for further processing until a spare thread is available
- Share nothing or share something upon you decision
- Heavy context switch between working threads
- IO block is still pain unless good NIO framework (Grizzly?)
Something II
Erlang:
- Try to service everyone simultaneously
- But may loss service quality when the work is heavy, may time out (out of service)
- Ideal when processing cost is comparable to context switching cost
- Ideal for small message processing in soft real-time
- Bad for massive data processing, and cpu-heavy work
Scala:
- Try to service limited number of customers best first
- If can not service all, the later comers will be put in waiting list and may time out (out of service)
- It's difficult for soft real-time on all coming concurrent customers
- Ideal when processing cost is far more than context switching cost (context switch time is in μs on modern JVM)
- When will there be perfect NIO + Actor library?
New Scala Plugin for NetBeans 6.5 RC2 Is Packed
I packed a new Scala plugin, and tried several times to upload it to NetBeans' Plugins Portal and could not get job successfully done. So I uploaded it to sourceforge.net instead. The zip file is located at: [ https://sourceforge.net/project/showfiles.php?group_id=192439&package_id=256544&release_id=638797 scala-plugin-081106.zip
NetBeans' trunk has been targeting 7.0 now, the plugin on Last Development Build update center is no longer compatible with 6.5 RC2. That is, you should ignore my previous blog talked about installing plugin on 6.5 RC2, if you are using 6.5 RC2, you should download and install this one.
Nightly built version users can get latest plugin via NetBeans' plugin management feature as normal.
For more information, please visit http://wiki.netbeans.org/Scala
This version fixed various bugs, and with some enhancements. The bundling Scala runtime is 2.7.2RC6, with NetBeans' maven plugin, it works on newest Liftweb project.
Bug reports are always welcome.
Click on the picture to enlarge it
RPC Server for Erlang, In Scala
There has been Java code in my previous blog: RPC Server for Erlang, In Java, I'm now try to rewrite it in Scala. With the pattern match that I've been familiar with in Erlang, write the Scala version is really a pleasure. You can compare it with the Java version.
I do not try Scala's actor lib yet, maybe late.
And also, I should port Erlang's jinterface to Scala, since OtpErlangTuple?, OtpErlangList? should be written in Scala's Tuple and List.
The code is auto-formatted by NetBeans' Scala plugin, and the syntax highlighting is the same as in NetBeans, oh, not exactly.
/* * RpcMsg.scala * */ package net.lightpole.rpcnode import com.ericsson.otp.erlang.{OtpErlangAtom, OtpErlangList, OtpErlangObject, OtpErlangPid, OtpErlangRef, OtpErlangTuple} class RpcMsg(val call:OtpErlangAtom, val mod :OtpErlangAtom, val fun :OtpErlangAtom, val args:OtpErlangList, val user:OtpErlangPid, val to :OtpErlangPid, val tag :OtpErlangRef) { } object RpcMsg { def apply(msg:OtpErlangObject) : Option[RpcMsg] = msg match { case tMsg:OtpErlangTuple => tMsg.elements() match { /* {'$gen_call', {To, Tag}, {call, Mod, Fun, Args, User}} */ case Array(head:OtpErlangAtom, from:OtpErlangTuple, request:OtpErlangTuple) => if (head.atomValue.equals("$gen_call")) { (from.elements, request.elements) match { case (Array(to :OtpErlangPid, tag:OtpErlangRef), Array(call:OtpErlangAtom, mod :OtpErlangAtom, fun :OtpErlangAtom, args:OtpErlangList, user:OtpErlangPid)) => if (call.atomValue.equals("call")) { Some(new RpcMsg(call, mod, fun, args, user, to, tag)) } else None case _ => None } } else None case _ => None } case _ => None } }
/* * RpcNode.scala * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package net.lightpole.rpcnode import com.ericsson.otp.erlang.{OtpAuthException, OtpConnection, OtpErlangAtom, OtpErlangExit, OtpErlangObject, OtpErlangString, OtpErlangTuple, OtpSelf} import java.io.IOException import java.net.InetAddress import java.net.UnknownHostException import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.logging.Level import java.util.logging.Logger trait Cons { val OK = new OtpErlangAtom("ok") val ERROR = new OtpErlangAtom("error") val STOPED = new OtpErlangAtom("stoped") val THREAD_POOL_SIZE = 100 } /** * * Usage: * $ erl -sname clientnode -setcookie mycookie * (clientnode@cmac)> rpc:call(xnodename@cmac, xnode, parse, []). * * @author Caoyuan Deng */ abstract class RpcNode(xnodeName:String, cookie:String, threadPoolSize:Int) extends Cons { def this(xnodeName:String, cookie:String) = this(xnodeName, cookie, 100) private var xSelf:OtpSelf = _ private var sConnection:OtpConnection = _ private var execService:ExecutorService = Executors.newFixedThreadPool(threadPoolSize) private val flags = Array(0) startServerConnection(xnodeName, cookie) loop def startServerConnection(xnodeName:String, cookie:String ) = { try { xSelf = new OtpSelf(xnodeName, cookie); // The node then publishes its port to the Erlang Port Mapper Daemon. // This registers the node name and port, making it available to a remote client process. // When the port is published it is important to immediately invoke the accept method. // Forgetting to accept a connection after publishing the port would be the programmatic // equivalent of false advertising val registered = xSelf.publishPort(); if (registered) { System.out.println(xSelf.node() + " is ready."); /** * Accept an incoming connection from a remote node. A call to this * method will block until an incoming connection is at least * attempted. */ sConnection = xSelf.accept(); } else { System.out.println("There should be an epmd running, start an epmd by running 'erl'."); } } catch { case ex:IOException => case ex:OtpAuthException => } } def loop : Unit = { try { val msg = sConnection.receive val task = new Runnable() { override def run = RpcMsg(msg) match { case None => try { sConnection.send(sConnection.peer.node, new OtpErlangString("unknown request")); } catch { case ex:IOException => } case Some(call) => val t0 = System.currentTimeMillis flag(0) = processRpcCall(call) System.out.println("Rpc time: " + (System.currentTimeMillis() - t0) / 1000.0) } } execService.execute(task) if (flag(0) == -1) { System.out.println("Exited") } else loop } catch { case ex:IOException => loop case ex:OtpErlangExit => case ex:OtpAuthException => } } /** @throws IOException */ def sendRpcResult(call:RpcMsg, head:OtpErlangAtom, result:OtpErlangObject) = { val tResult = new OtpErlangTuple(Array(head, result)) // Should specify call.tag here val msg = new OtpErlangTuple(Array(call.tag, tResult)) // Should specify call.to here sConnection.send(call.to, msg, 1024 * 1024 * 10) } /** @abstact */ def processRpcCall(call:RpcMsg) : Int } object RpcCall { def getShortLocalHost : String = getLocalHost(false) def getLongLocalHost : String = getLocalHost(true) def getLocalHost(longName:Boolean) : String = { var localHost = "localhost" try { localHost = InetAddress.getLocalHost.getHostName; if (!longName) { /* Make sure it's a short name, i.e. strip of everything after first '.' */ val dot = localHost.indexOf(".") if (dot != -1) localHost = localHost.substring(0, dot) } } catch { case ex:UnknownHostException => } localHost } }
FOR, WHILE Is Too Easy, Let's Go Looping
With several 10k code in Erlang, I'm familiar with functional style coding, and I found I can almost rewrite any functions in Erlang to Scala, in syntax meaning.
Now, I have some piece of code written in Java, which I need to translate them to Scala. Since "for", "while", or "do" statement is so easy in Java, I can find a lot of them in Java code. The problem is, should I keep them in the corresponding "for", "while", "do" in Scala, or, as what I do in Erlang, use recursive function call, or, "loop"?
I sure choose to loop, and since Scala supports recursive function call on functions defined in function body (Erlang doesn't), I choose define these functions' name as "loop", and I tried to write code let "loop" looks like a replacement of "for", "while" etc.
Here's a piece of code that is used to read number string and convert to double, only piece of them.
The Java code:
public class ReadNum { private double readNumber(int fstChar, boolean isNeg) { StringBuilder out = new StringBuilder(22); out.append(fstChar); double v = '0' - fstChar; // the maxima length of number stirng won't exceed 22 for (int i = 0; i < 22; i++) { int c = getChar(); switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = v * 10 - (c - '0'); out.append(c); continue; case '.': out.append('.'); return readFrac(out, 22 - i); case 'e': case 'E': out.append(c); return readExp(out, 22 - i); default: if (c != -1) backup(1); if (!isNeg) return v; else return -v } } return 0; } }
The Scala code:
class ReadNum { private def readNumber(fstChar:Char, isNeg:Boolean) :Double = { val out = new StringBuilder(22) out.append(fstChar) val v:Double = '0' - fstChar def loop(c:Char, v:Double, i:Int) :Double = c match { // the maxima length of number stirng won't exceed 22 case _ if i > 21 => 0 case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => out.append(c) val v1 = v * 10 - (c - '0') loop(getChar, v1, i + 1) case '.' => out.append('.') readFrac(out, 22 - i) case 'e' | 'E' => out.append(c) readExp(out, 22 - i) case _ => if (c != -1) backup(1) if (isNeg) v else -v }; loop(getChar, v, 1) } }
As you can see in line 25, the loop call is put at the position immediately after the "loop" definition, following "}; ", I don't put it to another new line, it makes me aware of the "loop" function is just used for this call.
And yes, I named all these embedded looping function as "loop", every where.
An Example Syntax in Haskell, Erlang and Scala
>>> Updated Oct 16:
I found some conventions of coding style make code more readable for Scala. For example, use <pre>{ x => something } instead of (x => dosomething)</pre> <p>for anonymous function; Use x, y, z as the names of arguments of anonymous functions; Put all modifiers to the ahead line etc. That makes me can pick up these functions by eyes quickly.</br>
======
It's actually my first time to write true Scala code, sounds strange? Before I write Scala code, I wrote a Scala IDE first, and am a bit familiar with Scala syntax now. And I've got about 1.5 year experience on Erlang, it began after I wrote ErlyBird.
Now it's time to write some real world Scala code, I choose to port Paul R. Brown's perpubplat blog engine, which is written in Haskell. And I have also some curiosities on how the syntax looks in Erlang, so I tried some Erlang code too.
Here's some code piece of entry module in Haskell, Erlang and Scala:
Original Haskell code piece
empty :: Model empty = Model M.empty M.empty M.empty [] 0 build_model :: [Item] -> Model build_model [] = empty build_model items = Model (map_by permatitle sorted_items) bid (build_child_map sorted_items) (sorted_items) (n+1) where sorted_items = sort_by_created_reverse items bid = (map_by internal_id sorted_items) n = fst . M.findMax $ bid build_child_map :: [Item] -> M.Map Int [Int] build_child_map i = build_child_map_ (M.fromList $ (map (\x -> (internal_id x,[])) i)) i -- Constructed to take advantage of the input being in sorted order. build_child_map_ :: M.Map Int [Int] -> [Item] -> M.Map Int [Int] build_child_map_ m [] = m build_child_map_ m (i:is) = if (parent i == Nothing) then build_child_map_ m is else build_child_map_ (M.insertWith (++) (unwrap $ parent i) [internal_id i] m) is sort_by_created_reverse :: [Item] -> [Item] sort_by_created_reverse = sortBy created_sort_reverse created_sort_reverse :: Item -> Item -> Ordering created_sort_reverse a b = compare (created b) (created a)
In Erlang:
% @spec empty :: Model empty() -> #model{}. % @spec build_model :: [Item] -> Model build_model([]) -> empty(); build_model(Is) -> SortedIs = sort_by_created_reverse(Is), Bid = dict:from_list([{I#item.internal_id, I} || I <- SortedIs]), N = lists:max(dict:fetch_keys(Bid)), #model{by_permatitle = dict:from_list([{X#item.permatitle, X} || X <- SortedIs]), by_int_id = Bid, child_map = build_child_map(SortedIs), all_items = SortedIs, next_id = N + 1}. % @spec build_child_map :: [Item] -> M.Map Int [Int] build_child_map(Is) -> build_child_map_(dict:from_list(lists:map(fun (X) -> {X#item.internal_id, []} end), Is), Is). %% Constructed to take advantage of the input being in sorted order. % @spec build_child_map_ :: M.Map Int [Int] -> [Item] -> M.Map Int [Int] build_child_map_(D, []) -> D; build_child_map_(D, [I|Is]) -> case I#item.parent of undefined -> build_child_map_(D, Is); P_Id -> build_child_map_(dict:append(unwrap(P_Id), I#item.internal_id, D), Is) end. % @spec sort_by_created_reverse :: [Item] -> [Item] sort_by_created_reverse(Is) -> lists:sort(fun created_sort_reverse/2, Is). % @spec created_sort_reverse :: Item -> Item -> Ordering created_sort_reverse(A, B) -> compare(B#item.created, A#item.created).
In Scala
object Entry { def empty = new Model() def build_model(is:List[Item]) = is match { case Nil => empty case _ => val sortedIs = sortByCreatedReverse(is) val bid = Map() ++ sortedIs.map{ x => (x.internalId -> x) } val n = bid.keys.toList.sort{ (x, y) => x > y }.head // max new Model(Map() ++ sortedIs.map{ x => (x.permatitle -> x) }, bid, buildChildMap(sortedIs), sortedIs, n + 1) } def buildChildMap(is:List[Item]) = buildChildMap_(Map() ++ is.map{ x => (x.internalId -> Nil) }, is) private def buildChildMap_(map:Map[Int, List[Int]], is:List[Item]) = { map ++ (for (i <- is if i.parent.isDefined; pid = i.parent.get; cids = map.getOrElse(pid, Nil)) yield (pid -> (cids + i.internalId))) } def sortByCreatedReverse(is:List[Item]) = is.sort{ (x, y) => x.created before y.created } }
>>> Updated Oct 16:
Per Martin's suggestion, the above code can be written more Scala style (the reasons are in the comments). Thanks, Martin.
object Entry { def empty = new Model() def build_model(is:List[Item]) = is match { case Nil => empty case _ => val sortedIs = sortByCreatedReverse(is) val bid = Map() ++ sortedIs.map{ x => (x.internalId -> x) } // use predefined max in Iterable val n = Iterable.max(bid.keys.toList) new Model(Map() ++ sortedIs.map{ x => (x.permatitle -> x) }, bid, buildChildMap(sortedIs), sortedIs, n + 1) } // you can use a wildcard anonymousfunction here def buildChildMap(is:List[Item]) = buildChildMap_(Map() ++ is.map(_.internalId -> Nil), is) private def buildChildMap_(map:Map[Int, List[Int]], is:List[Item]) = map ++ { // rewrite for so that definitions go into body -- it's more efficient. for (i <- is if i.parent.isDefined) yield { val pid = i.parent.get val cids = map.getOrElse(pid, Nil) pid -> (cids + i.internalId) } } // you can use a wildcard anonymous function here def sortByCreatedReverse(is:List[Item]) = is.sort{ _.created before _.created } }
======
I use ErlyBird for Erlang coding, and Scala for NetBeans for Scala coding. The experience seems that IDE is much aware of Scala, and I can get the typing a bit faster than writing Erlang code.
If you are not familiar with all these 3 languages, which one looks more understandable?
Scala for Netbeans Beta Is Ready, Working with NetBeans 6.5 Beta
>>> Updated Aug 15:
For Windows Vista users: There is a known bug #135547 that may have been fixed in trunk but not for NetBeans 6.5 Beta, which causes exception of "NullPointerException at org.openide.filesystems.FileUtil.normalizeFileOnWindows" when create Scala project. If you are Vista user and like to have a try on Scala plugins, you may need to download a recent nightly build version of NetBeans. Since I have none Vista environment, I'm not sure about above message.
======
I'm pleased to announce that the first beta of Scala for NetBeans is released, followed NetBeans 6.5 beta releasing. The availability and installation instructions can be found at http://wiki.netbeans.org/Scala.
Features:
- Full featured Scala editor
- syntax and semantic coloring
- outline navigator
- code folding
- mark occurrences
- go to declaration
- instant rename
- indentation
- formatting
- pair matching
- error annotations
- code completion
- Project management (build/run/debug project)
- Debugger
- Interactive console
- JUnit integration
- Maven integration (works with [ http://www.liftweb.net Lift Web Framework)
There are some known issues. Bug reports are welcome.
Installation on NetBeans 6.5 beta:
- Get the NetBeans 6.5 beta or later version from: > http://download.netbeans.org/netbeans/6.5/beta/
- Get the Scala plugins beta binary from: http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=11854
- Unzip Scala plugin binary to somewhere
- Open NetBeans, go to "Tools" -> "Plugins", click on "Downloaded" tab title, click on "Add Plugins..."
button, choose the directory where the Scala plugins are unzipped, select all listed *.nbm files, following the instructions. Restart IDE.
Developing IDE Based on GSF for NetBeans#1 - Minimal Support
There has been GSF (Generic Scripting Framework) which is Tor's working derived and abstracted from Java supporting code, and, the base of Ruby/JavaScript support for NetBeans.
So, how to develop an IDE based on GSF for NetBeans? I'd like to share some experiences in this series of articles, a series of outline description, without too much code and details, for detailed information, please go into the source code on hg.netbeans.org
I. Minimal Support - Highlighting
To implement a minimal support of your editor, you need to implement/extend following classes/interface:
public class ScalaLanguage implements GsfLanguage public class ScalaMimeResolver extends MIMEResolver public enum ScalaTokenId implements TokenId public class ScalaLexer implements Lexer<ScalaTokenId>
Where ScalaLexer is the token scanner of your language.
Then, register your language in layer.xml:
<filesystem> <folder name="Editors"> <folder name="text"> <folder name="x-scala"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.scala.editing.Bundle"/> <file name="language.instance"> <attr name="instanceCreate" methodvalue="org.netbeans.modules.scala.editing.lexer.ScalaTokenId.language"/> <attr name="instanceOf" stringvalue="org.netbeans.api.lexer.Language"/> </file> <folder name="FontsColors"> <folder name="NetBeans"> <folder name="Defaults"> <file name="coloring.xml" url="fontsColors.xml"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.scala.editing.Bundle"/> </file> </folder> </folder> </folder> <folder name="CodeTemplates"> <folder name="Defaults"> <file name="codetemplates.xml" url="codetemplates.xml"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.scala.editing.Bundle"/> </file> </folder> </folder> <folder name="Keybindings"> <folder name="NetBeans"> <folder name="Defaults"> <file name="org-netbeans-modules-scala-editing-keybindings.xml" url="DefaultKeyBindings.xml"/> </folder> </folder> </folder> </folder> </folder> </folder> <folder name="GsfPlugins"> <folder name="text"> <folder name="x-scala"> <file name="language.instance"> <attr name="instanceOf" stringvalue="org.netbeans.modules.gsf.api.GsfLanguage"/> <attr name="instanceClass" stringvalue="org.netbeans.modules.scala.editing.ScalaLanguage"/> </file> </folder> </folder> </folder> <folder name="Loaders"> <folder name="text"> <folder name="x-scala"> <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/netbeans/modules/scala/editing/resources/scala16x16.png"/> <attr name="iconBase" stringvalue="org/netbeans/modules/scala/editing/resources/scala16x16.png"/> <folder name="Actions"> <file name="OpenAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.OpenAction"/> <attr name="position" intvalue="100"/> </file> <file name="Separator1.instance"> <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/> <attr name="position" intvalue="200"/> </file> <file name="CutAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.CutAction"/> <attr name="position" intvalue="300"/> </file> <file name="CopyAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.CopyAction"/> <attr name="position" intvalue="400"/> </file> <file name="PasteAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.PasteAction"/> <attr name="position" intvalue="500"/> </file> <file name="Separator2.instance"> <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/> <attr name="position" intvalue="600"/> </file> <file name="NewAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.NewAction"/> <attr name="position" intvalue="700"/> </file> <file name="DeleteAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.DeleteAction"/> <attr name="position" intvalue="800"/> </file> <file name="RenameAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.RenameAction"/> <attr name="position" intvalue="900"/> </file> <file name="Separator3.instance"> <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/> <attr name="position" intvalue="1000"/> </file> <file name="SaveAsTemplateAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.SaveAsTemplateAction"/> <attr name="position" intvalue="1100"/> </file> <file name="Separator4.instance"> <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/> <attr name="position" intvalue="1200"/> </file> <file name="FileSystemAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.FileSystemAction"/> <attr name="position" intvalue="1300"/> </file> <file name="Separator5.instance"> <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/> <attr name="position" intvalue="1400"/> </file> <file name="ToolsAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.ToolsAction"/> <attr name="position" intvalue="1500"/> </file> <file name="PropertiesAction.instance"> <attr name="instanceClass" stringvalue="org.openide.actions.PropertiesAction"/> <attr name="position" intvalue="1600"/> </file> </folder> </folder> </folder> </folder> </filesystem>
Don't forget to prepare all these resource files that registered in above layer.xml, such as scala16x16.png etc
After that, write an one-line service descriptor org.openide.filesystems.MIMEResolver under META-INF/services, which looks like
org.netbeans.modules.scala.editing.ScalaMimeResolver
That's it.
Scala for NetBeans Screenshot#7: Working on Debug II
So, I've found the cause of that can't add breakpoints on context of object. By setting the enclosing class name of object as object's name + "$", I can now add breakpoints upon object.
To reach here, I have to write an EditorContextImpl? for Scala, which will get all compilation information from Scala semantic analyzer. But, to get all debugging features working, I have still to process a bit more conditions. After that, I'll release these modules for public trying.
Click on the picture to enlarge it
Scala Support for NetBeans Screenshot#1: Syntax Highlighting and Scala Project
Scala editor module has been integrated with existing Scala Project module. The xml syntax is almost supported. There are still a little bit complex syntax not be supported yet.
I hope a downloadable Scala modules package can be ready in one week, so you can get it from the NetBeans update center.
BTW, new version of ErlyBird is not ready yet, I'm waiting for fixing of some issues in NetBeans' Generic Languages Framework module.
Screen snapshot: (Click to enlarge)