6 ms·
From the project docs: "Generate simple readable Javascript" - isn't this more of a disadvantage than an advantage? the created Javascript code won't be high p
by derengel 12y ago
From the project docs: "Generate simple readable Javascript" - isn't this more of a disadvantage than an advantage? the created Javascript code won't be high performance, which matters a lot in mobile today.
- paf31 12y ago"Simple readable JavaScript" as opposed to the sort of output you might expect from compilers for other Haskell-like languages which attempt to preserve Haskell's semantics. Certainly there are cases where using techniques from pure functional programming can lead to poor performance (monadic recursion jumps to mind), but it is perfectly possible to write fast code using PureScript. And if you really need to squeeze out every last bit of performance, you can always write code in JavaScript and use the FFI.
- geoffc 12y agoI can vouch for the real world performance. Phil has used Purescript to dramatically improve the performance of our medical image viewer. It's pretty impressive stuff.
- eru 12y agoWhat's monadic recursion?
- paf31 12y agoConsider a recursive function like factorial: fact :: Number -> Number fact 0 = 1 fact n = n * fact (n - 1) This will build stack frames and fail with a stack overflow for large inputs. We can fix this by using tail recursion and an accumulator: fact :: Number -> Number fact = go 1 where go acc 0 = acc go acc n = go (acc * n) (n - 1) The PureScript compiler will recognize that every recursive call is in tail position, and will convert the function to a while loop. If we want to perform side-effects during the recursion, we typically use a monad to track the type of side effect in use. For example, we can use PureScript's Eff monad to trace information to the console: fact :: Number -> Eff (trace :: Trace) Number fact = go 1 where go acc 0 = do trace "Done!" return acc go acc n = do trace "Keep going ..." go (acc * n) (n - 1) In the case of the Eff monad, the PureScript compiler employs some special tricks in the optimizer to enable the tail call optimization, so we're still OK. But this is only possible because Eff is defined in the standard library (at least until we implement custom rewrite rules). In the general case, the compiler only sees applications to the monadic bind function of the underlying monad. For example, if we use the Writer monad instead: fact :: Number -> Writer String Number fact = go 1 where go acc 0 = do tell "Done!" return acc go acc n = do tell "Keep going ..." go (acc * n) (n - 1) which is equivalent after desugaring to the following code: fact :: Number -> Writer String Number fact = go 1 where go acc 0 = tell "Done!" >>= \_ -> return acc go acc n = tell "Keep going ..." >>= \_ -> go (acc * n) (n - 1) This is an example of "monadic recursion" - a recursive function whose return type uses some monad. In this case, the recursive calls are no longer in tail position, so the compiler cannot apply the tail call optimization. The result is that the compiled JavaScript might blow the stack for large inputs. The point is that idiomatic Haskell code is often not appropriate in JavaScript because of the different semantics, so it might not be appropriate in PureScript either. The good news is that many Haskell idioms have corresponding PureScript idioms (often using the Eff monad).
- eru 12y agoThanks! Another trick I've seen used is `naked' avoiding recursion in your code, and relying on combinators implemented in some optimized lower level code. Would that work with PureScript?
- paf31 12y agoYes - in fact, that's the approach we encourage for working with immutable arrays: https://github.com/purescript/purescript-arrays https://github.com/purescript/purescript-arrays
- codygman 12y agoI wanted to play around with this, maybe you do too... Install purescript: https://leanpub.com/purescript/read#leanpub-auto-installing-purescript https://leanpub.com/purescript/read#leanpub-auto-installing-... The code, put it in a file named recur.purs (or w/e you want): -- recur.purs module where import Control.Monad.Eff import Debug.Trace -- naive factorial function fact :: Number -> Number fact 0 = 1 fact n = n * fact (n - 1) -- factorial function exploiting tail call optimization by putting function in tail position (at the end) fact' :: Number -> Number fact' = go 1 where go acc 0 = acc go acc n = go (acc * n) (n - 1) -- use Eff monad to trace information to the console fact'' :: Number -> Eff ( trace :: Trace) Number fact'' = go 1 where go acc 0 = do trace $ "The answer is: " ++ show acc return acc go acc n = do trace $ "(" ++ show acc ++ " * " ++ show n ++ ")" ++ "(" ++ show n ++ " - 1)" go (acc * n) (n - 1) main = fact'' 8 Then to compile it: psc recur.purs --output dist/Main.js --main Recur Hope this is as educational (and fun) for others as it was for me.
- BinaryIdiot 12y agoYour minification process should be separate from your build process that's required to run it. This can generate JavaScript that's not a huge pain in the ass to debug, then you as the developer can minify it with existing libraries. Though I'm not necessarily sure it wouldn't be high performance just maybe a hair slower to load. Then again I don't know how well their generator works.