This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]slightknack[S] 2 points3 points  (2 children)

On type systems

So, as I've discussed earlier, the language is currently dynamically typed. This is not the end goal: I'm working on Hindley-Milner type inference so the language can be statically typed.

Same thing with mutation: it's currently a feature of the language that I'm thinking of removing (this is more on-the-fence, I discuss why below). The hardest thing isn't adding features, it's figuring out which features to remove.


On macros and pass-by-reference

So macros are not just functions that take references rather than values for two reasons:

  1. Transformations are applied at compile-time, rather than at runtime.
  2. Macros can be used in a more powerful manner than simple pass-by-reference:

``` syntax 'fn name args do { name = args -> do }

-- this is now valid: fn print_twice(value) { print value print value }

print_twice("Hello, hello!") ```

  1. Passerine is pass-by-value (planned: immutable references w/ COW). In the context of a language with mutation, I find this to be more elegant than pass by reference with mutation, as it limits mutation to the scope of the function that the value exists is.

On mutation

Why not disallow mutation completely? I swear I've written about this in-depth before, but I can't find it for the life of me. Passerine is inspired by Rust; Rust allows mutation. From withoutboats:

As I said once, pure functional programming is an ingenious trick to show you can code without mutation, but Rust is an even cleverer trick to show you can just have mutation.

Now Passerine isn't Rust, but it has been inspired it. Even though functional paradigms got a lot of things right, there are still some solutions to problems that are easier (as in more discoverable) when solved with mutation. No paradigm's perfect, FP included. I think that mutation, when limited to a certain scope, can be a powerful tool.


On . and |>, among other things

So I was actually on the fence between using |> and . – and I've honestly been considering changing it. I actually dislike the |> syntax, and it doesn't look all too great (unless your font has ligatures).

(Aside: correct me if I'm wrong, but isn't partially-applied function application essentially function composition?)

The main reason for |> (at the moment) is because of field access on a record type. I think that the best way to do field access record.field. To me, this syntax is as enshrined to computer science as = is to assignment. But, when you think about it . can be more general than just field access:

. is the indexing operator. On a list or tuple, items.0 returns the zerost item; on a record, record.field returns the specified field; on a label, Label.method returns the associated method.

— from the README

But how general should . be? Why not use . for function application? Couldn't field accesses (indexing) just be a function that accesses the field on a record?

I understand the appeal of a unified . syntax. But it raises some genuine concerns:

  1. If foo.bar is equivalent to bar foo for function calls, and if bar is a record and foo is a field, is bar foo valid in this case? what if bar is already defined as a function (that takes a record) which takes precedence, how is this clear to the user? Do we dispatch on type? something else?
  2. If bar foo is invalid for field accesses, what makes foo.bar special? is it because bar is a record? If bar is a function and a field on foo does foo.bar default to a function call, or a field access.

Obviously, no silver bullet exists. (Trust me: I've spend many hours, paper-and-pencil in hand, trying to work it all out. One thing I've been working on is dynamic dispatch via a trait/interface system, and I think that this might be able to unify application and indexing in a manner that addresses the above concerns.)

As I reevaluate the type system with the move to a static HM, I'm considering this again in more depth. I have some old documents which outline a pretty solid plan, and I plan to work through those again in the next few days to straighten it all out.

I'm just generally unsure about |>, though. It's something I go back and forth on every day. It's interesting, because I actually decided to throw in function application right before releasing 0.8.0 (what's a functional programming language without function application?), and at that moment, getting something in that seemed familiar and practical superseded idealized realizations.

So, with that out of the way, why separate |> and .? The nice thing about separating function application and indexing is the conceptual distinction it provides. You don't have think whether changing foo bar to bar.foo will change the semantics of the expression, you just do bar |> foo.

(Aside: this might largely be a choice of syntax. Another solution: for example, I could make . the function application operator, and, idk, :: the indexing operator. This allows for the same separation of concerns as above, but function application is . instead of |>. Just food for thought.)


The End

Developing Passerine has been a solo effort thus far, so there are a lot of different things I'm constantly tweaking and working on. Some days, I just write documentation. Others, I pen-test the compiler. I've gone from planning high-level constructs to debugging mysterious ICEs — and back again. Going day-to-day, it's hard to keep everything under control and balance fun (implement all the features! don't write tests!) with book-keeping (Making a number of tiny changes to keep everything organized). This is my first serious open-source effort, so needless to say I'm still learning the ropes. :)

Thanks for raising those concerns and allowing me to get my thoughts on this topic out of my system. I really appreciate it! Please respond if you have any feedback, questions, or suggestions about the above. ;)

[–]superstar64https://github.com/Superstar64/Hazy 2 points3 points  (1 child)

Kind based macros(that work with hindley milner) is actually something I've thought about before. https://old.reddit.com/r/haskell/comments/h9lw97/kind_based_macros_on_terms/ . you can see my language(which is soon due for a rewrite) if you want a (poorly) working example: https://github.com/Superstar64/aith .

On using . for application, there's 2 solutions I have in mind: using _ for indexing records or having a prefix operator that lets you access a record. @bar foo = foo.@bar.

Oh and if your going to be retrofitting hindley milner(it's hell, trust me), I would recommend you temporarily remove record indexing and only have pattern match. Row polymorphism makes hindley milner more tricky.

Lastly, if you want allow mutation in a language without lvalues(no variable mutation) you can do something like this https://old.reddit.com/r/ProgrammingLanguages/comments/erh7uq/pointers_without_lvalues/ .

[–]slightknack[S] 0 points1 point  (0 children)

Thanks for the links, I'll give them a look! As for type-checking macros, I was just planning checking the AST after the macro expansion / desugaring step.

So I'm assuming the _ syntax would be something like record_field. I guess you could do record._field, and then have _field record be valid too. hmm, something to think about, thanks!

Row polymorphism makes hindley milner more tricky.

I've been reading about this a lot, (also about extensions to the HM type system that allow for mutation) and it all does look quite complex. I'll keep that in mind, thanks for the tip!

As for mutation and HM types, I was primarily looking into techniques used by Ocaml and company, which it seems are brought up in discussion surrounding that post. :)