Coding with Claude.ai, a short journey
Understanding the uses, limits and pitfalls of AI as your “coding buddy” requires some work and investigation. I’ve chosen to investigate with Claude.ai and compare coding solutions in Elixir, Erlang and Javascript. My goal here is to evaluate idiomatic style and forecast how the approaches may hold up. Let’s start with the claude.ai chat interface, nothing fancy like cursor.
The task I’ve chosen is to transform a list of number digits with a valid range of zero to nine into their english words. My approach is to use a minimal prompt instead of building a deep/detailed base prompt.
Javascript Solution
My first prompt is for Javascript:
you are an experienced javascript programmer and you need to transform an array of numbers to an array of words that represent the numbers. write the javascript to handle zero through nine numbers.
Claude’s generated work plan is straight forward:
I’ll write a JavaScript function that transforms an array of numbers (0-9) into an array of their corresponding word representations.
Number to Word ConverterCode This function takes an array of numbers (0-9) and returns an array with their word representations. It includes error handling for:
- Checking if the input is an array
- Validating that each number is an integer between 0 and 9
Would you like me to explain how the code works or modify it in any way?
Code result is:
/**
* Converts an array of numbers (0-9) to their word representations
* @param {number[]} numbers - Array of numbers to convert
* @returns {string[]} Array of words representing the numbers
*/
function numbersToWords(numbers) {
const wordMap = {
0: "zero",
1: "one",
2: "two",
3: "three",
4: "four",
5: "five",
6: "six",
7: "seven",
8: "eight",
9: "nine"
};
// Check if input is an array
if (!Array.isArray(numbers)) {
throw new Error("Input must be an array");
}
// Convert each number to its word representation
return numbers.map(num => {
// Check if the number is within the valid range
if (num < 0 || num > 9 || !Number.isInteger(num)) {
throw new Error(`Number ${num} is not a valid integer between 0 and 9`);
}
return wordMap[num];
});
}
Provided example usage:
// Example usage
const numberArray = [1, 4, 0, 9, 5, 2];
const wordArray = numbersToWords(numberArray);
console.log(wordArray); // ["one", "four", "zero", "nine", "five", "two"]
Nothing unexpected in that solution. The code is exactly as the plan stated.
A “functional” approach
So now let’s try to go functional with javascript with this additional prompt:
use a functional programming approach
The work plan now has a significant number of additions:
I’ll rewrite the solution using a more functional programming approach:
Functional Number to Word ConverterCode
This version embraces functional programming principles by:
- Using arrow functions throughout
- Avoiding mutable state (no variables are reassigned)
- Using pure functions that have no side effects
- Using function composition with method chaining
- Using declarative rather than imperative style
- Using expressions instead of statements where possible
- Including immediate function invocation for error handling instead of imperative throws
- Storing the word array directly in the function rather than in an object
Adding an additional example showing function composition through chaining
The core logic is separated into a numberToWord function that handles individual number conversion, and a numbersToWords function that applies this conversion across an array using map.
These new plan statements seem reasonable as “functional” constraints. Let’s see the code it produces:
/**
* Maps numbers 0-9 to their word representations
*/
const numberToWord = num => {
const words = [
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine"
];
return (num >= 0 && num <= 9 && Number.isInteger(num))
? words[num]
: (() => { throw new Error(`Number ${num} is not a valid integer between 0 and 9`); })();
};
/**
* Converts an array of numbers (0-9) to their word representations
* using functional programming principles
*/
const numbersToWords = numbers =>
Array.isArray(numbers)
? numbers.map(numberToWord)
: (() => { throw new Error("Input must be an array"); })();
With the following examples:
// Example usage
const numberArray = [1, 4, 0, 9, 5, 2];
const wordArray = numbersToWords(numberArray);
console.log(wordArray); // ["one", "four", "zero", "nine", "five", "two"]
// More functional-style examples:
// Example with pipe-like approach (though JS doesn't have built-in pipe)
const result = [3, 7, 2, 0]
.filter(n => n % 2 === 0) // Keep only even numbers
.map(numberToWord) // Convert to words
.join(", "); // Join with commas
console.log(result); // "two, zero"
Putting the examples aside, it is unclear why the first example uses a map and the second uses an array. The more functional solution seems a bit more concise, but it relies heavily on the ternary operator since if
is not an expression (with a value).
Erlang Solution
Switching now to Erlang with the prompt:
Now you are an experience erlang programmer. write the same program in erlang
The generated work plan is:
I’ll implement the number-to-word conversion program in Erlang:
Number to Word Converter in ErlangCode
This Erlang implementation follows functional programming principles that are natural to the language:
- The solution uses pattern matching, a core feature of Erlang, to handle different input cases
- It’s composed of pure functions with no side effects
- Error handling is done through pattern guards and explicit error throwing
- It uses lists:map/2 for functional transformation of the list
- The code is immutable - no variables are reassigned after initial binding
The main function numbers_to_words/1 takes a list of integers and maps each one to its word representation using number_to_word/1. The number_to_word/1 function uses pattern matching to convert each number to its corresponding word.
The example at the bottom shows how you would compile and use this module in the Erlang shell.
These principles make all the difference in the outcome.
-module(number_converter).
-export([numbers_to_words/1]).
%% @doc Converts a list of numbers (0-9) to their word representations
-spec numbers_to_words(list(integer())) -> list(string()).
numbers_to_words(Numbers) when is_list(Numbers) ->
lists:map(fun number_to_word/1, Numbers);
numbers_to_words(_) ->
erlang:error("Input must be a list").
%% @doc Converts a single number to its word representation
-spec number_to_word(integer()) -> string().
number_to_word(0) -> "zero";
number_to_word(1) -> "one";
number_to_word(2) -> "two";
number_to_word(3) -> "three";
number_to_word(4) -> "four";
number_to_word(5) -> "five";
number_to_word(6) -> "six";
number_to_word(7) -> "seven";
number_to_word(8) -> "eight";
number_to_word(9) -> "nine";
number_to_word(Num) when is_integer(Num) ->
erlang:error(io_lib:format("Number ~p is not between 0 and 9", [Num]));
number_to_word(_) ->
erlang:error("Input must be an integer").
Example usage guidance:
%% Example usage:
%% 1> c(number_converter).
%% 2> number_converter:numbers_to_words([1, 4, 0, 9, 5, 2]).
%% ["one","four","zero","nine","five","two"]
Raising errors the way this code does is unusual and is probably undesirable.
Elixir solutions
Moving on to Elixir with the following prompt:
Now you are an expert Elixir programmer. rewrite the program in elixir
Which generates the following plan:
Here’s the number-to-word converter implemented in Elixir, which embraces the language’s functional and pattern matching features:
Number to Word Converter in ElixirCode
I’ve provided two implementations to showcase different Elixir approaches:
The first implementation uses pattern matching for each number, which is idiomatic in Elixir for smaller sets of values.
The second implementation uses a map for lookup, which can be more maintainable for larger sets or when the mappings might change.
Both implementations:
- Use Elixir’s module and function structure
- Include proper typespecs with @spec
- Include doctests and documentation with @doc and @moduledoc
- Use pattern matching and guards for validating input
- Leverage Enum.map/2 for transforming lists
- Raise appropriate exceptions with descriptive messages
- Follow Elixir’s functional programming principles
To run this code in an Elixir project, you would save it to a file and then compile and run it in IEx (Elixir’s interactive shell).
Before getting to the code, Claude decided to produce two solutions. The justification is simply that maps are more extensible than pattern matching. We’ll see. In both cases it is leaning on raising errors instead of planning an interface that can communicate failure in a more manageable way.
Solution 1:
defmodule NumberConverter do
@moduledoc """
Converts numbers from 0-9 to their word representations.
"""
@doc """
Converts a list of integers (0-9) to their word representations.
## Examples
iex> NumberConverter.numbers_to_words([1, 4, 0, 9, 5, 2])
["one", "four", "zero", "nine", "five", "two"]
iex> NumberConverter.numbers_to_words([3, 7])
["three", "seven"]
iex> NumberConverter.numbers_to_words([10])
** (ArgumentError) Number 10 is not between 0 and 9
iex> NumberConverter.numbers_to_words("not a list")
** (ArgumentError) Input must be a list
"""
@spec numbers_to_words(list(integer())) :: list(String.t())
def numbers_to_words(numbers) when is_list(numbers) do
Enum.map(numbers, &number_to_word/1)
end
def numbers_to_words(_) do
raise ArgumentError, message: "Input must be a list"
end
@doc """
Converts a single number (0-9) to its word representation.
"""
@spec number_to_word(integer()) :: String.t()
def number_to_word(0), do: "zero"
def number_to_word(1), do: "one"
def number_to_word(2), do: "two"
def number_to_word(3), do: "three"
def number_to_word(4), do: "four"
def number_to_word(5), do: "five"
def number_to_word(6), do: "six"
def number_to_word(7), do: "seven"
def number_to_word(8), do: "eight"
def number_to_word(9), do: "nine"
def number_to_word(num) when is_integer(num) do
raise ArgumentError, message: "Number #{num} is not between 0 and 9"
end
def number_to_word(_) do
raise ArgumentError, message: "Input must be an integer"
end
end
The “alternate” elixir solution:
# Alternative implementation using a more concise approach with maps
defmodule NumberConverterAlt do
@word_map %{
0 => "zero",
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
6 => "six",
7 => "seven",
8 => "eight",
9 => "nine"
}
def numbers_to_words(numbers) when is_list(numbers) do
Enum.map(numbers, &number_to_word/1)
end
def numbers_to_words(_) do
raise ArgumentError, message: "Input must be a list"
end
def number_to_word(number) when is_integer(number) and number in 0..9 do
Map.get(@word_map, number)
end
def number_to_word(number) when is_integer(number) do
raise ArgumentError, message: "Number #{number} is not between 0 and 9"
end
def number_to_word(_) do
raise ArgumentError, message: "Input must be an integer"
end
end
Where it falls apart
Extended the elixir code to handle numbers from 1 to 100
The earlier plans were fine for their limited scope. But which one would an experienced programmer choose? Without cause or intuition (which Claude lacks), there’s no good selection criteria. When I looked at the solutions, especially the two for Elixir, I knew immediately that the map approach was undesirable. Now we’ll see why.
I’ll extend the Elixir code to handle numbers from 1 to 100, which requires handling teens, tens, and compound numbers:
Extended Number to Word Converter in Elixir (1-100)Code
This extended implementation:
- Handles all numbers from 0 to 100 with appropriate English words
- Uses separate maps for ones digits (0-9), teens (10-19), and tens (20, 30, etc.)
- Handles compound numbers (21-99, excluding teens) by combining the tens and ones with a hyphen
- Treats 100 as a special case (“one hundred”)
- Maintains all the input validation from the original implementation
- Includes comprehensive documentation and examples
The solution follows Elixir’s functional paradigm by:
- Using immutable data structures (maps) for the word mappings
- Using pattern matching and guards for appropriate function selection
- Breaking down the problem into clear, logical components
- Using conditional logic with cond for handling different number ranges
- Providing clear documentation with doctests
I’ve also included a demo module showing how you might use the converter in a pipeline, demonstrating Elixir’s powerful pipe operator for functional composition.
defmodule NumberConverter do
@moduledoc """
Converts numbers from 0-100 to their word representations.
"""
@ones %{
0 => "zero",
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
6 => "six",
7 => "seven",
8 => "eight",
9 => "nine"
}
@teens %{
10 => "ten",
11 => "eleven",
12 => "twelve",
13 => "thirteen",
14 => "fourteen",
15 => "fifteen",
16 => "sixteen",
17 => "seventeen",
18 => "eighteen",
19 => "nineteen"
}
@tens %{
2 => "twenty",
3 => "thirty",
4 => "forty",
5 => "fifty",
6 => "sixty",
7 => "seventy",
8 => "eighty",
9 => "ninety"
}
@doc """
Converts a list of integers (0-100) to their word representations.
## Examples
iex> NumberConverter.numbers_to_words([1, 42, 100, 15])
["one", "forty-two", "one hundred", "fifteen"]
iex> NumberConverter.numbers_to_words([0, 10, 21, 99])
["zero", "ten", "twenty-one", "ninety-nine"]
iex> NumberConverter.numbers_to_words([101])
** (ArgumentError) Number 101 is not between 0 and 100
iex> NumberConverter.numbers_to_words("not a list")
** (ArgumentError) Input must be a list
"""
@spec numbers_to_words(list(integer())) :: list(String.t())
def numbers_to_words(numbers) when is_list(numbers) do
Enum.map(numbers, &number_to_word/1)
end
def numbers_to_words(_) do
raise ArgumentError, message: "Input must be a list"
end
@doc """
Converts a single number (0-100) to its word representation.
## Examples
iex> NumberConverter.number_to_word(0)
"zero"
iex> NumberConverter.number_to_word(15)
"fifteen"
iex> NumberConverter.number_to_word(42)
"forty-two"
iex> NumberConverter.number_to_word(100)
"one hundred"
"""
@spec number_to_word(integer()) :: String.t()
def number_to_word(number) when is_integer(number) and number >= 0 and number <= 100 do
cond do
number == 0 ->
@ones[0]
number == 100 ->
"one hundred"
number < 10 ->
@ones[number]
number < 20 ->
@teens[number]
true ->
tens_digit = div(number, 10)
ones_digit = rem(number, 10)
if ones_digit == 0 do
@tens[tens_digit]
else
"#{@tens[tens_digit]}-#{@ones[ones_digit]}"
end
end
end
def number_to_word(number) when is_integer(number) do
raise ArgumentError, message: "Number #{number} is not between 0 and 100"
end
def number_to_word(_) do
raise ArgumentError, message: "Input must be an integer"
end
end
There are now three maps and a cond that is branching on ranges, which rewrites the same lower order digits instead of decomposing the problem into the units that are already expressed. In short, it does not use recurse for lower order digits.
This approach with maps and tiered functions ignores the composability of numbers and creates “data” as a separate entity that adds complexity to the solution without an upside.
For reference, the examples provided:
# Optional pipe-based example
defmodule NumberConverterDemo do
def run do
# Example using pipes
[4, 20, 13, 75, 100]
|> NumberConverter.numbers_to_words()
|> Enum.join(", ")
|> IO.puts()
# Outputs: "four, twenty, thirteen, seventy-five, one hundred"
# Example with filtering
[110, 5, 200, 42, -3, 99]
|> Enum.filter(&(&1 >= 0 and &1 <= 100))
|> NumberConverter.numbers_to_words()
|> IO.inspect()
# Outputs: ["five", "forty-two", "ninety-nine"]
end
end
Erlang is to 100
Asking the same extended version for Erlang:
extend the erlang example to handle number from 1 to 100
I’ll extend the Erlang implementation to handle numbers from 1 to 100: Extended Number to Word Converter in Erlang (1-100)Code This extended Erlang implementation handles numbers from 0 to 100, following functional programming principles:
The program is organized into multiple helper functions:
ones/1 for single-digit numbers (1-9) teens/1 for numbers 10-19 tens/1 for the tens place words (twenty, thirty, etc.)
The main number_to_word/1 function uses pattern matching and guards to:
- Handle special cases (0 and 100) directly
- Route numbers to the appropriate helper function based on their range
- Combine tens and ones for compound numbers (21-99, excluding teens) with a hyphen
- Validate that numbers are within the specified range
The implementation maintains immutability, with no variables being reassigned after their initial binding. Error handling is done through pattern guards and explicit error throwing. The numbers_to_words/1 function uses lists:map/2 to transform a list of numbers to their word representations.
The code now effectively handles all numbers from 0 to 100, including the special cases of zero, teens (10-19), round tens (20, 30, etc.), compound numbers (like 42, 78), and one hundred.
%%%-------------------------------------------------------------------
%%% @doc Extended number to word converter in Erlang
%%% Converts numbers from 0-100 to their word representations
%%%-------------------------------------------------------------------
-module(number_converter).
-export([numbers_to_words/1, number_to_word/1]).
%% @doc Converts a list of numbers (0-100) to their word representations
-spec numbers_to_words(list(integer())) -> list(string()).
numbers_to_words(Numbers) when is_list(Numbers) ->
lists:map(fun number_to_word/1, Numbers);
numbers_to_words(_) ->
erlang:error("Input must be a list").
%% @doc Converts a single number to its word representation
-spec number_to_word(integer()) -> string().
number_to_word(0) -> "zero";
number_to_word(100) -> "one hundred";
number_to_word(Number) when Number >= 1, Number =< 100 ->
if
Number < 10 -> ones(Number);
Number < 20 -> teens(Number);
true ->
TensDigit = Number div 10,
OnesDigit = Number rem 10,
case OnesDigit of
0 -> tens(TensDigit);
_ -> tens(TensDigit) ++ "-" ++ ones(OnesDigit)
end
end;
number_to_word(Number) when is_integer(Number) ->
erlang:error(io_lib:format("Number ~p is not between 0 and 100", [Number]));
number_to_word(_) ->
erlang:error("Input must be an integer").
%% @doc Converts ones digit (1-9) to word
-spec ones(integer()) -> string().
ones(1) -> "one";
ones(2) -> "two";
ones(3) -> "three";
ones(4) -> "four";
ones(5) -> "five";
ones(6) -> "six";
ones(7) -> "seven";
ones(8) -> "eight";
ones(9) -> "nine".
%% @doc Converts teens (10-19) to word
-spec teens(integer()) -> string().
teens(10) -> "ten";
teens(11) -> "eleven";
teens(12) -> "twelve";
teens(13) -> "thirteen";
teens(14) -> "fourteen";
teens(15) -> "fifteen";
teens(16) -> "sixteen";
teens(17) -> "seventeen";
teens(18) -> "eighteen";
teens(19) -> "nineteen".
%% @doc Converts tens digit (2-9) to word
-spec tens(integer()) -> string().
tens(2) -> "twenty";
tens(3) -> "thirty";
tens(4) -> "forty";
tens(5) -> "fifty";
tens(6) -> "sixty";
tens(7) -> "seventy";
tens(8) -> "eighty";
tens(9) -> "ninety".
%% Example usage:
%% 1> c(number_converter).
%% 2> number_converter:numbers_to_words([0, 7, 13, 25, 42, 70, 99, 100]).
%% ["zero","seven","thirteen","twenty-five","forty-two","seventy","ninety-nine","one hundred"]
%%
%% 3> number_converter:number_to_word(54).
%% "fifty-four"
%%
%% 4> number_converter:number_to_word(101).
%% ** exception error: "Number 101 is not between 0 and 100"
This is effectively the same as the Elixir version, but not using maps and still ignores the decomposition of numbers. It still hobbles the approach by using different functions for different digits.
My Erlang solution
I took the first Erlang implementation and used the same function with heads to deal with larger numbers that had only different cases but reused the same function by recursion to decompose larger numbers and reuse the lower digit code. This yields a shorter and more consolidated outcome and the larger cases are progressive in the function heads. With only a few lines it handles up to 1000.
%%%-------------------------------------------------------------------
%%% @doc Number to word converter in Erlang
%%% Converts numbers from 0-9 to their word representations
%%%-------------------------------------------------------------------
-module(number_converter).
-export([numbers_to_words/1]).
%% @doc Converts a list of numbers (0-9) to their word representations
-spec numbers_to_words(list(integer())) -> list(string()).
numbers_to_words(Numbers) when is_list(Numbers) ->
lists:map(fun number_to_word/1, Numbers);
numbers_to_words(_) ->
erlang:error("Input must be a list").
%% @doc Converts a single number to its word representation
-spec number_to_word(N :: integer()) -> string().
number_to_word(0) -> "zero";
number_to_word(1) -> "one";
number_to_word(2) -> "two";
number_to_word(3) -> "three";
number_to_word(4) -> "four";
number_to_word(5) -> "five";
number_to_word(6) -> "six";
number_to_word(7) -> "seven";
number_to_word(8) -> "eight";
number_to_word(9) -> "nine";
number_to_word(10) -> "ten";
number_to_word(11) -> "eleven";
number_to_word(12) -> "twelve";
number_to_word(13) -> "thirteen";
number_to_word(15) -> "fifteen";
number_to_word(18) -> "eighteen";
number_to_word(N) when N < 20 ->
number_to_word(N-10) ++ "teen";
number_to_word(20) -> "twenty";
number_to_word(30) -> "thirty";
number_to_word(40) -> "fourty";
number_to_word(50) -> "fifty";
number_to_word(60) -> "sixty";
number_to_word(70) -> "seventy";
number_to_word(80) -> "eighty";
number_to_word(90) -> "ninety";
number_to_word(N) when N < 100 ->
number_to_word(N div 10 * 10) ++ " " ++ number_to_word(N rem 10);
number_to_word(N) when (N < 1000) and ((N rem 100) == 0) ->
number_to_word(N div 100) ++ " hundred ";
number_to_word(N) when (N < 1000) ->
number_to_word(N div 100 * 100) ++ number_to_word(N rem 100);
number_to_word(Num) when is_integer(Num) ->
erlang:error("out of range");
number_to_word(_) ->
erlang:error("Input must be an integer").
Conclusions
The AI solutions beyond 0-9 revises the solution for the already solved problem 0-9 instead of leveraging that but adding on to it for teens and higher. The AI preferred use of maps for Elixir make the code more messy, or just bigger and split up, which obscures a simpler solution. Interestingly, the Erlang solution with function heads is good at the start but gets ugly for the bigger numbers. This might be pedantic for such a simple problem, but it is an indicator of the bias or approach that does have implications for more complex problems. In other words, in the context of this article, which is already too long, a simple problem has to be over examined for hints about how AI will try to solve more complex problems.
AI gets to a working solution, and one that’s not inherently bad. But it misses the most direct approach. A junior, or new to functional programming, developer would probably miss the same thing. Adding more guardrails to the prompt, updating the prompt to exclude undesirable approaches may have produced the recursive answer in the end. This is an exercise I leave to the reader.