koka-lang / koka

Koka language compiler and interpreter
http://koka-lang.org
Other
3.32k stars 165 forks source link

Function list/find-maybe is a duplicate of list/foreach-while #583

Open chtenb opened 1 month ago

chtenb commented 1 month ago
pub fun find-maybe( xs : list<a>, pred : a -> e maybe<b> ) : e maybe<b>
pub fun foreach-while( xs : list<a>, action : (a) -> e maybe<b> ) : e maybe<b>

I think we can remove find-maybe from the standard library?

daanx commented 1 month ago

Ha, good catch. Mm, I think it was done this way as I was considering having a different datatype for the while family of actions:

type while<a> 
   Continue
   Break( result : a )

but I didn't follow up on this. It does feel a bit weird to look for an element in a list with foreach-while, like

[1,2,3].find-maybe(fn(i) if (i>=3) then Just(i) else Nothing)

vs

[1,2,3].foreach-while(fn(i) if (i>=3) then Break(i) else Continue)

not sure.

chtenb commented 1 month ago

Interesting. The Break/Continue vocabulary is very charming.

What are your thoughts on having different isomorphic types in the core library, like maybe and while? So continuing on your example, the foreach-while function would then return a while<a> value. Suppose I would like to transform it to an a value by supplying some default value, I would want to call the fun default( m : maybe<a>, nothing : a ) : a on it. However, that function is defined for maybe types, not for while types. We could duplicate all functions on maybe to work for while values, but that has a clear downside in code complexity.

Why do want to distinguish between isomorphic types at all? I think because we like to use this in domain modelling, such that code becomes more readable, and to make it more difficult to make mistakes related to mixing up values that conceptually represent different things. One could argue that the core library does not do domain modelling, but exists to provide a programmer with a good set of basic operations. To this end, interoperability (i.e. making things possible) is perhaps more important than segmentation (i.e. forbidding things).

On the other hand, perhaps it's not a big deal, as long as there exist conversion functions between isomorphic types?

chtenb commented 1 month ago

It does feel a bit weird to look for an element in a list with foreach-while

This is probably different for everyone. For me that was a natural way of using it. I was looking through the module for an iteration function that would allow some form of conditionality, such that I could pick a first element that would satisfy a condition. Something with while in the name sounds familiar and logical to reach for. It was only later that I found the find-maybe version and noticed that it did the same thing. I also think that foreach-while sounds more general than find-maybe.

zephyrtronium commented 1 month ago

However, that function is defined for maybe types, not for while types. We could duplicate all functions on maybe to work for while values, but that has a clear downside in code complexity.

FWIW, I would say fun while/maybe(a: while<a>): maybe<a> solves this problem much more compactly.

chtenb commented 1 month ago

Yes, that is what I alluded to in the last sentence. But note that if maybe is the canonical representation of its isomorphism class, and all the utility functions are defined on maybe, you will always have to call this conversion function to do something with the result of foreach-while, save explicit pattern matching.

TimWhiting commented 1 month ago

It would be interesting to have functional inheritance as a feature of datatypes.

i.e.

type maybe<a>
 ....

type while<a> inheriting (while/maybe)
   Continue
   Break( result : a )

fun while/maybe(a: while<a>): maybe<a>
   ...

Essentially the idea is, first implicit resolution attempts to find based on while, and then if it cannot find it, also tries maybe using the implicit coercion. Coercions could be automatically generated for isomorphic types (by request).