Are you sure you can free an STRef when you leave the runST? You can hide an STRef in an existential (if you have those) and smuggle it out. You can’t do anything very interesting with it, but it is reachable.
I’m looking forward to running some code soon then.
The only thing you can with an STRef without the the state token is compare them for equality and it shouldn’t be an issue to compare dead pointers for equality if they never move. It’s impossible to compare dead pointer to live ones too because the s parameters have to be the same in the Eq instance. I am a bit worried that LLVM or some other C optimizer would freak out if it saw dead pointer comparisons though.
Small update. I implemented putStrLn (and Text.pack, Text.putStrLn) so now this example works.
module Main where
type Peano :: *
data Peano = Zero | Succ Peano
length :: Peano -> String
length (Succ peano) = '+' : length peano
length Zero = ""
class Double a where
double :: a -> a
instance Double Peano where
double (Succ peano) = Succ (Succ (double peano))
double Zero = Zero
quadruple :: (Double a) => a -> a
quadruple peano = double (double peano)
three, twelve :: Peano
three = Succ (Succ (Succ Zero))
twelve = quadruple three
main :: IO ()
main = putStrLn (length twelve)
Here’s is what the generated code looks like: Peano.js - Pastebin.com
Also, this is in already Readme, but let me reiterate the process of generating this.
$ cp -R runtime/ output
$ cabal run hazy -- -I library/runtime/ library/base -o output
Configuration is affected by the following files:
- cabal.project
$ cabal run hazy -- -I library/runtime/ -I library/base/ Peano.hs -o output
Configuration is affected by the following files:
- cabal.project
$ esbuild output/index.mjs --bundle --format=esm --platform=node --outfile=Peano.js
Peano.js 4.6kb
Done in 13ms
$ node Peano.js
++++++++++++
$
Today is my birthday and I’d figure I would celebrate by giving a progress report on Hazy.
Here’s a quick list of what I’ve done over these past 3 months
- Syntax sugar
- Lambda expressions
- Case expressions
- Lambda case expressions
- Let pattern binding
- Integer literals
- Floating point literals
- Do notation
- Right sections
- Type annotation expressions
- Language features
- Default methods
- Monomorphic generalization
- Newtypes
- Strict fields
- Library / Tooling
- A significant part of the Prelude has been implemented
- A bare minimum package format for saving and loading sets of modules
I also have another new extension: LevityPolymorphicFields. You can now parameterize a field by it’s strictness.
type List :: Levity -> Type -> Type
data List s a
= Nil
| Cons
{ head :: ~!s a,
tail :: ~!s (List s a)
}
Here List Strict a would be a fully strict list while List Lazy a would be a lazy one. I have more details in my readme.
Of course all of this isn’t enough for Hazy to be usable yet as there’s still some missing parts, but I am getting a lot closer.
Why is the arrow naked here? I’d expect either a new arrow or some other syntax.
Naked, my System F brain sees no reason Levity affects other args here. I don’t like that. System F is the north star.
you seem to pass it along..but not in a meaningful. dependent way. You’re close (the extension is a good idea) but no cigar.
Highly recommend being brave enough to submit this to ghc-proposals`. That’ll ensure it passes bona fide muster.
I wonder, is the Levity attached to the arrow? In Coalton they attached * to the arrow, like “Int * Char → Int,” or “Int → Char * Text”. I.e one can give multiple inputs and output multiple outputs for a function, but there isn’t a free-standing “a*b”. I find this really neat in a strict language like Coalton. (PureScript has a less neat mechanism, but similar idea.)
I wonder, is the Levity attached to the arrow?
Well, I did make this post: Levity should be on the arrow, not on the kind. So far I only implemented levity polymorphic fields and not levity polymorphic arrows, but it’s something I plan on doing eventually.
I also have a post on this: Levity should be on the arrow, not on the kind - #35 by superstar64. If you have levity on the arrow in system-f, then these levity polymorphic fields appear in scott encoding.
Happy belated birthday!!!
Let me post another update. I’ve implemented the following:
- Binding groups
- Global generalization (across mutually recursive modules too)
- Local generalization
- List comprehensions
- Prefix negation
- Record updates
Hazy is now at the point where nearly all of Haskell 2010’s language features work. The main missing feature now is deriving. There are these deficiencies that I need to fix too:
- Contained type variables are not generalizated
- Constraints are limited to unique type class / type variable pairs
- Some missing builtin instances
- Other small issues
Lastly, these are the language features. Almost none of Hazy’s base is implemented.
Okay, another update. I’ve spent most of the last few months in refactoring hell so I haven’t done too much.
- Hazy now has preliminary support for
deriving - Hazy now implements
MultilineStrings - Hazy now has a
HygenicHidingextension
The deriving support is very bare bones at the moment. I only support deriving instance at the moment because that’s easier to implement. I also only support deriving Eq at the moment. Still this is a start, adding other classes should be easy.
The HygenicHiding is a tiny extension that fixes what I consider to be a flaw in Haskell 2010. It makes it so hiding declarations don’t have top level constructors. This makes it symmetric with standard import lists. This wouldn’t hide anything for example: import Prelude hiding (Left).
Lastly, I want to show off a fun parlor trick. This works as expected:
equal :: forall a. Eq a => a -> a -> Bool
equal a b = Wrapper a == Wrapper b where
newtype Wrapper = Wrapper a
deriving instance Eq Wrapper
HygenicHiding
From one compilateur to another, I too think that Haskell’s import system could use some love. Its often hard to put into terms precisely what it means, but I often wish for things like a foo as bar syntax in imports to alias conflicting names.
Have you seen the import proposals? Several proposals for Import syntax. I’m kinda like exposing (with my addendum: import-exposing-impqual by akhra · Pull Request #763 · ghc-proposals/ghc-proposals · GitHub).
If there were implemented, there would have to be some questions regarding fields and instance definitions that have to be answered. Consider something like this:
import Point (Point, x as abc, y as xyz)
p1 = Point { abc = 1, xyz = 2 }
p2 = Point { x = 1, y = 2 }
Here, p1 would be legal if selectors are their own standalone thing while p2 would be legal if fields are attached to constructors. Haskell 2010 leans more toward p1, while Hazy (with ConstructorsFields) would use p2. A similar problem would happen with instance definitions too.
Mmmm I have seen them now, haven’t I? Certainly food for thought.
Turning off field selectors aside (I usually have them turned disabled), my gut reaction is to ask “what do you do upon any other naming collision?” which of course is always a choice and not a mathematical given. I would personally probably throw a potential collision error, but allow shadowing unless they make it fatal.
My reasoning is: It’s an import, the consumer has control and is choosing how to read the interface. This is symmetric to an export being when how the producer has control and chooses to expose it.
Fun fact: Haskell 1.2 had this feature, and it was removed in Haskell 1.3. According to the rationale: “Instead of using renaming to resolve name clashes, Haskell 1.3 uses qualified names.”
I don’t remember much about the discussion at the time, but I do remember that renaming was a pain to implement and pretty complex to specify in the report too. Given that there are alternatives, my guess is it’s not a feature worth its weight.
Intriguing! I would like to investigate this more to understand why
I found it relatively easy to implement as a renaming pass in my toy compiler a few years ago, basically giving the module import the same name shadowing powers as a let-in declaration (if I remember properly, that’s precisely how I did it). To be fair, that was a toy compiler so I didn’t have the same bulk to deal with as GHC, so maybe I didn’t run into the same problems because I didn’t reach a large enough scale.
On the other hand, I am fully intending on bringing that feature back in my SECD machine, and it has a significant advantage in that I’m designing the compiler for first-class content-addressability (memory, cryptography, recursion-schemes, oh my), which I hope will trivialize renaming.
does his investigating
Aha! So in 1.2, there were no qualified names, and module import renaming was the only way to deal with conflicts. In 1.3, qualified names were added, and so module import renaming was removed, likely due to a mix of wanting to shepherd people into using the new qualified names, and not wanting to support multiple mechanisms at the time. That seems sensible.
Qualified names being equally powerful, there was a decision to prefer one over the other, which made sense at the time because Haskell was much much smaller and the overhead of doing so was far more meaningful. I think it was the right decision at the time.
However, I’d argue that the decision was more of an economic decision of “is it worth it to support both?”, rather than an academic “Is it possible to support both?”. It wasn’t worth the weight back then - but what about now?
For reference, FunctionalDependencies and TypeFamilies are similarly-competing features with significant overlap, carrying far more implementation weight - yet we choose to support both. I’m going to implement this in my compiler, and see if maybe the juice is worth the squeeze now.