14 ms·
Streem – a new programming language from Matz
- glibgil 12y agoWhatever, https://github.com/ekmett/machines https://github.com/ekmett/machines https://github.com/scalaz/scalaz-stream https://github.com/scalaz/scalaz-stream
- vessenes 12y agoWhy do most implementations of FizzBuzz special case % 15? I haven't ever really understood this. Maybe it's just my math-y background, but it always seemed to me you should just check mod 3 and mod 5 without an else between them, concatenating Fizz and Buzz. Can anyone else comment on this? Most canonical FizzBuzz programs special case 15, and I don't get it.
- rudolf0 12y agoEasier to understand for beginners, I suppose. For the record, I have utterly no math background (hardly passed algebra), but I also agree that checking only 3 and 5 is the better solution and is how I've always written FizzBuzz.
- donmcc 12y agoIt's to neatly handle the line break.
- vessenes 12y agoI suppose it's a style issue as to whether an extra mod is better than checking if one of the triggers has passed. I would propose it's more DRY to do it the short way though.
- pcmonk 12y agoIt's not any shorter. The extra check as to whether or not to print the line break cancels out the mod 15 check. In my opinion, it's cleaner to have three conditionals of the same type than two checking mods and a third checking the OR of the first two. Of course, it can be actually shorter with a goto.
- wenderen 12y agoI do that too. It seems more in the spirit of the problem. For an alternate problem, in which you are asked to print strings a, b and c (!= a concat b) if the number is divisible by 3, 5 and 15 respectively, it makes sense to special case 15.
- deleted 12y ago[deleted]
- PhearTheCeal 12y agonoice, here's my version print("\n".join([("FIZZ"*(i%3==0)+"BUZZ"*(i%5==0)) or str(i) for i in range(1,101)]))
- kaonashi 12y agoI would hope never to find a bomb like that in the code I'm maintaining.
- SwellJoe 12y agoReally? I find that one line far more pleasant than the Python example above it, with variables being assigned, etc. Both are quite clear in their intention, I think, assuming you recognize "join". I don't even recognize the language of the one-liner, but it makes perfect sense to me as a reader. It looks like a Perlish solution, but the string method is strange, I think. Maybe Perl 6 or Ruby?
- cgh 12y agoIt's Python as well.
- nostrademons 12y agoThe one-liner is valid Python. It's slightly non-idiomatic in that it uses a list-comprehension where a generator expression would do, and uses range instead of xrange (in Python 2.x; in 3.x range is the idiomatic alternative).
- nightpool 12y agoit's also non-idomatic in that is uses string-multiplication and ==0 where an "if not" ternary expression would be much cleaner.
- masukomi 12y agojust because 15 HAPPENS to be a concatenation of the output of 3 and the output of 5 today doesn't mean it will be tomorrow. If I said "say Fizz for multiples of 3, Buzz for multiples of 5, and 'this is a silly coding problem' for multiples of 3 and 5 then you'd have to rewrite your code. Some of us know that clients ALWAYS change their minds, specs are rarely equivalent to the end result, and code against future changes that are trivial to account for in advance.
- Doji 12y agoFor any implementation you can manufacture an example which will require a person to re-structure their code. Additionally, using %15 is not DRY. If the spec changes from saying "Fizz" on multiples of 3 to saying "Fizz" on multiples of 4, then you will have to also update 15->20. If you forget to do this, you have a bug. The correct implementation is dependent on the problem's context, and such context is not available with the FizzBuzz problem.
- tracker1 12y agoIf you're using something like JS, where strings are truthy, you can always do something like this... https://gist.github.com/tracker1/d58fa1f83ab17d37eb2c https://gist.github.com/tracker1/d58fa1f83ab17d37eb2c
- masukomi 12y agoPart of my point (which i obviosuly didn't communicate well) is that Fizz concatenated with Buzz is a premature optimization. It's the developer taking advantage of a linguistic coincience. The instructions are to output 3 different strings based on %3, %5, or %3 and %5. I have never seen a set of fizzbuzz instructions that actually specified that the last option should be a concatenation of the 1st two. It's always specified as a 3rd string that people independently notice is a concatenation of the two.
- Legion 12y agoThis is absolutely true for real-world coding. However, for the purposes of an exercise, I think the fact that 15 was chosen was not an accident, but is part of the exercise. Does the coder recognize the relationship between the multiples, and recognize the ability to optimize by removing an extraneous comparison? It's kind of a trivial test for that sort of recognition, but anything as small as FizzBuzz is going to be rather trivial.
- dragonwriter 12y agoWell, its more direct in that, while it increases the number of branches, it minimizes the number of statements executed on any branch. It's also the solution that maps most directly to the problem statement, and, absent a strong technical reason to do otherwise, a direct mapping from requirements to code is a good thing.
- icedog 12y agoI disagree that it maps most directly to the problem statement. You're performing a common factor computation in your mind, which may be more difficult given numbers other than 3 and 5. In my opinion, pattern matching offers the most direct solution and comes with an abundance of compiler optimizations. Here's an example in Rust... for i in range(1i, 101) { match (i % 3, i % 5) { (0, 0) => println!("Fizzbuzz"), (0, _) => println!("Fizz"), (_, 0) => println!("Buzz"), _ => println!("{}", i), } }
- dragonwriter 12y ago> I disagree that it maps most directly to the problem statement. You're performing a common factor computation in your mind, which may be more difficult given numbers other than 3 and 5. Well, sure, explicitly calling out i % 15 rather than (i % 3) && (i % 5) or the equivalent has that problem. > In my opinion, pattern matching offers the most direct solution and comes with an abundance of compiler optimizations. Pattern matching is not available in many languages, but, sure, where its available, its a great choice. Note that this still has a distinct case for the case where both % 3 and % 5 are true, rather than just testing those cases independently and sequentially and concatenating, so I think it falls into the general class of solutions I was describing.
- icedog 12y agoI'll take an extra branch over concatenation and dealing with new line chars any day.
- 12y ago
- FnuGk 12y agoAnother thing i dont understand is why people hardcode 15. I would rather write (3 * 5) and let the compiler figure out that 3 * 5=15. This way i think it more clearly states where the number 15 comes from. Any reason to write 15 over (3 * 5)?
- lfender6445 12y agointeresting point, but I feel the same could be said for (3 * 5)
- rzwitserloot 12y agoBecause of the requirement to print the number if it is divisible by neither. Here: if x isDivisibleBy 3: print "Fizz" if x isDivisibleBy 5: print "Buzz" and.. how do I now print x in the neither case? I can't 'else'. I could make a long if not divisible by either if expression, but that's less easy to read than an if/else chain that starts out with 'if divisible by both, print fizzbuzz'. If fizzbuzz was: Print FizzBuzz for multiples of 15, fizz for multiples of 3, and buzz for multiples of 5, and nothing otherwise, I bet you'd see the above pseudocode far more.
- apoorvai 12y agoI'm not really a programming language expert, but it seems to me that having an implementation being the spec wouldn't be a good idea. If the Streem implementation has a bug, then the bug becomes the authoritative behavior. Any platform specific quirks would also make it difficult to have defined behavior.
- ianlevesque 12y agoYep, welcome to Ruby! To be fair, a spec with tests was reversed out of the ruby implementation[1], so things have improved a bit. 1. http://rubyspec.org/ http://rubyspec.org/
- jfoutz 12y agoThere's an old argument about worse is better. The gist is, doing things the right way is hard and takes a long time. sometimes, it's better to just get something simple out there and deal with the problems later. http://www.jwz.org/doc/worse-is-better.html http://www.jwz.org/doc/worse-is-better.html
- skrebbel 12y agoSure, but this is not finished at all. Starting with the spec instead of a prototype implementation sounds like a very very limiting and unrewarding design process.
- aidenn0 12y agoImplementations are nearly always the spec when a language is young. You want to be able to experiment and make changes. Then as it matures, you typically get a spec.
- Igglyboo 12y agoIt looks like a weekends worth of work for Matz, I doubt he's even thinking of a spec at this point.
- dragonwriter 12y agoRight now, the closest thing to a spec is the sample FizzBuzz code (as an implicit spec that "this code will solve FizzBuzz"); there is no implementation (just work-in-progress parser/lexer code.) So, while I'll agree that there are issues that come from the implementation being the spec of a language in general, I would say we are well earlier than the point at which we can identify that as a problem with Streem.
- weeksie 12y agoAt the very least, it's going to be amazing to watch a master language designer build a new language from the ground up. That said, I'm incredibly optimistic about a new Matz language. If I was going to guess, the syntax will be much lighter and the semantics will make VM optimization much easier than in Ruby.
- alphonse23 12y agodude, the man publishes 3 files, claims he's going to make a new programming language, and it hits the front page of hackernews -- Matz has some power there.
- chrisduesing 12y agoIt's not power, it's credibility. If someone who has never done anything says they are going to accomplish something large people are skeptical. If someone who already created a successful and highly adopted programming language says they are making a new one people are rightly intrigued.
- deleted 12y ago[deleted]
- tenken 12y agoLet's all hope he's comes out with a spec 1st.
- skrebbel 12y agoReminds me a lot of Elixir's |> operator, which does the exact same thing. Nice! Curious how it'll turn out to compare with Elixir on other areas.
- mjt0229 12y agoI've never heard of Elixir, but I always assumed that the |> originated in F#. Ocaml has it pretty much standard, too, although you can define it in one line in Ocaml: let (|>) x f = f x
- reirob 12y agoAlso in Haskell [0]: (|>) :: Seq a -> a -> Seq a O(1). Add an element to the right end of a sequence. Mnemonic: a triangle with the single element at the pointy end. It seems to be in the containers package since 2005: [1] [0]: http://hackage.haskell.org/package/containers-0.5.5.1/docs/Data-Sequence.html#v:-124--62- http://hackage.haskell.org/package/containers-0.5.5.1/docs/D... [1]: https://github.com/haskell/containers/commit/1e61853dbd4b9fc6ecc58ad655990ea5e971fa13#diff-a4090fc3521182bd8d1ac467d170c6aaR40 https://github.com/haskell/containers/commit/1e61853dbd4b9fc...
- platz 12y agoWhich has a totally different meaning from the F# usage. '|>' from F# is the same as 'flip $' in haskell, or (&) imported from the lens library
- muraiki 12y agoIn Elixir, |> does not flip arguments. It lets you chain together multiple functions by inserting the result of the previous function as the first argument of the following function. Here's an example from http://www.theerlangelist.com/2014/01/why-elixir.html http://www.theerlangelist.com/2014/01/why-elixir.html The following code computes the sum of squares of all positive numbers of a list: list |> Enum.filter(&(&1 > 0)) |> Enum.map(&(&1 * &1)) |> Enum.reduce(0, &(&1 + &2))
- dwash 12y ago"Copyright (c) 2015 Yukihiro Matsumoto" - bit early, ey? ;)
- michaelmior 12y agoWhy? I think it's pretty normal to drop this in as boilerplate on new projects.
- callahad 12y agoNote the year ;)
- michaelmior 12y agoHa! You got me there :)
- ukd1 12y agowrong year
- breischl 12y agoPresumably because it's not 2015 yet, at least according to my calendar...
- deleted 12y ago[deleted]
- cpach 12y agoIt’s from the future!
- bronson 12y agoOnly three weeks early.
- NARKOZ 12y ago>the software is related to the magazine article of 2015 issue of the (Japanese) programming magazine https://github.com/matz/streem/commit/1c8189f9e1df3289801b28676238741f6050b8c6 https://github.com/matz/streem/commit/1c8189f9e1df3289801b28...
- ksherlock 12y agotrue{TRAIL} return keyword_false; Well, this will be fun to debug
- falcor84 12y agoThis is indeed one of the nicest typos I've seen, but note that there's already a pull request. I suppose bugs can become shallow when your 3 file repo reaches the first page of hn. https://github.com/matz/streem/pull/4/files https://github.com/matz/streem/pull/4/files
- retr0h 12y agoNot a fan of the closing braces.
- vortico 12y agoBut the opening brace `{` is fine?
- retr0h 12y agoMeaning I would rather not have opening or closing.
- k__ 12y agoDidn't Matz mention that he wasn't either, when explaining Rubys syntax? I'm a fan of indentation based blocks, so this seems like a step back for me :\
- tenken 12y agoyea, but besides python and maybe F# who uses any of these? http://en.wikipedia.org/wiki/Off-side_rule#Off-side_rule_languages http://en.wikipedia.org/wiki/Off-side_rule#Off-side_rule_lan... And I dont think declarative languages like SASS and Yaml count as they're not "programming languages". So I think that list is actually shorter. I don't see any of these languages outside of the 2 I mentioned as really "mainstream".
- randyrand 12y agoWhat does it matter?
- deleted 12y ago[deleted]
- spacemanmatt 12y agoHaving littered the battlefield with various versions of Ruby VM, Matz drops the mic, walks off the field
- 10098 12y agoI see this as a very good alternative to traditional shell scripting languages.
- Dirlewanger 12y agoAnything that beats Bash's ugliness I'm all for.
- 10098 12y agoit would also be nice if it had a static type system, but given that it's Matz, it's unlikely...
- luckydude 12y agoWe did a similar thing to the awk source. Made awk scripts first class, you could pipe them to each other.
- bnegreve 12y agoIt's definitely a good idea. Pipes are both very powerful and very simple to use and debug, yet they are not very common in general purpose programming languages (examples?). I'm not surprised that someone is trying to build a language around them. I'll follow that, but for now it's a bit too early to judge.
- eggie 12y agoSee node.js streams.
- c3RlcGhlbnI_ 12y agoI am glad that someone is working on a new stream processing language, it is a very interesting paradigm. However I hope that they provide some very robust tools for controlling input splitting. As I have spent too much time fighting with awk and wishing it was more flexible(it is frustrating always having exactly two levels of splitting with only matchers on the first and only inverse splitting on the second). As is you would have to put in filters to resplit input into lines and that is very messy for something that you will need/want to do very often. For example if you wanted to parse by character it would be wonderful to be able to do the following: STDIN | /./{|c| # stuff } Even better would be if you took it a step further and offered something like regex pattern matching for the block input. e.g. STDIN | /\w+/{|word| /house/ { # when word is house } /car/ { # when word is car } { # default case } }
- nateabele 12y ago> However I hope that they provide some very robust tools for controlling input splitting Yes, splitting and combining. Looking at the code example, I feel like so much opportunity is just being squandered, where something like Rust's mapping constructs would feel so much better. Yours is definitely more in that vein, here's something a little closer: STDIN | /\w+/{|word| /house/ => "foo" /car/ => "bar" "literal" => "baz" _ => "dib" # (where _ is a magical symbol for the default case... could be anything) }
- rudolf0 12y agoStreaming, pattern matching, and regex combined in one sounds pretty damn cool. Might also be nice to borrow some of Scala's features for terser code than Ruby, like ary | { _ + 5 } instead of ary | { |el| el + 5 }
- spoiler 12y agoI prefer {|el| el + 5} more than { _ + 5 } the latter is too implicit for my taste, also I imagine it could become more confusing in a more complex context.
- deleted 12y ago[deleted]
- chmartin 12y agoAwesome I hacked something like this together a couple years ago using ruby and gnu parallel https://github.com/charlesmartin14/gnu_parallel https://github.com/charlesmartin14/gnu_parallel it is badly needed
- pluma 12y ago*Yukihiro Matsumoto, creator of Ruby I'm not sure everyone is familiar enough with Ruby to know who Matz is.
- chc 12y agoIf they don't know who Matz is, are they likely to know who Yukihiro Matsumoto is? EDIT: Interesting fact: I believe this is now my most downvoted comment in five years on HN, at effectively -7. Never would have guessed. I'm not exactly sure what it says, but I thought it was an interesting data point.
- louthy 12y agoNo, but the sentence as a whole does explain who he is: "Yukihiro Matsumoto, creator of Ruby"
- sandstrom 12y agoProbably not, but the epithet `creator of Ruby` gives more context.
- kaeluka 12y agoI, for one, remember the full name but not "Matz"
- lfender6445 12y agoI agree with you chc. I've been using ruby for 3 years and would not have been able to retrieve first name from memory!
- Argorak 12y agoIf you ever meet Matz, talk to him about programming languages. While he gets some flak for all the problems of his original hobby project (Ruby), he obviously loves programming languages and gives things more thought then people give him credit for. I had the chance to talk to him while I was still a student and full of ideas how the language could be made "better" and he shot them all down. For good reasons, as I know nowadays. So I always love seeing him building languages. I shared a small story about him and languages quite a while ago, I guess it fits here as well: https://news.ycombinator.com/item?id=6562979 https://news.ycombinator.com/item?id=6562979
- danso 12y agoDoes he really get a "lot of flak" for Ruby? While I know not everyone loves Ruby, it seems crazy to me that people would denigrate Matz on a personal level...to me (admittedly, a novice in designing languages), Ruby always seemed well-thought out...that is, the trade-offs do not seem out of line given the philosophical benefits, and not everyone can make claim to turning a personal project into a worldwide language. Also, he seems like a nice guy, not the type to be drawn into the kind of flareups in which he would draw flak.
- gaius 12y agoI don't think so many people have a problem with Ruby the language so much as the culture around Rails.
- Argorak 12y agoSee the amount of people complaining about the missing spec for Ruby in this thread, which is his doing. At some point ~50% of the comments here were downvoted. I should clarify that I meant Ruby the implementation, not Ruby the language. Edit: I changed the word to "some", maybe that was a bit of a hyperbole. I see quite some unreflected bashing though.
- golemotron 12y agoGreat idea, but I'm a bit disappointed in the syntax. Composed chains in Ruby are much nicer to look at.
- patrick73 12y agoSorry, but if you know nothing about Ruby this is pure hype.
- samuell 12y agoWould be cool if it would incorporate some thinking and concepts from Flow-based programming [1], as that is AFAIK the most comprehensive architecture covering all the aspects of asyncronous concurrent processing that one might run into (multiple in/out-ports, channels with bounded buffers, sub-stream support, etc etc). [1] http://www.jpaulmorrison.com/fbp/ http://www.jpaulmorrison.com/fbp/ [2] http://en.wikipedia.org/wiki/Flow-based_programming http://en.wikipedia.org/wiki/Flow-based_programming
- kricstta31 12y ago-> in clojure or hy deals with that nicely: (-> (read) (eval) (print) (loop)) using python-sh in hy this is possible: (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l"))
- randyrand 12y agoWhy is Stream spelled incorrectly?
- faitswulff 12y agoEasier to search for, at least.
- grandalf 12y agoStream processing languages typically derive from SQL (streamSQL) or prolog (Rapide) ... This one doesn't seem anywhere near as powerful but who knows.
- NanoWar 12y agoNow it might be time to have a look into YACC, LEX and maybe BISON and follow Matz' repo :)
- vlucas 12y agoInteresting that he chose a C-like syntax after going the complete opposite direction with Ruby.
- coldtea 12y agoHuh? Ruby also has a C-like (algol derived) syntax. The replacement of "}" with "end" etc, is a trivial replacement, not a different type of syntax. Lisp, Prolog, etc, would be a non-C syntax.
- stormbrew 12y agoObviously you can flatten this inheritance tree any way you like, but while all C-likes are obviously algol-likes I do think it's useful to consider C-like a distinct branch within that. That said I think you probably want more than just curly braces to define C-like even then.
- mijoharas 12y agoCan someone help explain what's going on here: \"([^\\\"]|\\.)\" [seen here in context](https://github.com/matz/streem/blob/master/src/lex.l#L49 https://github.com/matz/streem/blob/master/src/lex.l#L49). Now it seems to be finding literal strings (so "strings" e.t.c.). That would explain the literal double quotes on either side. so without that we get: ([^\\\"]|\\.) so zero or more repeating versions of [^\\\"]|\\. What I don't understand is why there is the explicit or \\. construct there, as this seems unnecessary. Am I missing something? also, why does it seem that strings cannot have either literal \ or literal " in them?
- zb 12y agoBecause if there's a \ you want to skip over the next character, even if it's a ", but if it's not escaped then you want " to be the end of the string.
- mijoharas 12y agoYou're completely right, I think I had read it as \. rather than \\. and thus didn't understand it. Thanks for helping me get it straight in my head.
- faragon 12y ago"Then said Jesus unto him, Put up again thy sword into his place: for all they that take the sword shall perish with the sword" (Matthew 26:52, King James Version) Sony played hard many times, e.g. closing Linux on Playstation 3 devices. I do not condone the attack, although I have no sympathy at all for such kind of companies.
- mostafah 12y agoAnd... here’s an implementation: https://github.com/mattn/streeem https://github.com/mattn/streeem Well, that was quick.
- rurounijones 12y agoHeh, got to love the repo message "Sorry, Sorry"
- panic 12y agoTab (https://bitbucket.org/tkatchev/tab https://bitbucket.org/tkatchev/tab) is another interesting recent text processing language.
- eccstartup 12y agoI hope it will support really big integers as in Haskell and better floating point number calculation, although I cannot foresee the power right now. Because it is concurrent, it may be very useful in scientific computing, whether in small or large scale.
- 1qaz2wsx3edc 12y agoMatz, now get started on implementing transducers! https://www.youtube.com/watch?v=6mTbuzafcII https://www.youtube.com/watch?v=6mTbuzafcII
- hdmoore 12y agoThis is an excellent example of using a parser as a language. Whether it has any legs depends on whether it beats existing tools on some front (sed, perl, ruby, etc). The concurrent angle is interesting, but I have found a multi-process approach to stream data to be more efficient than most concurrent single-process implementations. For example, with DAP (https://github.com/rapid7/dap https://github.com/rapid7/dap), we found that GNU Parallel + Ruby MRI was more effective than a concurrent language such as Go.
- nchuhoai 12y agoFor people interested, you can do something like this in Ruby right now that has nearly the same syntax: http://pragdave.me/blog/2007/12/30/pipelines-using-fibers-in-ruby-19/ http://pragdave.me/blog/2007/12/30/pipelines-using-fibers-in...
- lgleason 12y agoI love Ruby and I love Matz. With that being said there are some things that Ruby struggles with. I know that there have been some conversations among the core on bringing in more functional concepts to Ruby....at least since April. To me this says that Matz is coming to the conclusion that we may need a new language to get functional right. While I am sad to see that Ruby may be superseded by a new language I'm really happy to see Matz leading the way with one of the solutions. In the Ruby community we have a expression "Matz is nice and therefore we are nice". That has set the tone for the community in ways that have never been the same in some of the others. As someone who has had the opportunity to talk with Matz on multiple occasions and work with the Ruby community it would be great to see this as a natural evolution of Ruby and the people who love it... As I have started to move on to working with more functional languages etc. I have started to move away from doing Ruby, but if the community can continue on and evolve with a new language that would be awesome!
- dragonwriter 12y ago> I know that there have been some conversations among the core on bringing in more functional concepts to Ruby....at least since April. Only since April? Has there been a non-bugfix release of Ruby that didn't bring in more functional concepts to Ruby since, well, ever? AFAICT, bringing more support for ideas that come from the FP world has been one of the perennial drivers of progress in Ruby, not a new issue that emerged this spring. > To me this says that Matz is coming to the conclusion that we may need a new language to get functional right. I think that's reading a lot into it. Matz experimenting with something doesn't mean that the intent is to replace Ruby with it.
- emmanueloga_ 12y ago> "Matz is nice and therefore we are nice" This non sequitur annoys me. Deconstructing it: * A: "Matz is nice": Let's say we all agree this is true. * B: "we are nice": i.e., the ruby community is nice. * P(A -> B): (A therefore B) is a slogan, so I assume the proposition P is believed to be true. Is it? In order for P to be true, the only option is for B to be true as long as Matz keeps being nice. Assuming Matz is still good-natured, is not hard to find counterexamples for B (every big community have some less-than-nice people). So the facts tell us that P is false. Alternatively, if you assume that Matz is not nice, then, without mattering if "we" are nice or not, the slogan holds true (modus ponens [1]). Anyway, my point is that the slogan is as silly as this rant :p. 1: http://en.wikipedia.org/wiki/Modus_ponens#Justification_via_truth_table http://en.wikipedia.org/wiki/Modus_ponens#Justification_via_...
- programminggeek 12y agoI like the idea of dataflow or stream processing ideas. I would love if you could make the connector pieces smarter so that you were enforcing a contract between the piping mechanisms. I believe you could build some very interesting systems with that approach.
- leke 12y agoWhat kind of areas could Streem be used in?
- zvrba 12y agoTake a look at dss from AT&T AST tools: http://www2.research.att.com/~astopen/dss/dss.html http://www2.research.att.com/~astopen/dss/dss.html http://www2.research.att.com/~astopen/publications/dss-2004.pdf http://www2.research.att.com/~astopen/publications/dss-2004....
- pmalynin 12y agoLooking to hire a Streem professional, must have at least 5 years experience with Streem.
- enry_straker 12y agoGreat. I have been streem-ing for more than 15 years now. :-)