-
Notifications
You must be signed in to change notification settings - Fork 197
Replies: 1 comment · 18 replies
-
|
This really is about two different things:
I think it is pretty clear what the first issue is about, but I want to address the second. Structures are easy to derive, you don't have to derive them manually for each implementation. This helps avoid the issue you mention entirely. struct num<a>
(+): (a, a) -> a
(*): (a, a) -> a
fun any/num(?(+): (a, a) -> a, ?(*): (a, a) -> a): num<a>
Num((+), (*))
fun fadd( x : a, y : a, z : a, .?num : num<a> ) : a
x + (y * z)
// Library #2
// This library provides its own type, here "number":
struct number<a>
(+): (a, a) -> a
(*): (a, a) -> a
fun any/number(?(+): (a, a) -> a, ?(*): (a, a) -> a): number<a>
Number((+), (*))
fun foo( x : a, y : a, z : a, .?number : number<a> ) : a
x + (y * y * z)
// Library #3
fun main ()
println(foo(1, 2, 3) + fadd(1, 2, 3)) // mixAdditionally you can even "abstract" over names / signatures that you don't even provide in your own module. fun foo(x: a, y: a, z: a, ?fadd: (a, a, a) -> a): a
fadd(x, y, z) |
Beta Was this translation helpful? Give feedback.
All reactions
-
|
Yes, this fits nicely into the loose grouping / naming aspect of implicits in Koka (and the fact that really for implicits we are using namespaces), and has been discussed. We definitely know there are still use cases that we need to improve for implicits. But also, I'm not saying that modules are incompatible with Koka, but just that they might be less needed, or maybe something else needs to fill in the gap. |
Beta Was this translation helpful? Give feedback.
All reactions
-
|
Headsup: I'm not sure if I understand Koka well enough to not mix up some things here, but I hope I got everything right at this point. It wasn't as easy for me though to follow up on everything said.
Would you mind giving a short example on syntax? I am not following what you mean.
Well yes, when writing this response, I almost forgot that multiple implementations are fine as long as they apply to different types.
I am personally not too fond of the newtype pattern. I see some use-cases where I would like to use it (say turn an integer into a file-descriptor, for example), but I also believe (I may be wrong or change my opinion) that in many cases it is a work-around for a deficiency in the polymorphism implementation or function dispatch. I would even go as far to say that the term "newtype" usually refers to exactly that workaround. Is wrapping an integer in a file-descriptor type really a newtype pattern? Maybe it is, but I usually use the term more for wrapping a type with the intent to have other typeclasses/traits apply. In OCaml I can create a new type (or a new abstract type) when I want to make sure that type is on purpose incompatible with the inner type used for storing the data. I would never do that for changing dispatch, as OCaml simply doesn't dispatch based on the type (with a few exceptions regarding record fields and constructors, for example). When in other languages a new wrapping type is used to dispatch operations instead of describing a different semantic for the stored data, I think it's not a good pattern in many cases. For example, when I simply want to use a "sort" function in reverse order, do I really want to wrap On the other hand, if I create a new wrapping type to distinguish file descriptors from semantic-less integers, I could be annoyed if sorting fails because the type has changed and my newtype isn't
Yes, maybe that example is a case where newtype might make sense, but also because it effectively affects the meaning of each value (and the domain space as you say).
Yes, but even if both Koka and OCaml have "convention + adaptors", I still believe there is a difference. When newtypes are involved (only relevant for Koka, as OCaml doesn't do dispatch based on types), you will have to wrap and unwrap values later when using the polymorphic code (unless you wrap every function and make the function wrappers perform the wrapping/unwrapping). When no newtypes are involved, OCaml and Koka are still different. In OCaml, if the convention doesn't match, I can create an adaptor (a module) that fits a certain interface (a module type or signature) without needing to add any dependency to where the interface is defined. In Koka, I can either create a wrapper module or also a wrapper struct, each with the limitations as previously discussed.
I think it would be interesting to formalize (or at least point out using natural language) what this "price" exactly is (compared to what OCaml can do). Not sure how easy or difficult that is.
Yes, that avoids boilerplate. But I think in order to reach the same expressiveness as OCaml (in that particular matter) without having to name each item of the grouped operations (thus effectively not really grouping them), it would be necessary to do the import on a per-implicit-basis. Say you have a function that requires two monoids (as two implicit arguments). Then you might want to import one module to be used for the first implicit argument, and the other module for the second implicit argument. In OCaml that's easily possibe: module M = SomeFunctor (Additive) (Multiplicative)
let _ = SomeFunctor.foo arg1 arg2 arg3In Koka we might have some function: fun foo( arg1 : list<a>, arg2 : a, arg3 : int, ?monoid1 : monoid1<a>, ?monoid2 : monoid2<a> )But a locally scoped import wouldn't work unless we could do that on a per-argument basis, like:
Yes, and I think this makes Koka quite powerful already.
Yes, but to my understanding it hasn't been mentioned in the paper that this "grouping" is (or should be?) limited in exposure and perhaps only be a library-local syntax-aid? In a way you can't have both: Interoperability of this grouping mechanism and avoiding creating a type identity (with resulting dependencies) at the same time.
I don't understand what you mean? Could you elaborate what you mean about OCaml here? |
Beta Was this translation helpful? Give feedback.
All reactions
-
See this example file in the test suite. Here is the definition, but you'll want to look at the full example to understand what Line 9 in 338bf4f
Newtyping:
I wholeheartedly agree with you on this. I would never use a newtype to do a reverse-sort. Two different approaches / patterns I would use in Koka: one primary advantage of Koka's implicits is the ability to manually pass in an implicit - which is one way I would do a reverse sort: Convention:
I guess what I still think is that what you term an adaptation to an interface without needing to add a dependency would be more related in Koka to adding additional top level functions with the right names. To me grouping via structs is just a syntactic convenience, not conforming to an interface. Conformance to interface to me is conventional naming. In other words, I would first start by adding static overloads. I can add
I think this is rather difficult as evidenced by this conversation. Whether it is just something that could be "fixed" by adding structural subtyping is one interesting avenue of answering this question more formally. But, the fact that this deals with several features in Koka (overloading + implicits) makes it more difficult.
I agree that imports can be tricky to understand or visualize what they bring into / out of scope (especially when working with a narrow aspect). However, I think there is a bit of a misunderstanding here. There is no need to locally scope imports here, because I assume
At the end of the section we explicitly state that we are still learning the limitations in practice: "This simple mechanism can work surprisingly well, but of course it is limited as well compared to more specialized mechanisms like type classes. We hope to gain more experience in practice to evaluate its limits."
I don't believe I said that you can avoid creating a type identity with grouping. You can avoid the type identity with static overloading and convention. Implicits and static overloading are two separate mechanisms, but tightly related, and surprisingly powerful together as the paper shows.
I just mean that regular OCaml doesn't have implicits. Every disambiguation or adaptation is done manually rather than through implicits. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Ah thanks, I understand now.
The manual passing I see in other languages too (minus the part that it's an implicit in Koka and can be omitted in the standard cases in Koka, which is better in Koka of course). But this only works if it's a single operation (or a few operations that are all explicitly passed) or a struct. In the last case, we have the "type identity issue" again, as discussed (which is the main concern of my original post). As a side note, we see an explicit passing of a sorting function even in OCaml, see
I'm not sure if
My feeling (and I don't have any substantiated or formal arguments for this) is that types should not be used with the sole intent to discriminate different operations that work on the same underlying data. I believe newtypes should be used when we have the same representation for an entirely different datum from a semantic point of view (like "a file descriptor is an abstract entity, and not the same as an integer"). We then can select operations based on that newtype, of course, but I don't think we should motivate a newtype in order to do that selection of operations. But in some languages we are forced to do the latter.
Interesting thought. Like we could sort a file-descriptor by default in that case.
I think I understood that part now and it's what I meant with: "that this 'grouping' is (or should be?) limited in exposure and perhaps only be a library-local syntax-aid?" The grouping mechanism outlined in the paper is not meant to be used as an interface. Interfaces are still done through conventional (ungrouped) naming. (Or only grouped using modules.) This is something I misunderstood for a while. Specifically, I didn't conclude this from the "limitations" mentioned in the paper. But if interfaces are really given by conventional naming, then Koka's limitations on grouping operations and passing grouped operations aren't really limited by the
Well, yes, you previously proposed two use different conventions for those:
Maybe the monoid example is a bit unfortunate here. There could be an example where some code needs two "abstract numbers", e.g. numbers for weights and numbers for coordinates, and you would want to be able to pass different implementations. In some OCaml code I recently was writing a functor as follows: module Make
(WeightNum : Number.S)
(CoordNum : Number.S)
(VertexSet : Set.S with type elt = CoordNum.t Iarray.t) : … = …
Yes, the paper doesn't say anything wrong. I just intepreted more into this "grouping" and assumed it could be used for interfaces (why shouldn't I assume it?). Now I learned it is likely not the canonical way to define interfaces. Maybe that point could be made more clear in the future. Like I said above, the practical limitations then will not just arise from this "grouping" (and what it cannot do), but also from what can or cannot be done when not using that grouping mechanism (while we still might want to handle a set of types and operations on them as a single unit).
Oh, yeah sure. Specifically, OCaml can be very verbose syntactically. And this is how I would like to draw a small résumé for myself: I really like the way how OCaml (not Koka, sorry 😅) allows me to specify interfaces that group types and operations while avoding dependencies (due to the structural typing of signatures). It also allows me to pass implementations of those interfaces. I have not seen any other language so far, which gives me this expressiveness, and I really love it. But OCaml does not have implicits at all. This makes syntax verbose and unhandy in practice. Moreover, every functor in OCaml is a lot of syntactical noise. While I like OCaml's approach in theory (disregarding several inconsistencies and issues in OCaml, e.g. this one), I feel like Koka's approach may be much nicer in practice, but I still see limitations in theory. I would like to better understand what I'm really missing in Koka, coming from OCaml. Maybe all it needs is an easier way to import modules selectively in order to reach the same level of expressiveness? Or maybe the problem I see is only of theoretical value anyway and doesn't occur in practice? I'm not sure. Either way, thanks again a lot for this discussion. When I hopefully have some more time to try Koka in practice, I might get a better feeling for the different options Koka offers (and maybe feel its limitations or learn that they are neglectible or even non-existent). Generally, I really like the dot-syntax and the idea of combining implicits with overloading. It might create very easy-to read and concise code (opposed to the clunky syntax in OCaml). |
Beta Was this translation helpful? Give feedback.
All reactions
-
I think your opinion is totally valid. OCaml does not have the same theoretical issue as Koka due to its structural subtyping. Thanks as well for the discussion, especially since this is one of the parts of the paper that we could use the most feedback on what the limitations are in practice and theory. I wonder if we can address the structural typing issue with only a slight change in grouping. Currently In other words, we can view "interfaces" / structural typing, as just another kind of constraint on a particular shape of polymorphic function. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I have a remark on implicits, referring to the paper Syntactic Implicit Parameters with Static Overloading.
As I already said last year in thread #777, I love this approach. I do have a possible concern however.
Specifically I would like to have a look at "Grouping Operations" as discussed in Section 4.2 of your paper, and as also discussed in the other thread (777), and comparing it to some aspects of OCaml's module system, specifically signatures.
What I love both about Koka's implicits and also about OCaml's signatures is that they avoid "interface identities". In OCaml, two equally defined OCaml signatures are indistinguishable:
Say
NumandNum2are defined by entirely different libraries. Then it's still possible to use these two interfaces interchangeably. Similiarly, using implicits in Koka, two different libraries could demand the same interface without needing to depend on each other or a common dependency. Consider:Here Koka library 1 and Koka library 2 both provide functions (
faddandfoo) that operate on numbers that have an addition and multiplication. These two libraries don't need to know about one another, and if they use the same nomenclature, they are automatically compatible.But things change when we group operations:
Even disregarding the different naming,
int/numandint/numberare not compatible, because they are different types. I see this as an unwanted property, arising from using (or abusing?) types to group implicits, because types have an identity.Compare with OCaml, where this problem doesn't occur:
I wonder if it is worth considering adding a dedicated language construct to group implicits to avoid the issue in Koka, and I'm curious about your assessment of this issue.
Beta Was this translation helpful? Give feedback.
All reactions