Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout try 2b #6

Closed
wants to merge 5 commits into from
Closed

Layout try 2b #6

wants to merge 5 commits into from

Conversation

TimWhiting
Copy link
Member

@TimWhiting TimWhiting commented Jun 5, 2024

Same as #5, but with changes to return component instead of a function like #3

@TimWhiting
Copy link
Member Author

TimWhiting commented Jun 5, 2024

By the way, here is an extended example of OO koka that I created awhile back:

named effect animalSpeak
  fun speak(): ()
  fun animalInfo(): animalInfo // Base 
  fun animalInfoX(): animalInfoX // Extendable

struct animalInfo 
  name: string
  sound: string
  calls: int;

pub open type animalInfoX
  BaseAnimal
  DogInfo(breed: string)

fun animalDefault<a, h, e>(an: ref<h, animalInfo>, action: (ev<animalSpeak>) -> <console,read<h>,write<h>|e> a): <console,read<h>,write<h>|e> a
  with f <- named handler
    fun speak()
      val a = !an
      ("The " ++ a.name ++ " says " ++ a.sound ++ "! x" ++ a.calls.show).println()
      an := a(calls = a.calls + 1)
      ()
    fun animalInfo()
      !an
    fun animalInfoX()
      BaseAnimal
  action(f)

fun dog(action)
  val animal = AnimalInfo(name = "dog", sound = "woof", calls=0)
  var breed := "german shepard"
  val an = ref(animal)
  with super <- animalDefault(an)
  with f <- named handler
    fun speak()
      super.speak()
      val a = !an
      ("The " ++ breed ++ " " ++ a.name ++ " says " ++ a.sound ++ "! x" ++ a.calls.show).println()
      an := a(calls = a.calls + 1)
      ()
    fun animalInfo()
      !an
    fun animalInfoX()
      DogInfo(breed)
  action(f)

fun cat(action)
  animalDefault(ref(AnimalInfo(name = "cat", sound = "meow", calls=0)), action)

pub fun main()
  with d <- dog()
  with c <- cat()
  d.speak()
  c.speak()

  val l = Cons(d, Cons(c, Nil))
  l.foreach(fn (x) x.speak())
  l.foreach(fn (x) x.speak())

As you'll note it supports mixed lists of different values having the same interface, which is non-typical for a functional language like Koka. Of course you can think of it as simply a record of closures, which all have access to refs for the object's state, and a ref to it's super classes state. So it definitely could be done in other functional languages, but the syntax feels pretty nice to me. You'll notice animalInfoX is an open type, which means that it could be extended outside this file to add other variants, but it will never be exhaustive in a match statement.

One limitation of this approach is the fact that the methods available cannot change (the effect has to be something that all 'extended' classes inherit). I guess we could create a subclass effect declaration with more definitions, but then you cannot mix them in a list. However, by exposing the extendable type info, we can create methods that use the evidence to get the internal data, match a specific variant, and get the appropriate bits of state depending on the type.

With an accompanying updateAnimalInfoX(), you can have most everything you expect from classes I think. Private state is just internal to the effect handler definition and not exposed in the animalInfoX.

This was referenced Jun 5, 2024
@TimWhiting
Copy link
Member Author

TimWhiting commented Jun 5, 2024

@complyue I've added you as a contributor to this repository.

As there are 0-2 users and this is somewhat of an experimental package anyways, feel free to experiment / merge and iterate on designs here. We don't have to commit to a certain API quite yet, and feel free to make breaking changes if you feel it improves the overall API. If you want feedback or help on tricky typing issues, feel free to open PRs still and I'll review them.

I'd love to get to the point where we also have APIs to update an actual DOM using Koka's JS backend. (This should be in a clearly differentiated module though, so we can use the core modules on other Koka backends for server-side programming).

@complyue
Copy link
Collaborator

complyue commented Jun 5, 2024

I'd been longing for open sum type several times in working with Haskell, so nice to hear that Koka has it!

from the animal example, I roughly feel that "name effect" maps to interface, "open type" maps to abstract class, and data constructor maps to concrete class as in Java. and yes, almost everything landed 😄 tho runtime type representation thus reflection may possibly need more work. I'd implemented https://en.wikipedia.org/wiki/C3_linearization for multiple inheritance in my dynamic/scripting language, showcased at https://github.com/complyue/tour/blob/master/lang/advanced/objs.edh , maybe the "Composition over Inheritance" approach there can be promoted to statically/typed PL (i.e. Koka) in some way, looking forward to experiment with it :p

@complyue
Copy link
Collaborator

complyue commented Jun 5, 2024

@TimWhiting , thanks for inviting me to this repo, I really have pleasure to learn Koka this way (asking your kind help whenever needed ;-), and certainly will try contribute as best here.

wrt exploring templating designs, may I suggest you commit the effect-polymorphic, component/build parts to main branch, and we rebase templating proposals atop of that? these PRs may be more lean & mean by just focusing on layout.kk and front.kk.

@TimWhiting
Copy link
Member Author

Done. Also I simplified the naming a bit to try to match the current API design.

BTW, In Koka I prefer to use shorter names like page instead of html-page, especially since we are already in the html package, and if there is ambiguity we can use html/page which is just as short as html-page. It removes a bit of redundancy.

@complyue
Copy link
Collaborator

complyue commented Jun 5, 2024

I see :) loving the new concise names 👍 and Koka is really lovable in its local namespace design 👍 👍

I realized Koka just supports function/operator overloading via that local namespacing, brilliant idea 👍 had been longing for it when with Haskell several times

html/layout.kk Outdated Show resolved Hide resolved
val title = "The Front Page"

with super <- default()
with page <- named handler
Copy link
Collaborator

@complyue complyue Jun 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how realistic it is, to add syntactic sugar to Koka, for surface syntax to omit methods simply delegating to super, in defining page here?

i.e. is it simple or complex in drafting a PR to add this?

update: might need https://en.wikipedia.org/wiki/C3_linearization with multiple inheritance in mind, also that'll need a way to store and investigate an ev<>'s supers (i.e. mro), apparently more complex.

Copy link
Member Author

@TimWhiting TimWhiting Jun 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there are a lot of considerations here, ordering of superclasses / mixins, allowing multiple different effects to have the same operation names etc. To fully support OO would be quite a change to possibly the type system as well, and not just a simple desugaring. In general I try to keep the number of operation clauses small, and then any time I think I need a new operation, see if I can make the current operations more general and provide functions that call those general clauses and do something more specific. The current design actually could be simplified a lot by creating a struct to hold the individual components and just making the effect have an operation for getting and setting that struct held in a state variable.

Comment on lines 80 to 89
fun main()
val page = do
with val cnodes-info = [
Cnode("dev", "192.168.11.50", 8, 16, 30),
Cnode("dev", "192.168.11.51", 8, 18, 30),
Cnode("gpu01", "192.168.11.20", 8, 65, 128),
Cnode("gpu02", "192.168.11.21", 8, 90, 128),
]
frontpage()
println(page.show)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TimWhiting , do you think this a sufficiently motivating example for do to go into std/core?

I'd quite like it in reminiscent of Haskell.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I don't really see the need, at least in this example, because you can omit the do and just keep the indentation and Koka evaluates this as a value (it's not a function really, just a sequence of expressions / statements and the last one is the definition's value).

Koka supports this kind of nested defintion scope.

That being said, do is kind of nice coming from Haskell. From other languages, it might require a bit more explaination, but it is also mostly self-explanatory.

Copy link
Collaborator

@complyue complyue Jun 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

besides the indentation, I mean to limit some effects/vars within a nested scope, of shorter lifetime, what's the idiomatic Koka way to do that? by far I can only think of do fitting this...

update: ah, i see, val = can just accept indented rhs, nice already 👍

@complyue
Copy link
Collaborator

complyue commented Jun 6, 2024

@TimWhiting what's the syntax to add more data constructors to an open type?

i'm tinkering with this:

pub open type object<c>
  Object(self: ev<c>)

pub effect self<c>
  val this: object<c> // head of its MRO
  // howto manage super objects of different named-effects?
  // list is incorrect for sure
  val supers: list<object<c>> // tail of its MRO

pub fun aswith<c,e>(
  obj: object<c>,
  action: (ev<c>) -> <pure,self<c>|e> a
): <pure|e> a
  with handler
    val this = obj
    // any Object should know its own MRO,
    // this might not be a proper user-facing interface,
    // neither well implemented
    val supers = Nil
  match obj
    Object(self) -> action(self)
    // how to handle other open cases here?

pub fun isa<c>(obj: object<c0>): bool
  False // howto dynamicly check c ~ c0 ?

pub fun hasa<c>(obj: object<c0>): maybe<object<c>>
  Nothing // howto?
named effect file
  fun read-line() : string

// this syntax is incorrect, how to add a data constructor to open type `object<c>`?
pub fun File(pathname: string, action: object<file> -> <io|e> a): <io|e> a
  with fh <- named handler
    fun read-line() "xxx"
  action(Object(fh))


// a free method of `object<file>`
fun file/tr(from: char, to: char): self<file> (list<string>)
  with self <- aswith(this)
  [self.read-line()] // todo: howto translate each line


fun main()
  // prepare an object<file>
  with obj <- File("/etc/hosts")

  // use an object<file>
  with self <- aswith(obj)
  // accessing raw MRO
  supers.length.show.println
  // `self` considered a facade/interface of the very object
  println( self.read-line() )

@TimWhiting
Copy link
Member Author

pub extend type object<c>
  MyConstructor(...)
  AnotherConstructor(..)

@complyue
Copy link
Collaborator

complyue commented Jun 6, 2024

Rather strange error:

pub open type object<c>
  Object(self: ev<c>)

named effect file
  fun read-line() : string

pub extend type object<file>
 FileStream(pathname: string, fh: ev<file>)

pub fun with-file(pathname: string, action: object<file> -> <io|e> a): <io|e> a
  with fh <- named handler
    fun read-line() "xxx"
/*
types do not match
context      :          FileStream(pathname, fh)
term         :                               fh
inferred type: hnd/ev<file>
expected type: hnd/ev<file>
*/
  action(FileStream(pathname, fh))

@TimWhiting
Copy link
Member Author

Instead of this:

pub extend type object<file>
 FileStream(pathname: string, fh: ev<file>)

Try:

pub extend type object<f>
 FileStream(pathname: string, fh: ev<f>)

Koka doesn't really support multiple letters in type variables. You can use a single letter followed by a number.

To restrict the type variable to a specific type you'd have to have some form of subtyping which Koka does not.

@complyue
Copy link
Collaborator

complyue commented Jun 6, 2024

To restrict the type variable to a specific type you'd have to have some form of subtyping which Koka does not.

pity to hear 😞

how does abstract type in Koka works? i don't see it's documented in the book...

@TimWhiting
Copy link
Member Author

TimWhiting commented Jun 6, 2024

I've created some documentation for some lesser known / used Koka features here in a community repo:
https://koka-community.github.io/koka-docs/koka-docs.kk.html

It's not quite polished enough to include in the Koka book I think, but it answers maybe some of your questions about abstract / extend type, under the type-definitions section.
https://koka-community.github.io/koka-docs/koka-docs.kk.html#sec-type-definitions

@complyue
Copy link
Collaborator

complyue commented Jun 6, 2024

update: with the fix from your previous suggestion, full code

https://gist.github.com/complyue/245b7b95edc298128fbab26597b08de1


another typing error - seems I've still got the wrong intuition about composing effects:

pub fun with-file<e>(pathname: string, action: object<file> -> e a): <console|e> a
/* type error: effects do not match
  context        :   println("xx")
  term           :   println("xx")
  inferred effect: $e
  expected effect: <console/console|_e1>
*/
  println("xx")
  with fh <- named handler
    fun read-line() "xxx"
  action(FileStream(pathname, fh))

help pls! why this doesn't work?

@TimWhiting
Copy link
Member Author

TimWhiting commented Jun 6, 2024

Because with-file is called in a context that has a console handler + e handlers installed, action is called in a context that has the same console handler + e handlers installed (you haven't added or removed any handlers to the context - named handlers are first class values). There are a few ways of fixing this:

Change the type to reflect what I said:

pub fun with-file<e>(pathname: string, action: object<file> -> <console|e> a): <console|e> a

This is most commonly the best solution - make sure that your function inputs have the same effect type as the returned effect type - as long as no handlers are installed.

or,
Mask the console effect:

println("xx")
with fh <- named handler
 fun read-line() "xxx"
mask<console>
 action(FileStream(pathname, fh))

Also a fine solution, a bit more runtime overhead for explicitly masking the effect, and restricts inference of the action to ensure the console effect is not used in the action (if it is used in the action it would cause a duplicate console effect, and require duplicate console handlers - which for the console effect in particular might not be an issue). This is why the first solution is typically the right choice, unless you are purposefully dealing with multiple different instances of handlers for the same effect.

or, when debugging:
Don't use println, trace is meant for temporary debugging without forcing you to add effects. It basically is the same as println, but doesn't have any effects.

trace("xx")

@complyue
Copy link
Collaborator

complyue commented Jun 6, 2024

so action() is not allowed to use less effects than its surrounding site? i feel no easy to get over this, i'd feel it sorta anti-ergonomic to require the explicit mention of console in action's type, can't effect-polymorphism work here to accommodate both console-less actions as well as console-esque actions?

update: i'm sketching for the case where with-file() needs the io (or some similar effect, using console here for easy veryfying with println) to provide handlers for the file effect, but with-file() is unwilling to expose that implementation-detail-like-effect to the file-consuming action.

update2: so mask<console> is the right technique to achieve that? is there syntax to explicitly forbid console still be contained by e in this case?

@TimWhiting
Copy link
Member Author

TimWhiting commented Jun 6, 2024

I agree that we need to make it easier to type annotate, it is not very ergonomic currently, and encourage uses of creating aliases for groups of effects, which are occasionally hard to name.

The only way to actually dismiss effects is to handle them. Otherwise you are claiming a function is a total function with respect to that effect without it actually being so. In particular Koka needs to know about handlers to appropriately compile and optimize calls to effect operation. Otherwise the optimizer might improperly index into the internal vector of handlers it keeps.

In other words if with-file needs the io effect, why should we make the user think that they are using a function that doesn't access disk, throw exceptions, or behave non-deterministically. Seems like a security risk. If the action delegates anything to the handler which uses those effects, then it is using them as well by proxy.

This doesn't prevent you from creating another handler for the file effect such as with-memory-file to create something that adheres to the file interface but uses an in memory filesystem, not accessing disk / io. And the same action can be used in both places as long as it unifies in both contexts (a total context, as well as a context that also requires io). In this case the action would need to be total of course.

Despite the security implications, I do recognize that there are legitimate reasons to dismiss some builtin effects. (Haskell provides unsafePerformIO). You can dismiss div using pretend-no-div from std/core/undiv, and you can look at the implementation of that function to create functions to dismiss other builtin abstract effects (including console). Do not dismiss the exn effect, or any other effects that have handlers though. It will mess up the effect handler machinery and cause segfaults.

I would not use mask for this case. It doesn't make sense to me, but maybe it is what you are looking for. There is also no "forbidding" of effects in a polymorphic variable - that is not polymorphism, it is subtyping. There are other languages with algebraic effects with different type systems that allow negative effects, but also are more limited in other ways.

While I do think that Koka, or some derivative of it will need or should add subtyping at some point, there is still a lot to explore with what we can do without subtyping. Koka has so many features that have not been experimented with much in practice, and I think we need to figure out the limitations, to figure out what is actually needed and what just needs new or different programming patterns that haven't been explored before or as much. Implicits, algebraic effects, named effects, static overloading, already lead into an area not explored very much at all.

Additionally Daan envisions Koka as a sort of C for functional programming. Fast, flexible and minimal but general. Algebraic effects are the kinds of minimalism he is talking about - not that it isn't powerful, but that it doesn't complicate the compiler with special compilation for async / exceptions / generators / other higher order control flow. Implicits are another example of something that is very general, and a small change to the type system. It allows you to have ad-hoc polymorphism, but also other design patterns as well, instead of a more specific feature like type-class constraints.

For more advanced type systems with subtyping, or larger changes, I think he'd rather someone create their own language that compiles to Koka. Not saying that Koka will never get subtyping - it is quite general, but that it probably will not be a priority, because it doesn't quite fit minimal as far as implementation / type system complexity, especially when Koka's type system is already getting a bit complex.

@complyue
Copy link
Collaborator

complyue commented Jun 7, 2024

thanks for the informative reply! i get more fresh new insights from your words, and realized that action: ... -> <console|e> a marks the upper bound of effects action can use, rather than the exact signature it must take, that seems the right intuition i should have got, making the 1st solution you gave quite reasonable to me now. i've verified the intuition by writing a outstanding function with smaller effect signature, feeling good 😄

i do find implicits in Koka as nice as type classes in Haskell, in adding showability to new custom ADTs, and love that it needed minimum support from the language side.

tho it may need easy ways to extend the surface syntax of Koka to further smooth lib/end programmer experience. and toward this direction, are there plans for Koka to digest IRs/ASTs translated by some mechanism on the fly, like "Template Haskell" from GHC? i'm a fun of DSL, Koka is already great at this, by custom infix operator and overloading support, it'll be even greater if fostering advanced type-level / compile-time programming.

@TimWhiting
Copy link
Member Author

TimWhiting commented Jun 7, 2024

Yes, it definitely marks the upper bound.

I am also very interested in DSLs or a form of meta-programming / flexible front-end for Koka.

Yes, I agree that Koka is already great at this with infix operators, overloading, and as exemplified by this package indentation and trailing lambdas. I think it is important to explore what kinds of DSLs we can make with the current features.

However, every time I have to write my own show / (==) functions, I feel kind of sad inside - especially if they are the straightforward default implementations. I do have a PR to automatically generate those koka-lang/koka#426, but due to recursive types and divergence the types aren't quite what Daan wants. I think I can fix that a bit, but he has other things he'd like to focus on first.

For more discussion about metaprogramming see this issue: koka-lang/koka#536
And for more discussion on OO and other design patterns lets move the discussion to this repo which I added yesterday with a few of the samples I had played around with: https://github.com/koka-community/samples

@TimWhiting
Copy link
Member Author

As far as this PR goes, how do you feel about the changes, and should we actually have the default expected js files (site.js and init-page.js), to enforce a sort of consistency? Personally I feel we should aim for more minimal, but also have some templates for 'standard' websites.

@TimWhiting TimWhiting requested a review from complyue June 7, 2024 14:32
@complyue complyue mentioned this pull request Jun 7, 2024
@complyue
Copy link
Collaborator

complyue commented Jun 7, 2024

yeah, Koka has its very inner beauty in minimalism as well as being very fundamental, i don't expect it very smooth wrt programmer experience from the early days (i.e. now), but i believe it'll get polished over time, and let's do that :D


about the changes

if u mean the bool-attrs and such? i think they can go directly into the main branch, why not?

should we actually have the default expected js files (site.js and init-page.js), to enforce a sort of consistency?

my opinion is that we can defer adding the .js .css stuff, after we've settled with some templating design, and then create example html files meant to be viewed in a browser. before that, i think we can stay with println of the html source for templating design exploration.

Personally I feel we should aim for more minimal, but also have some templates for 'standard' websites.

i'd love to work with u for that via later/other PRs, and contribute as best as i can, is that okay?


and I just created #7 , pls have a look, and see if that worth more effort to try? i think i need your help to progress on that direction.

@TimWhiting
Copy link
Member Author

i'd love to work with u for that via later/other PRs, and contribute as best as i can, is that okay?

Yeah, that is great!

@TimWhiting TimWhiting closed this Jun 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants