koka-lang / koka

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

not use path based imports #333

Open xialvjun opened 1 year ago

xialvjun commented 1 year ago

not use path based imports is very important.

Node, Deno are running on the wrong way, on the other hand, Carogo/rust do it right.

Why do I emphasize this ? Let's see:

// package: ffmpeg
// file: ffmpeg/utils.js
export const a = xxx;
export const b = xxx;

// file: ffmpeg/index.js
import { a, b } from './utils';
export const encode = xxx;
export const decode = xxx;

// As a package, I want to make just `encode, decode` public, but `a, b` private to other users.
// But in JS, I can't do it. People may just:
import { a, b } from 'ffmpeg/utils';
// You must don't want it

What if we change JS to:

// package user can just import package index file
import * as ffmpeg from 'ffmpeg';

// they can't import other files in that package
import * as utils from 'ffmpeg/utils';
// this should be syntax error

// what if I want to make `ffmpeg/utils` public
// file: ffmpeg/index.js
export * as utils from './utils';
export const encode = xxx;
export const decode = xxx;

// the people can
import { utils } from 'ffmpeg'

So, JUST MAKE THIS SYNTAX ERROR: import * as utils from 'ffmpeg/utils';

Well, we can't change JS, but we should do Koka the right way.

Well, it's just like Java's public protected private, the last problem is "should it default private or protected". I prefer protected in Koka.

If it's default protected, then

// package: ffmpeg
// file: ffmpeg/src/utils.kk
fun a() xxx
fun b() xxx
pub fun c() xxx

// file: ffmpeg/src/lib.kk
pub fun encode()
  utils.a() // just use utils here
// if want to make utils public
pub utils

// package: my_app
// file: my_app/src/main.kk
use ffmpeg
// or
use ffmpeg.{utils, encode}
fun main()
  utils.c()

Originally posted by @xialvjun in https://github.com/koka-lang/koka/issues/31#issuecomment-1482200826

reference: https://api-extractor.com/pages/setup/configure_rollup/#:~:text=(The%20API%20Extractor,with%20that%20effort.)

xialvjun commented 1 year ago

@daanx Hope you fix this before it's too late.

Timmmm commented 1 year ago

Surely a better way to fix this is just to have the code explicitly set the scope of public/private? For example in Cargo you can do pub(crate) to restrict access to that crate.

Don't forget the enormous benefits that come from using path based imports:

  1. It's way simpler for users to understand.
  2. It's way simpler for IDEs to deal with - they can literally just open the file.
  3. It requires less setup and makes ad hoc scripting tractable (Deno is very good for this for example).

You can see the kind of disaster that you end up with if the import system gets too "magical" in Python.

xialvjun commented 1 year ago

I don't know why "no path based imports" may be a disaster.

Things should be simple. We can access:


sibling
sibling's public children
sibling's public children's public children...
ancestor
ancestor's public children
ancestor's public children's public children...
ancestor's sibling
ancestor's sibling's public children
ancestor's sibling's public children's public children

or short version

sibling
ancestor
ancestor's sibling
and use dot operator to access the public children of everything you can access

So, an example:

pac: package
pri: private - default
pub: public
use: we can access these things here

pac A
  pri B
    pri C
      use C, B, B.D, A, A.E, A.E.G, D (ie C,B,A,D and their recursive pubic children)
    pub D
      use D, B, B.D, A, A.E, A.E.G, C (ie D,B,A,C and their recursive pubic children)
    use C, D, B, B.D, A, A.E, A.E.G (ie C,D,B,A and their recursive pubic children)
  pub E
    pri F
      use F, E, E.G, A, A.E, A.E.G, G (ie F,E,A,G and their recursive pubic children)
    pub G
      use G, E, E.G, A, A.E, A.E.G, F (ie G,E,A,F and their recursive pubic children)
    use F, G, E, E.G, A, A.E, A.E.G (ie F,G,E,A and their recursive pubic children)
  use B, B.D, E, E.G, A, A.E, A.E.G (ie B,E,A and their recursive pubic children)
xialvjun commented 1 year ago

It seems use and mod is just sugar of const, and dependency list is just top module

// Cargo.toml
[dependency]
tokio=0.1.0
hyper=0.2.0

// In src/main.rs
// dependency list is just top module, it's just like put the content of the crate in src/main.rs
mod tokio {
  // tokio's content
}
mod hyper {xxx}
pub fn main() {}

use hyper::http;
// is the same as
const {http} = hyper;

// if you want to define a module with the same name of one of your dependency list, just shadow the name
const real_tokio = tokio;
mod tokio { xxx };

// module can shadow the name, so it's just like const.  mod is just a sugar
mod abc { pub fn a() {} }
const abc = { fn a() {}; { a } }

// even we define some compile time items like type in module, because const is also compile time
mod abc { pub type a {} }
const abc = { type a {}; { a } }
TimWhiting commented 5 months ago

I guess the request is for something like protected? If you give a Koka file a module declaration by default all of its functions are private unless explicitly marked pub. This goes for both files relative to the module (inside the library) as well as external users. Are you arguing that without something like protected (package private) we might end up making too many things public?

I don't see what that issue has to do with referring to files by path based imports. If it is private or protected (package private) then it shouldn't matter if you know the path to it, you cannot access the definition. If JavaScript messed that up it doesn't have to be the same for Koka regardless whether Koka uses paths or not.

xialvjun commented 5 months ago

My question in short: how to declare something public in its library but private out of its library.

TimWhiting commented 5 months ago

Yeah, there is no current mechanism for that in Koka, other than to include the private definitions in the library you want to use them in - though this makes it hard to share across internal files, or use a naming convention that will make it clear to external users not to use it internal- etc. I'm not sure if Daan has plans for some sort of mechanism like this. If you want users to be able to see a type, but not be able to construct / match on the type you can use the keyword abstract, which forces them to interact with it via functions you define (since they cannot inspect it at all).

Personally I would go with the naming convention. I'm not a huge fan of protected/private in general. If a user wants to use pieces of your library to create their own library that fits their use case better they have to fork it and try to keep the fork synced and up to date. A naming convention would make it clear to most users which functions are intended for internal use only, and only expert users who understand the internal mechanisms should use. Additionally I would create a top level import mypackage which includes only the necessary interfaces / functions or pub exports some other files that contain only the necessary interfaces, and put all the implementation under an internal or impl directory. This makes it clear when a user is using internal or implementation details, while encouraging them to use just the single import.

I understand that many people will not feel the same way as me, just putting out my opinion.

xialvjun commented 5 months ago

Some use case: I have a library like ffmpeg for av processing, and I need some tool functions in it, those tool functions are only used by myself in this library, I won't ensure their api stability, so they shouldn't be exposed to library users.

Well, explicit name internal- did solve problem, but explicit effect name in function function a_function_use_io() {} also solve effect type, then why we need algebraic effect.

Mean no offend, I just want to say: we should not trust human consciousness.

EDIT: Sorry, I found explicit name internal- and explicit effect name in function function a_function_use_io() {}, they do have differences. We do set explicit item name internal_, but we didn't set explicit effect name _use_io. That's the difference. I just imagined a problem which doesn't exist.

complyue commented 1 month ago

@TimWhiting I noticed there at https://github.com/koka-community/html/blob/b59d465bb8813a97e36f7310885a5e8f8ff1b893/html/html.kk#L9-L10

pub import html-builder
pub import core-components

Non-pub artifacts within html/html-builder and html/core-components seem to have been publicly re-exported from html/html, I'd imagine only pub artifacts get re-exported in this way, is current result a bug or right by design?

TimWhiting commented 1 month ago

Only the pub functions should be re-exported and available. If that is not the case we should create a new issue for this.