Laziness and TMC are fundamentally different from partial instantiation in that they are implementation details, mostly/wholly invisible to the language semantics themselves.
A key aspect of partial instantiation is that the "holes" may be filled in by a semantically unrelated piece of code, which is not the case for either laziness or TMC (wherein the contents data structure must be defined in one place, even if the implementation does not evaluate it immediately).
Yes, that is true! Koka's constructor contexts also allow you to do this. A constructor context is a data structure with exactly one hole which you can fill in with an arbitrary value (or even several arbitrary values, copying the constructor context in the process).
Also, you can do lazy initialization in any language that has functions by passing a getter function around instead of a value.
I recently had need for this to implement validation for recursive data structures in TypeScript. It depends on support for forward references in the body of a function. The tricky bit is ensuring that the getter isn’t called until the cycle is completed by defining the forward reference’s target. The type system doesn’t help; you just have to write the implementation so it doesn’t call the getter.
I don't think Haskell can do this, can have a growable linked list for example. 'last a' is 'last a', regardless what is between them (modulo shadowing and such).
And I suspect that Prolog's Partial Instantiation is, while not mutating data, but it is mutating references somewhere
Technically, Haskell laziness is just mutability under the hood :)
And the "difference list" mentioned in the article is also in Haskell - although framed differently (more "functionally")
type DList a = [a] -> [a]
concat :: DList a -> DList a -> DList a
concat = (.)
toList :: DList a -> [a]
toList d = d []
fromList :: [a] -> DList a
fromList = (++)
Haskell has cyclic data structures, which also can’t be implemented without a mutable reference somewhere, though it may be buried in the implementation.
The difference is being able to define an incomplete data structure (with a forward reference) and then defining the target of the reference at runtime. Most languages will complain about an undefined reference if it’s not defined by the end of a module.
You could do it with soft references, though. Use a symbol or string to refer to something and define it later.
colanderman|1 year ago
A key aspect of partial instantiation is that the "holes" may be filled in by a semantically unrelated piece of code, which is not the case for either laziness or TMC (wherein the contents data structure must be defined in one place, even if the implementation does not evaluate it immediately).
(I don't know Koka so I can't speak to that.)
anfelor|1 year ago
skybrian|1 year ago
I recently had need for this to implement validation for recursive data structures in TypeScript. It depends on support for forward references in the body of a function. The tricky bit is ensuring that the getter isn’t called until the cycle is completed by defining the forward reference’s target. The type system doesn’t help; you just have to write the implementation so it doesn’t call the getter.
bmacho|1 year ago
And I suspect that Prolog's Partial Instantiation is, while not mutating data, but it is mutating references somewhere
whateveracct|1 year ago
And the "difference list" mentioned in the article is also in Haskell - although framed differently (more "functionally")
skybrian|1 year ago
The difference is being able to define an incomplete data structure (with a forward reference) and then defining the target of the reference at runtime. Most languages will complain about an undefined reference if it’s not defined by the end of a module.
You could do it with soft references, though. Use a symbol or string to refer to something and define it later.
unknown|1 year ago
[deleted]