elm-lang / elm-make

A build tool for Elm projects
BSD 3-Clause "New" or "Revised" License
175 stars 45 forks source link

`elm-make` growing to consume infinite resources #21

Closed novaluke closed 9 years ago

novaluke commented 9 years ago

I somehow managed to write an Elm file today that when run through elm-make caused elm-make to consume rapidly-increasing and seemingly unending system resources (see screenshot).

TL;DR: See the code at the end of the page.

I double-checked that the issue wasn't with the types by removing all code except the type declarations and successfully passing the file through elm-make. I would understand things blowing up if I punched an infinite list into elm-repl or had a recursive type alias that the compiler didn't catch. However, since elm-make just compiles and therefore doesn't run the code (I think?), and since it didn't choke on the data types when they were the only thing in the file, it seems like it was having issues compiling the functions defined in the file.

Although there are definitely errors in the code, it seems a bit unfriendly for new Elm users to have the compiler blow their entire system up if they make a mistake (elm-make pushed everything into swap so fast that the GUI locked up within the first few seconds). Is this a bug, or is there just some "gotcha" here that I/people need to be aware of?

Here's the original code and the corrected version, respectively:

-- This is the original attempt that caused `elm-make` to go crazy.
import Graphics.Element exposing (show)

import Dict exposing (Dict)

type alias Violation = String
type FormComponent comparable =
     FormComponent { error          : Dict comparable (List FormComponent)
                   , valdrViolations : Maybe (List Violation)
                   }

violations' comp =
  case comp.valdrViolations of
    Just xs -> xs
    Nothing -> Dict.values comp.error |> List.foldl (++) [] |> violations

violations comps =
  case comps of
    [] -> []
    (comp::t) -> violations' comp :: violations t

main = show "foo"
-- This is the modified version that works, with or without the type annotations.
import Graphics.Element exposing (show)

import Dict exposing (Dict)

type alias Violation = String
type FormComponent comparable = FormComponent
  { error           : Dict comparable (List (FormComponent comparable))
  , valdrViolations : Maybe (List Violation)
  }

violations' : FormComponent comparable -> List Violation
violations' (FormComponent comp) =
  case comp.valdrViolations of
    Just xs -> xs
    Nothing -> Dict.values comp.error |> List.foldl (++) [] |> violations

violations : List (FormComponent comparable) -> List Violation
violations comps =
  case comps of
    [] -> []
    (comp::t) -> violations' comp ++ violations t

main = show "foo"

Sidenote: This actually came about from having to solve a problem in my JavaScript day-job - I decided to solve it in Elm and then port the solution back over to JavaScript (because FP/Elm FTW). Because of this the data structures may not make the most sense for an Elm environment, but it's what the JS library we're using provides.

LiamGoodacre commented 9 years ago

I iteratively compressed your example down to:

f x = (x, g x)
g c = (f, g c)

I also independently observed the effect by:

oops = List.foldr (|>) []

Seems like elm's type checker doesn't catch some kinds of infinite types.

nsmryan commented 9 years ago

I also had this issue. The simplified program is: import Signal exposing (foldp, constant)

value : { field1 : Int } value = { field1 = 0 }

sig : Signal { field1 : Int } sig = foldp update value (constant 0)

update input state = { state | field1 = 1 }

While playing with this issue, I found that it seemed to come up (at least in this case) when I was accidentally adding a field to a type that already contained a field with the same name and type and therefore changing the type of the result. Adding a type signature causes an error explaining this (example program below) but omitting the type signature causes elm-make to eat all of memory.

import Signal exposing (foldp, constant)

value : { field1 : Int } value = { field1 = 0 }

sig : Signal { field1 : Int } sig = foldp update value (constant 0)

update : Int -> { field1 : Int } -> { field1 : Int } update input state = { state | field1 = 1 }

This was a small an example as I could muster.

jvoigtlaender commented 9 years ago

This all being about types, probably you should direct your reports to the elm-compiler repo, instead of this one here.

In fact, the elm-compiler repo already has some bugs open about infinite types, so your examples could be added there (if they add anything new, cases not already observed there).

Here, the issue could be closed.

nsmryan commented 9 years ago

Very true. I'll look for corresponding issue.

evancz commented 9 years ago

Probably https://github.com/elm-lang/elm-compiler/issues/832 or https://github.com/elm-lang/elm-compiler/issues/656

I'm going to close in the meantime. If it is one of those, is it possible to find a more minimal example? Or do those issues cover things well enough already?