So I did some research on how JavaScript handles default values, and wanted to document that here. Do we even need ? in the type definition?
--Do default values need to apply to record type only?
optionalRecord : {a: Num a, b: Num a, c ? Num a}
optionalRecord = {a:1 b:2}
--JavaScript allows default values for function arguments with equal sign regardless of the type
primitveType = (n=1)-> n+1
tupleType = ([a,b=1])->a+b
ObjectType = ({a,b=2})->a+b
--Can default values be applied to all types in ROC? Should ROC even default values?
primitiveType : Num a -> Num a
primitiveType = \n ? 1 -> n + 1
tupleType : (Num a, Num a) -> Num a
tupleType = \(a,b ? 1,c) -> a + b + c
recordType : {a: Num a, b: Num a} -> Num a
recordType = \{a, b ? 2,c} -> a + b + c
callPrimitiveType =
primitiveType _ --I used an underscore place holder to indicate the absence of a value
callTupleType =
tupleType (1,_,2) --What to put in the middle here to represent an explicit missing value?
callRecordType =
recordType {a:1,c:2} --Easier since we can just not pass b.
--More complex scenario with renaming variables for multiple instances of the same type
--Renaming variables and defaulting those variables would have to be supported at the same time.
--Destructuring inside the function definition
MyTuple : (Num a, Num a)
tupleType : MyTuple, MyTuple -> Num a
tupleType = \(a1,b1?2),(a2,b2?4)->(a1+a2)-(b1+b2)
callTupleType =
tupleType (1, _) (3, _)
MyRecord : {a: Num a, b: Num a}
recordType : MyRecord, MyRecord -> Num a
recordType = \{a:a1,b:b1?2},{a:a2,b:b2?4}->(a1+a2)-(b1+b2)
callRecordType =
recordType {a:1} {a:3}
--Destructuring in the body of the function
MyTuple : (Num a, Num a)
tupleType : MyTuple, MyTuple -> Num a
tupleType = \tuple1,tuple2->
(a1,b1?2) = tuple1
(a2,b2?4) = tuple2
(a1+a2)-(b1+b2)
callTupleType =
tupleType (1, _) (3, _)
MyRecord : {a: Num a, b: Num a}
recordType : MyRecord, MyRecord -> Num a
recordType = \obj1, obj2->
{a:a1,b:b1?2} = obj1
{a:a2,b:b2?4} = obj2
(a1+a2)-(b1+b2)
callRecordType =
recordType {a:1} {a:3}
The difficulty is ROC would have to know that the properties or values eventually get a default value inside the body or function definition if they are not provided when calling the function. If you don't pass a required value to the function, and there is no default, then ROC would obviously need to throw a compiler error.
yes, that's what the ? in the type solves :big_smile:
(it conveys that the field can safely be omitted because a default has been provided, and if it turns out that's not true, then the compiler can tell you about the disagreement)
Would it be beneficial to ROC to add? for any type including tuples, not just records only?
defaultNum : ? Num a -> Num a
defaultNum = \n ? 1 -> n + 1
callDefaultNum =
defaultNum _
I think it would be detrimental. :big_smile:
my conclusion from looking into optional positional arguments and "nullable types" in other languages is that they would lead to worse APIs in Roc, and so we should explicitly choose not to support either
in contrast, I do think optional labeled arguments are valuable
and that was the original motivation for optional record fields
I think it's worth exploring alternative designs to the current optional record field system, but I don't think introducing nullable types or optional positional arguments are things Roc should have :big_smile:
Richard Feldman said:
my conclusion from looking into optional positional arguments and "nullable types" in other languages is that they would lead to worse APIs in Roc, and so we should explicitly choose not to support either
Can you explain this more? Specifically optional positional arguments. Not sure why nullable types matter for this. I am just thinking about default values.
basically I looked at:
Str.join that takes an optional Str for what to intersperse between the strings, defaulting to ""), where would it make sense to use it? And then same question: does it seem like the resulting world of Roc APIs is better or worse?and it seemed like in both situations the "positional optional arguments get used" designs were worse
leading me to conclude that those other languages (without naming names) would have better APIs if the languages dropped support for that feature :sweat_smile:
it's an interesting exercise - like try it for some Roc APIs, see where it feels like it would make sense to use that feature if it existed, and then compare it to the alternative design where that feature doesn't exist (both times trying to aim for the nicest design possible, with the only variable changing being whether or not you're using optional positional arguments in some way)
And this comment is specific to positional optional args? Not named optional args?
yeah
I agree with that outcome regarding default value handling :wink: but methodology-wise, aren't there situations in which you need to change more than one variable?
Limiting it to a single variable change at a time limits the range of outcomes that can be explored, since language features (and omissions) work together. Changing just one variable from X=A -> X=B might be a worse world (as might any change to X alone), yet X=B Y=D might be an altogether better world than the current arrangement of X=A Y=C
I'm just zooming in on your use of the words "with the only variable changing"...
Last updated: Jun 16 2026 at 16:19 UTC