Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thoughts on operators in elm-in-elm #60

Open
harrysarson opened this issue Aug 30, 2019 · 2 comments
Open

Thoughts on operators in elm-in-elm #60

harrysarson opened this issue Aug 30, 2019 · 2 comments

Comments

@harrysarson
Copy link
Contributor

Standard elm uses a syntax borrowed from Haskell to define new operators.

I propose that elm-in-elm should hard code operators into the compiler. The main advantage of this is approach is that we avoid having to define a kernel function for each arithmetic operator: instead the compiler can generate code directly*.

Only standard packages can define operators and they do so very rarely so I think it would be feasible to handle all elm operators in the compiler.

*: The standard compiler also generates code directly as an optimsation.

@harrysarson
Copy link
Contributor Author

Code from the elm compiler that special cases some infix operators

The variable name in each code snippet is the name of an elm function. Sometimes these functions are exposed via infix operators (e.g. infix left 6 (+) = add) and sometimes elm function calls are turned into operators by the elm compiler (e.g. the elm code complement 67 becomes ~67 in js).

Operators in Basics

Source: https://github.com/elm/compiler/blob/master/compiler/src/Generate/JavaScript/Expression.hs#L503-L546

generateBasicsCall :: Mode.Mode -> ModuleName.Canonical -> Name.Name -> [Opt.Expr] -> JS.Expr
generateBasicsCall mode home name args =
  case args of
    [elmArg] ->
      let arg = generateJsExpr mode elmArg in
      case name of
        "not"      -> JS.Prefix JS.PrefixNot arg
        "negate"   -> JS.Prefix JS.PrefixNegate arg
        "toFloat"  -> arg
        "truncate" -> JS.Infix JS.OpBitwiseOr arg (JS.Int 0)
        _          -> generateGlobalCall home name [arg]

    [elmLeft, elmRight] ->
      case name of
        -- NOTE: removed "composeL" and "composeR" because of this issue:
        -- https://github.com/elm/compiler/issues/1722
        "append"   -> append mode elmLeft elmRight
        "apL"      -> generateJsExpr mode $ apply elmLeft elmRight
        "apR"      -> generateJsExpr mode $ apply elmRight elmLeft
        _ ->
          let
            left = generateJsExpr mode elmLeft
            right = generateJsExpr mode elmRight
          in
          case name of
            "add"  -> JS.Infix JS.OpAdd left right
            "sub"  -> JS.Infix JS.OpSub left right
            "mul"  -> JS.Infix JS.OpMul left right
            "fdiv" -> JS.Infix JS.OpDiv left right
            "idiv" -> JS.Infix JS.OpBitwiseOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
            "eq"   -> equal left right
            "neq"  -> notEqual left right
            "lt"   -> cmp JS.OpLt JS.OpLt   0  left right
            "gt"   -> cmp JS.OpGt JS.OpGt   0  left right
            "le"   -> cmp JS.OpLe JS.OpLt   1  left right
            "ge"   -> cmp JS.OpGe JS.OpGt (-1) left right
            "or"   -> JS.Infix JS.OpOr  left right
            "and"  -> JS.Infix JS.OpAnd left right
            "xor"  -> JS.Infix JS.OpNe  left right
            "remainderBy" -> JS.Infix JS.OpMod right left
            _      -> generateGlobalCall home name [left, right]

    _ ->
      generateGlobalCall home name (map (generateJsExpr mode) args)

Functions in Bitwise

Source: https://github.com/elm/compiler/blob/master/compiler/src/Generate/JavaScript/Expression.hs#L481-L500

generateBitwiseCall :: ModuleName.Canonical -> Name.Name -> [JS.Expr] -> JS.Expr
generateBitwiseCall home name args =
  case args of
    [arg] ->
      case name of
        "complement" -> JS.Prefix JS.PrefixComplement arg
        _            -> generateGlobalCall home name args

    [left,right] ->
      case name of
        "and"            -> JS.Infix JS.OpBitwiseAnd left right
        "or"             -> JS.Infix JS.OpBitwiseOr  left right
        "xor"            -> JS.Infix JS.OpBitwiseXor left right
        "shiftLeftBy"    -> JS.Infix JS.OpLShift     right left
        "shiftRightBy"   -> JS.Infix JS.OpSpRShift   right left
        "shiftRightZfBy" -> JS.Infix JS.OpZfRShift   right left
        _                -> generateGlobalCall home name args

    _ ->
      generateGlobalCall home name args

Operators in Tuple

Source: https://github.com/elm/compiler/blob/master/compiler/src/Generate/JavaScript/Expression.hs#L460-L470

generateTupleCall :: ModuleName.Canonical -> Name.Name -> [JS.Expr] -> JS.Expr
generateTupleCall home name args =
  case args of
    [value] ->
      case name of
        "first"  -> JS.Access value (JsName.fromLocal "a")
        "second" -> JS.Access value (JsName.fromLocal "b")
        _        -> generateGlobalCall home name args

    _ ->
      generateGlobalCall home name args

Operators in JsArray

Source: https://github.com/elm/compiler/blob/master/compiler/src/Generate/JavaScript/Expression.hs#L473-L478

generateJsArrayCall :: ModuleName.Canonical -> Name.Name -> [JS.Expr] -> JS.Expr
generateJsArrayCall home name args =
  case args of
    [entry]        | name == "singleton" -> JS.Array [entry]
    [index, array] | name == "unsafeGet" -> JS.Index array index
    _                                    -> generateGlobalCall home name args

@harrysarson
Copy link
Contributor Author

harrysarson commented Sep 19, 2019

So the special casing of operators is more complicated. Sometimes (a vast majority of real world cases) the elm compiler special cases these operators. Othertimes it falls back to calling the kernel implementation of the arithmetic operation.

Here is an image (here is it running live - but link will expire when I switch my laptop running the server off). On the left is elm code and on the right is the relevant parts of the compiled javascript.
image

The elm compiler cannot special case the use of + in addSlow and so it falls back to the function in the Basics module.


As a tangent this is dangerous as a bug in elm/core could cause the result of addSlow to diverge from add - would be a very nasty bug to track down and would only occur vary rarely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant