Hacker News new | past | comments | ask | show | jobs | submit login

Author here. I've been working on Fo part time for 4 months. Feel free to ask me anything.



Hi there! I worked on something similar a while ago: a compile-to-Go superset that adds some generic functions/methods. You may have come across it: https://github.com/lukechampine/ply

My perspective was that when I find myself wishing for generics in Go, what I really want is not full-blown generic types and functions, but rather a few helper functions like map/filter/reduce, or converting a map to a slice, etc.

Having "true" generics is undoubtedly useful when you really need them, but the Go ecosystem has shown that they aren't strictly required in order to write large, maintainable programs. And you pay for them in the form of misuse. In that sense, I view generics much the same as operator overloading.

Anyway, I ran into some tricky edge cases when working on Ply and I'm curious how you address them. First, Go lets you define local types, i.e. with a function scope instead of top-level scope. You can't define methods on these, nor can you reference them in top-level functions. How does Fo handle the case where I want to call a generic function/method on a locally-scoped type?

The second issue I ran into was imports. I didn't devote a ton of time to this, but it seemed like it could be tricky to properly fetch both Go and Ply packages and pre-compile the Ply packages to Go. How does Fo handle this?


Ply looks really cool! I can certainly understand the appeal of that kind of approach in terms of simplicity, stability, and better interop.

Local types are an edge case I'll need to spend more time working on. The type-checker won't have any problems; the real question is how to generate the appropriate Go code. One solution might be to move the local type into a higher scope during code-generation. Another might be inlining the relevant generic types in the same scope as the local type. Finally, the best solution might just be to say this sort of thing isn't allowed and the compiler will return an error.

Imports aren't supported right now, but it's one of the most important things I need to work on next. It'll be pretty tricky, but I'm confident I can find a solution.

The fortunate thing about the approach I'm using with Fo is that I have complete control over the parser, type-checker, and code-generator. At the cost of having significantly more complexity, I have the flexibility to tackle these sorts of edge cases without relying solely on existing tooling.


Did you consider parameterized packages? The idea is you can declare a set of related objects/types/methods as well as have concrete type specific initialization. E.g.

  package stack[t]
  type Type []t
  func New() Type { return Type{} }
  ...
  
Then it can be used so:

  import s “stack”[int]
  var s1 = s.New()


Just as additional info to others, this is how Ada, Modula-2 and Modula-3 generics kind of work.


I think parameterized packages are a great idea. It would be a light-weight way of getting just a bit of generic code into go.

I made gotemplate to explore that idea

https://github.com/ncw/gotemplate

This requires a round of `go generate` for the actual code generation, but otherwise quite a similar experience.

Having it built in would be great!


Parameterized packages are interesting but they come with their own problems. It can get tedious if I want to use, e.g., a stack with multiple different types. They also lose some of the flexibility that comes with scoping type parameters to any function/method/data structure.


On the other hand writing such packages becomes easier as only two syntax extensions are needed (package header and import). You can easily abstract a package by replacing a concrete type with a package level parameter. More importantly, there is no other mechanism to tie together a bunch of objects, functions, methods etc. to the same parameter types.


I’ve seen this in ocaml and always thought it strange. I think it’s useful, but binding it to a package seems odd since packages are units of code distribution or some such.


It is not strange if you think of objects in OOP as modules/packages you can inherit from.


Think of it as a package macro.


Any plans to add formal sum types (as opposed to interface hacks)?


Definitely something I'm thinking about. It's one of a handful of features that I have in my head but haven't created an issue for.


> haven't created an issue for

You have one now: https://github.com/albrow/fo/issues/7

:)


What does the implementation look like under the hood? What are the limitations of your approach (i.e. generic interfaces) and are they fundamental or just on the todo list?


The Fo compiler parses and type-checks Fo source code and then generates and outputs Go code. It generates a unique concrete type for each usage of a generic type. You can look at the examples directory of the repo to see what this output looks like: https://github.com/albrow/fo/tree/master/examples/box.

Generic interfaces are pretty fundamental IMO. It's something I plan to add to the language before the v1 release.



It's very likely monomorphizing the types, i.e. creating a unique type for each generic invocation.


Can you do ad-hoc or bounded polymorphism? What about recursive types?


Yes, I've already been thinking about constraining type parameters to an interface. Internally, the type-checker considers type parameters to have underlying type interface {}. A constraint on a type parameter should be as straightforward as changing it's underlying type to a specific named interface instead of an empty one. The only tricky bit I can see is thinking about how this would work with generic interfaces or whether we should allow other kinds of type constraints (e.g., union types).


If it considers parametric types to be interface {}, is that a kind of type erasure? I don't know much about Go, but it's my understanding that interface {} types have an extra layer of indirection and are essentially just pointers.


Did you consider incrementing the first letter instead of decrementing when choosing a name?


The name would be "Ho", maybe he did.


Does this have anything in common with Go besides the language syntax? Is it binary-compatible with Go?


It seems to compile ("transpile") to Go, and then invoke Go compiler on the generated source. So I guess that would make it also binary compatible?

https://github.com/albrow/fo/blob/master/main.go#L70


Briefly looking at source one can tell it just transforms Fo into Go and invokes go run




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: