DALL-E Prompt: I have a personal development blog called 'Laforge'. I am writing my first technical post about the benefits of AI and why even a seasoned developer shouldn't ignore it. In this post I am going to be using chatGPT 4.0 to help me build a calculator widget in a framework I''m interested in but don''t know too much about called SolidJS with typescript. Generate an image that represents this blog post.

Building Blind: Even with skepticism, do not ignore the benefits of LLM AI.

- 10 min read

Building Blind

Recently I’ve been using chat GPT (among other AI tools) quite a bit in order to get the ball rolling on a few personal projects. I will post more about matchmaker-rs (and the later implementation to be made, matchmaker-ex in elixir) soon, but first I wanted to talk a bit about this pair coding with AI.

Motivation

I have a few close friends who are very skeptical of AI and its potential, they generally give me a shrug and say things like “It just seems like a gimmick” or “I just don’t see how it can help me day to day in my work”.

I can see where they are coming from (and about a year ago, I was with them!), but I’ve been playing more and more with this stuff, have close friends in the industry, and I am confident in this:

We cannot ignore these tools anymore. Get with it or you may be left behind.

A gif of Keanu Reaves saying "whoa"

Whoa. That’s a bold (crazy!?) claim. But I believe it and I intend to back it up with this post.

What we’ll build

Let’s build something blind in a framework I like and have read some about but have never really used.

I am telling you the truth. I know roughly how I’ll build this, but I haven’t ever really used SolidJS. Take any framework you are interested in but don’t know much about and follow along with me. I am going to use chatGPT to help me build a calculator widget in SolidJS with typescript.

If you’ve read my first post you know this blog is built with Astro, and so I can embed components from any framework.

Final note on using ChatGPT

I don’t believe chatGPT or Bard or any other AI is a replacement for an engineer.

The point I’m trying to make here is simply this:

I know SolidJS somewhat. I’ve used other frameworks similar, I’ve used it once before, I’ve read through docs, I’ve thought about problems in the shower…but I’ve never really built anything with it bigger than a couple of components. To use it, I’d have to heavily reference examples and documentation.

ChatGPT saves me this hassle. It’s not a replacement for an engineer, but it compresses all the information I need and saves me time and effort searching, keeping me fresh.

It is a companion to the documentation, not a replacement.

The start of a calculator

Ok so I’m not totally helpless, but I’m going to be lazy and just go from teh start and get some scaffolding in straight away to avoid having to brush up on the docs. Now my friends (one in particular this article is targeted towards) will be the first to tell you and make fun of me for Loving documentation (all you have to do is read the manual!), but I’ve come to their way of thinking somewhat. IFF you can execute and get the job done quickly without reading the manual, shouldn’t you sometimes?

GPT Alone

Now, chatGPT is good enough to do this entire component all by itself I reckon, and you can do this, cross reference the docs, and be done.

But that’s not my intent here. I intend to show how one might use this tool on something larger and more complex. So I’m going to use chatGPT to help me get the scaffolding in place, and then build on that bit by bit. But in case you wanna know what chat GPT comes up with:

import { createSignal } from "solid-js";

function Calculator() {
  // State for the inputs and result
  const [input1, setInput1] = createSignal("");
  const [input2, setInput2] = createSignal("");
  const [result, setResult] = createSignal("");

  // Operation functions
  const add = () => setResult(parseFloat(input1()) + parseFloat(input2()));
  const subtract = () => setResult(parseFloat(input1()) - parseFloat(input2()));
  const multiply = () => setResult(parseFloat(input1()) * parseFloat(input2()));
  const divide = () => {
    if (parseFloat(input2()) === 0) {
      alert("Cannot divide by zero");
      return;
    }
    setResult(parseFloat(input1()) / parseFloat(input2()));
  };

  // Clear function
  const clear = () => {
    setInput1("");
    setInput2("");
    setResult("");
  };

  return (
    <div>
      <input
        type="number"
        value={input1()}
        onInput={(e) => setInput1(e.target.value)}
        placeholder="Input 1"
      />
      <input
        type="number"
        value={input2()}
        onInput={(e) => setInput2(e.target.value)}
        placeholder="Input 2"
      />
      <div>
        <button onClick={add}>+</button>
        <button onClick={subtract}>-</button>
        <button onClick={multiply}>*</button>
        <button onClick={divide}>/</button>
        <button onClick={clear}>Clear</button>
      </div>
      <div>Result: {result()}</div>
    </div>
  );
}

export default Calculator;

“GPT4 Prompt: I want to make a simple calculator component in solidJS, can you build that for me? It should have addition, subtraction, multiplication, division, and clear functionality”


Link: ChatGPT

This is nearly right, there is just a missing conversion, some missing padding, and the input is text (I wanted to build one with buttons). But I didn’t specify that.

const add = () => setResult(parseFloat(input1()) + parseFloat(input2()).toString());
// etc...

Here it is all fixed up with a bit of styling added to help out:

Well wasn’t that simple!

OK, but—like—for real

Ok yeah sure. Real stuff is harder and more complex. So let’s treat this problem like a series of harder problems.

Here are a set of new requirements:

  • I want all those operations listed
  • I want to find a square root using Newton’s method
  • I want everything to be a button, not typing number inputs

Let’s go!

Starting with a button

I’m using TailwindCSS and that is set up for me. Imagine you’re working at a company and some other smart engineer already did all that. You’re simply told “make this thing in this way we make things ok go kthxbie”.

Alright…better start with just a button. I’m going to use chatGPT to help me.

const CalculatorButton = (props: any) => {
  // Button click handler
  const handleClick = () => {
    if (props.onClick) {
      props.onClick(props.value);
    }
  };

  return (
    <button
      class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
      onClick={handleClick}
    >
      {props.label}
    </button>
  );
};

export default CalculatorButton;

export const WowButton = () => {
  function alertValue(value: any) {
    console.log("Alerting", value);
    window.alert(value);
  }

  return <CalculatorButton label="Say WOW (click me)" onClick={alertValue} value="WOW!!!!" />;
};

ChatGPT prompt: I’m building a calculator in solidJS. I already have tailwindCSS setup and want to use that. Let’s go slowly one step at a time. First I don’t want text input I want to use button input. Can you help me make the buttons? I want them to have a flat style with rounded cornerned rectangles. Let’s start with just the button part.


Link: ChatGPT

Whoa! ChatGPT went pretty hard, it generated more than just the button I asked for, anticipating my next questions and building the grid and everything (you can see that in the link).

Let’s just look at this button, though.


Can’t I just say? Wow.

Wow.

Not only did it make a nice button, but it also did a ton of nice things I needed but forgot to ask for:

  • It made the button have a hover effect
  • It added event handling
  • It added arbitrary labeling
  • It handled unset onClick handlers.

It did make a “mistake” in that this is not valid strict typescript since the props have no type.

It also oddly (to me but perhaps I don’t know some idiom) separated the callback from the data by also passing a “value” prop. I suppose this is nice if you want to change the payload but not reload different callbacks etc in the application.

Fixing the errors.

ChatGPT forgot to be type-safe, probably because most folks don’t. I’ll just fix that up really quickly by defining the interface (I could have asked ChatGPT to do this for me if I don’t know typescript so well, but that’s ok)

interface CalculatorButtonProps {
  label: string;
  value: string;
  onClick: (value: string) => void;
}

const CalculatorButton = (props: CalculatorButtonProps) => {
  // Button click handler
  const handleClick = () => {
    if (props.onClick) {
      props.onClick(props.value);
    }
  };

  return (
    <button
      class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
      onClick={handleClick}
    >
      {props.label}
    </button>
  );
};

export default CalculatorButton;

export const WowButton = () => {
  function alertValue(value: string) {
    console.log("Alerting", value);
    window.alert(value);
  }

  return <CalculatorButton label="Say WOW (click me)" onClick={alertValue} value="WOW!!!!" />;
};

Juicy. Nice and safe. Thanks, types!

Constructing the grid

Well, chatGPT already did that, so let’s show what that looks like now.

I’ve also added a bit of cleanup for typing as before and some renaming to make it fits with what we have so far.

If you open the chat, you’ll see at first it uses some stringly typing…ick.

I don’t want to type a bunch of boilerplate, so maybe chatGPT can help me save some finger strokes.

export enum CalculatorButtonType {
  Wow = "WOW!!!!",
  Clear = "C",
  Zero = "0",
  One = "1",
  Two = "2",
  Three = "3",
  Four = "4",
  Five = "5",
  Six = "6",
  Seven = "7",
  Eight = "8",
  Nine = "9",
  Add = "+",
  Subtract = "-",
  Multiply = "*",
  Divide = "/",
  Equals = "=",
  Decimal = ".",
  ToggleSign = "±",
  Sqrt = "√",
}
import CalculatorButton from "./calculator_button_final";
import { CalculatorButtonType } from "./calculator_button_type";

export const CalculatorGrid = () => {
  const onButtonClick = (value: string) => {
    console.log(value); // Here you'll handle the logic based on the button clicked
  };

  return (
    <div class="max-w-xs mx-auto">
      <div class="grid grid-cols-4 gap-2">
        <CalculatorButton label="7" value={CalculatorButtonType.Seven} onClick={onButtonClick} />
        <CalculatorButton label="8" value={CalculatorButtonType.Eight} onClick={onButtonClick} />
        <CalculatorButton label="9" value={CalculatorButtonType.Nine} onClick={onButtonClick} />
        <CalculatorButton label="+" value={CalculatorButtonType.Add} onClick={onButtonClick} />
        <CalculatorButton label="4" value={CalculatorButtonType.Four} onClick={onButtonClick} />
        <CalculatorButton label="5" value={CalculatorButtonType.Five} onClick={onButtonClick} />
        <CalculatorButton label="6" value={CalculatorButtonType.Six} onClick={onButtonClick} />
        <CalculatorButton label="-" value={CalculatorButtonType.Subtract} onClick={onButtonClick} />
        <CalculatorButton label="1" value={CalculatorButtonType.One} onClick={onButtonClick} />
        <CalculatorButton label="2" value={CalculatorButtonType.Two} onClick={onButtonClick} />
        <CalculatorButton label="3" value={CalculatorButtonType.Three} onClick={onButtonClick} />
        <CalculatorButton label="*" value={CalculatorButtonType.Multiply} onClick={onButtonClick} />
        <CalculatorButton
          label="0"
          value={CalculatorButtonType.Zero}
          onClick={onButtonClick}
          class="col-span-2"
        />
        <CalculatorButton label="." value={CalculatorButtonType.Decimal} onClick={onButtonClick} />
        <CalculatorButton label="/" value={CalculatorButtonType.Divide} onClick={onButtonClick} />
        <CalculatorButton
          label="±"
          value={CalculatorButtonType.ToggleSign}
          onClick={onButtonClick}
        />
        <CalculatorButton
          label="="
          value={CalculatorButtonType.Equals}
          onClick={onButtonClick}
          class="col-span-2"
        />
      </div>
    </div>
  );
};

export default CalculatorGrid;

”Followup prompt: can you generate buttons for 0-9, decimal point, addition, subtraction, division, multiplaction, positive negative, and equals?” (notice the spelling mistakes)

“Great! Instead of stringly typed “value” prop can we use an enum or something type safe?"

"can you save me some keystrokes and define the entire grid for me?”

Link: ChatGPT

Are you not entertained gif

Are you impressed yet?

It even puts the things in the right order so the higher numbers are at the top, operators on the right, things aren’t just strewn about!

Summary so far

Just to be clear, so far I’ve built a simple text input calculator, made a nice pretty button (and I barely know CSS…I’m an OS and distributed systems developer. I make tools. Any front end I’ve done is a hobby thing and it is BAD).

So now what we need is an output box and to wire up the buttons to do things.

Wiring up the buttons

Ok so as before, I’ve added some types and rearranged things a bit. I am the engineer, after all. GPT just helps me out.

I forgot how to use useState in SolidJS, I’m glad it generated that for me. I checked the Solid docs to make sure this all checks out. Evvverrrythinnngg looks Gucci…

import { createSignal } from "solid-js";
import { CalculatorButtonType } from "./calculator_button_type";
import CalculatorButton from "./calculator_button_final";

const Calculator = () => {
  const [currentInput, setCurrentInput] = createSignal("");
  const [previousInput, setPreviousInput] = createSignal("");
  const [operation, setOperation] = createSignal<CalculatorButtonType | null>(null);

  const handleNumber = (value: string) => {
    setCurrentInput((prev) => prev + value);
  };

  const handleOperation = (op: CalculatorButtonType) => {
    if (currentInput() !== "") {
      setPreviousInput(currentInput());
      setCurrentInput("");
      setOperation(op);
    }
  };

  const calculateResult = () => {
    const num1 = parseFloat(previousInput());
    const num2 = parseFloat(currentInput());
    let result = 0;

    switch (operation()) {
      case CalculatorButtonType.Add:
        result = num1 + num2;
        break;
      case CalculatorButtonType.Subtract:
        result = num1 - num2;
        break;
      case CalculatorButtonType.Multiply:
        result = num1 * num2;
        break;
      case CalculatorButtonType.Divide:
        result = num1 / num2;
        break;
      default:
        return;
    }

    setCurrentInput(String(result));
    setPreviousInput("");
    setOperation(null);
  };

  const onButtonClick = (value: CalculatorButtonType) => {
    switch (value) {
      case CalculatorButtonType.Zero:
      case CalculatorButtonType.One:
      case CalculatorButtonType.Two:
      case CalculatorButtonType.Three:
      case CalculatorButtonType.Four:
      case CalculatorButtonType.Five:
      case CalculatorButtonType.Six:
      case CalculatorButtonType.Seven:
      case CalculatorButtonType.Eight:
      case CalculatorButtonType.Nine:
      case CalculatorButtonType.Decimal:
        handleNumber(value);
        break;
      case CalculatorButtonType.Add:
      case CalculatorButtonType.Subtract:
      case CalculatorButtonType.Multiply:
      case CalculatorButtonType.Divide:
        handleOperation(value);
        break;
      case CalculatorButtonType.Equals:
        calculateResult();
        break;
      // Add cases for other buttons like ToggleSign if needed
      default:
        console.warn("Unhandled button type", value);
    }
  };

  return (
    <div>
      <div class="text-lg">{currentInput()}</div>
      <CalculatorGrid onButtonClick={onButtonClick} />
    </div>
  );
};

interface CalculatorGridProps {
  onButtonClick: (value: CalculatorButtonType) => void;
}

const CalculatorGrid = (props: CalculatorGridProps) => {
  const onButtonClick = props.onButtonClick;

  return (
    <div class="max-w-xs mx-auto">
      <div class="grid grid-cols-4 gap-2">
        <CalculatorButton label="7" value={CalculatorButtonType.Seven} onClick={onButtonClick} />
        <CalculatorButton label="8" value={CalculatorButtonType.Eight} onClick={onButtonClick} />
        <CalculatorButton label="9" value={CalculatorButtonType.Nine} onClick={onButtonClick} />
        <CalculatorButton label="+" value={CalculatorButtonType.Add} onClick={onButtonClick} />
        <CalculatorButton label="4" value={CalculatorButtonType.Four} onClick={onButtonClick} />
        <CalculatorButton label="5" value={CalculatorButtonType.Five} onClick={onButtonClick} />
        <CalculatorButton label="6" value={CalculatorButtonType.Six} onClick={onButtonClick} />
        <CalculatorButton label="-" value={CalculatorButtonType.Subtract} onClick={onButtonClick} />
        <CalculatorButton label="1" value={CalculatorButtonType.One} onClick={onButtonClick} />
        <CalculatorButton label="2" value={CalculatorButtonType.Two} onClick={onButtonClick} />
        <CalculatorButton label="3" value={CalculatorButtonType.Three} onClick={onButtonClick} />
        <CalculatorButton label="*" value={CalculatorButtonType.Multiply} onClick={onButtonClick} />
        <CalculatorButton
          label="0"
          value={CalculatorButtonType.Zero}
          onClick={onButtonClick}
          class="col-span-2"
        />
        <CalculatorButton label="." value={CalculatorButtonType.Decimal} onClick={onButtonClick} />
        <CalculatorButton label="/" value={CalculatorButtonType.Divide} onClick={onButtonClick} />
        <CalculatorButton
          label="±"
          value={CalculatorButtonType.ToggleSign}
          onClick={onButtonClick}
        />
        <CalculatorButton
          label="="
          value={CalculatorButtonType.Equals}
          onClick={onButtonClick}
          class="col-span-2"
        />
      </div>
    </div>
  );
};

export default Calculator;

”Now can we wire up the buttons to create numbers and state so that when I click them I can make a number, then I can click operations and make another number, and then I can click teh equals button and the final result is displayed?” (more spelling mistakes that GPT takes like a champ)

Aside

This ain’t how I’d structure this. I’d decouple my state. Hard. But this is just a demo, I’m making a point here.

I’d probably have a stateless module that does the calculations, hides that behind some opaque whatever if I needed to, then pass that (via props or other mechanisms to my widget here.) Heck, I could ask chatGPT to help with that too, but this has already taken me too long to write and you wanna go on with your day (or whatever it is you might be doing).

I don’t love this structure either. But it’s a start. I can refactor it later.

Chill out. Back to the code.

Hey, this is nice! There are some problems though:

  • It jumps around because there is no minimum height set on the output box
  • There is no clear button.
  • The pos/neg button doesn’t work right.
  • After creating an output on equals it doesn’t clear on the next input.
  • Probably other stuff I haven’t thought of yet.

But it is functional. I can work from here on my own quite easily and consult the AI when I get stuck.

Here is one more with some fixes added. To keep the article itself short, I left it out but you can see it in the blog’s GitHub under calculator_wired2.tsx.

I also added one more query with chatGPT to find out how to write a TS function that gets the square root with Newton’s method. See the chat for details.

I then lazily added this to the calculator. I didn’t even check the output. There are errors here if you put in a negative value. Don’t come for me.

Here is the diff:

8a9,16
>   const [resultDisplayed, setResultDisplayed] = createSignal(false);
> 
>   const calculatorReset = () => {
>     setCurrentInput("");
>     setPreviousInput("");
>     setOperation(null);
>     setResultDisplayed(false);
>   };
10a19,23
>     if (resultDisplayed() === true) {
>       setResultDisplayed(false);
>       setCurrentInput("");
>     }
> 
15a29,33
>       if (op === CalculatorButtonType.ToggleSign) {
>         setCurrentInput((prev) => String(-parseFloat(prev)));
>         return;
>       }
> 
45a64
>     setResultDisplayed(true);
69a89,91
>       case CalculatorButtonType.Sqrt:
>         setCurrentInput(sqrtNewtonMethod(parseFloat(currentInput()))?.toFixed(5).toString()!);
>         break;
72a95,97
>       case CalculatorButtonType.Clear:
>         calculatorReset();
>         break;
81c106
<       <div class="text-lg">{currentInput()}</div>
---
>       <div class="text-lg min-h-12">{currentInput()}</div>
127a153,159
>         <CalculatorButton
>           label="Clear"
>           value={CalculatorButtonType.Clear}
>           onClick={onButtonClick}
>           class="col-span-2"
>         />
>         <CalculatorButton label="√" value={CalculatorButtonType.Sqrt} onClick={onButtonClick} />
133a166,192
> 
> function sqrtNewtonMethod(
>   x: number,
>   tolerance: number = 1e-7,
>   maxIterations: number = 1000
> ): number | undefined {
>   if (x < 0) {
>     console.error("Input must be non-negative.");
>     return undefined;
>   }
>   if (x === 0 || x === 1) {
>     return x;
>   }
> 
>   let s = x; // Initial guess for the square root
>   for (let i = 0; i < maxIterations; i++) {
>     let nextS = 0.5 * (s + x / s); // Update s using Newton's method formula
>     if (Math.abs(nextS - s) < tolerance) {
>       // Check if the change is within the tolerance
>       return nextS; // Found a sufficiently accurate approximation
>     }
>     s = nextS; // Prepare for the next iteration
>   }
> 
>   console.warn("Max iterations reached without converging to the specified tolerance.");
>   return s; // Return the last approximation
> }

Conclusion

So hopefully this has begun to convince you, that this tool is useful. This example is contrived but the long short is, GPT is great as a shortcut to the docs.

I know I know. “But Brandon this is just a calculator toy”. Yeah. It is, but I’ve used it for more complex things! But don’t just take my word for it, try it yourself! Try GPT4 or Gemini Ultra Pro Ultimate Edition with extras formerly known as Bard (or whatever they’re calling it now…). Code with it, try asking it questions instead of digging deep in docs. See what it says.

If you require more evidence, my most recent project that is the subject of an upcoming post, matchmaker-rs, a toy game match-maker (that does nothing) utilized this technique a lot. I had never really integrated tokio_tracing, all those different subscribers for it, made my own protocol with Serde (usually just use something that exists), or tons of other things. But I used chatGPT to help me get started and it was a huge boon to me, and I have ~10 years of experience. I like to think I’m not half bad sometimes.

I like to use it as a doc plus internet compressed search. It gets me going quickly when I don’t want to spend tons of time not coding.

Of course, grain of salt, this isn’t an engineer, and you require context and experience that you bring to the table. But it’s a great tool to have in your arsenal.

Ignore it at your peril.


Comments section loading...