#1165 Fantom and Haskell

cbeust Mon 2 Aug 2010

Hi everyone,

I'm porting a few short Haskell programs to Fantom for fun and I'm currently looking at the RPN calculator (look for solveRPN on the page).

The function is:

solveRPN :: (Num a, Read a) => String -> a  
solveRPN = head . foldl foldingFunction [] . words  
    where   foldingFunction (x:y:ys) "*" = (x * y):ys  
            foldingFunction (x:y:ys) "+" = (x + y):ys  
            foldingFunction (x:y:ys) "-" = (y - x):ys  
            foldingFunction xs numberString = read numberString:xs  

Here is what I came up with:

echo(["8", "1", "2", "+", "5", "*", "+"].reduce([,], | Int[] n, Str p -> Int[] |
{
  echo("n:" + n)
  switch(p) {
    case "+" : return n.insert(0, n.removeAt(0) + n.removeAt(0))
    case "*" : return n.insert(0, n.removeAt(0) * n.removeAt(0))
    default: return [ p.toInt ].addAll(n)
  }
}))

The output:

n:[,]
n:[8]
n:[1, 8]
n:[2, 1, 8]
n:[3, 8]
n:[5, 3, 8]
n:[15, 8]
[23]

I think this matches the Haskell semantics pretty closely, so now, I was wondering what would be a more idiomatic way of implementing this in Fantom...

Suggestions?

msl Mon 2 Aug 2010

Given you're looking at a stack, I'd prefer push and pop over insert, removeAt and addAll.

My take (not much different to yours):

calc := ["8", "1", "2", "+", "5", "*", "+"]
sum := calc.reduce([,]) |Int[] n, Str p -> Int[]| {
    // if (p.isNum) { // I wish
    if (p.isAlphaNum && !p.isAlpha) {
        return n.push(Int(p))
    }
    else {
        // return n.push(n.pop.trap(p, [n.pop])) // I Wish
        switch (p) {
            case "+": return n.push(n.pop + n.pop)
            case "-": return n.push(n.pop - n.pop)
            case "*": return n.push(n.pop * n.pop)
            case "/": return n.push(n.pop / n.pop)
        }
        return n
    }
}
echo(sum)

Raises a couple of interesting questions:

  1. Why no is there no Str#isNumeric?
  2. Would be super nice for the default implementation of sys::Obj.trap to handle the language provided shortcuts (sugar), so you could get the stuff in docLang::Expressions.shortcuts and remove the switch altogether.

msl Mon 2 Aug 2010

Raises a couple of interesting questions

I'm the first to admit that they're probably not really that interesting at all ;)

brian Mon 2 Aug 2010

I think this matches the Haskell semantics pretty closely, so now, I was wondering what would be a more idiomatic way of implementing this in Fantom...

Looks good to me. I concur with msl, I'd use push/pop.

cbeust Mon 2 Aug 2010

I was using push() and pop() initially until I noticed that they both work at the end of the list, not at the beginning, which was a bit counterintuitive to me.

I agree the code is a bit cleaner, though:

echo(["8", "1", "2", "+", "5", "*", "+"].reduce([,], | Int[] n, Str p -> Int[] | {
  echo("n:" + n)
  switch(p) {
    case "+" : return n.push(n.pop() + n.pop())
    case "*" : return n.push(n.pop() * n.pop())
    default: return n.push(p.toInt)
  }
}))

brian Mon 2 Aug 2010

I was using push() and pop() initially until I noticed that they both work at the end of the list, not at the beginning, which was a bit counterintuitive to me.

List is actually backed by an array in the JVM, so definitely more efficient to push/pop at the end (versus a more functional linked list backing store).

Also by convention, we typically leave off the () of method calls which don't take any parameters. See Conventions.

ivan Mon 2 Aug 2010

my 50 cents, single expression version (works in fansh :):

echo(["8", "1", "2", "+", "5", "*", "+"].reduce([,]) |Int[] n, Str p -> Int[]| {
  n.push((p.isAlphaNum && !p.isAlpha) ? Int(p) : [
    "+" : Int#plus, "-": Int#minus,
    "*" : Int#mult, "/": Int#div][p].call(n.pop, n.pop))
})

cbeust Wed 4 Aug 2010

I just posted a blog entry about this.

Login or Signup to reply.