I continue to experiment with various AI tools, including copilot, which I use in conjunction with Intellij. Here is something from about three minutes ago. I wrote type signature you see below, pressed , and waited a second or two. Copilot proposed what you see below the type signature. This is a best-case scenario. The code proposed is not always correct, but it is correct often enough that I’ve found it to be quite helpful.
(( Added later: see below for an experience report on chatGPT. ))
In the case at hand, I knew exactly how I would write this function — essentially just as Copilot did. Using it saves time.
It appears that Copilot has a reasonable understanding of Elm in general and my immediate working context in particular. I’d be interested in hearing what others are doing with this and related tools, and what the experience has been.
-- module Book
replaceCell : Cell -> Book -> Book
replaceCell cell book =
let
cells =
book.cells
in
{ book
| cells =
List.map
(\c ->
if c.index == cell.index then
cell
else
c
)
cells
}
Long addendum re ChatGPT
Here is a more elaborate and impressive example using chatGPT 4. I had written code (for the new elm-notebook, a work-in-progress) that is supposed to (1) update the dictionary of declarations in the current notebook and (2) at the same time evaluate all expressions in the notebook. The code listed below does fine for (1) but not for (2). When I run this code and then look at the notebook, only the last expression is evaluated. The problem is (very loosely stated), one of timing (I believe). Each execution of processCell_
requires first sending a request to the Elm compiler via Http, then sending the result, which is Javascript code, out to Javascript land for execution via ports, then sending the result (a stringifiied Json object) back to Elm, again via ports.
I realized that executions of processCell_
had to be properly sequenced. Below is my dialogue with chatGPT. In brief: (1) this was a harder question than I posed to Copilot; (2) chatGPT’s solution works beautifully.
My Question.
Consider the Elm code listed below. My problem is that I would like to have a pause of, say 50 milliseconds, between executions of the function ‘folder’.
updateDeclarationsDictionary : Model -> ( Model, Cmd FrontendMsg )
updateDeclarationsDictionary model =
let
n =
List.length model.currentBook.cells
indices =
List.range 0 n
oldEvalState =
model.evalState
newEvalState =
{ oldEvalState | decls = Dict.empty }
( newModel, commands ) =
List.foldl folder ( { model | evalState = newEvalState }, [] ) indices
in
( newModel, Cmd.batch commands )
folder : Int -> ( Model, List (Cmd FrontendMsg) ) -> ( Model, List (Cmd FrontendMsg) )
folder k ( model, cmds ) =
let
( model_, cmd ) =
processCell_ k model
in
( model_, cmd :: cmds )
ChatGPT’s Answer.
In Elm, the standard way to introduce a delay or pause is through the use of Cmd Msg
with Process.sleep
. Since you want a 50ms delay between each execution of the folder
function, you could introduce a time-based command to help with this.
One possible way to do this is to use a new message type for your model, say ProcessCell Int
, which would take care of processing a single cell. Your update function can then handle this new message type.
First, add a new message type:
type FrontendMsg
= ...
| ProcessCell Int
Then, modify your update
function:
update : FrontendMsg -> Model -> ( Model, Cmd FrontendMsg )
update msg model =
case msg of
...
ProcessCell k ->
let
( newModel, cmd ) =
processCell_ k model
in
( newModel, cmd )
Now, in your updateDeclarationsDictionary
function, rather than using List.foldl
, you would queue these ProcessCell
commands with delays.
updateDeclarationsDictionary : Model -> ( Model, Cmd FrontendMsg )
updateDeclarationsDictionary model =
let
n = List.length model.currentBook.cells
indices = List.range 0 n
oldEvalState = model.evalState
newEvalState = { oldEvalState | decls = Dict.empty }
commands = List.indexedMap createDelayedCommand indices
in
( { model | evalState = newEvalState }, Cmd.batch commands )
createDelayedCommand : Int -> Int -> Cmd FrontendMsg
createDelayedCommand idx _ =
Process.sleep (toFloat (idx * 50))
|> Task.perform (\_ -> ProcessCell idx)
Here, each cell would be processed after idx * 50
milliseconds, which means the first cell would be processed immediately, the second one after 50ms, the third one after 100ms, and so on.