tekknolagi / scrapscript

A functional, content-addressable programming language.
https://scrapscript.fly.dev/repl
Other
359 stars 9 forks source link

Implement alternates #39

Open tekknolagi opened 10 months ago

tekknolagi commented 10 months ago
 ()
  . _ = scoop :: chocolate ( )
  . scoop : # vanilla ( ) # chocolate ( ) # strawberry ( )
  . _ : p = p :: point { x = 3 , y = 4 }
  . p : # point { x : int , y : int }
  . tuple : x => y => z => # pair { x : x , y : y } # triplet { x : x , y : y , z : z }
surprisetalk commented 8 months ago

Okay, I'll put some design thoughts here before I rush into another PR :)

First, after a bit of thought, I like the idea of calling these things Tags (short for "tagged unions" and nice mnemonic with the octothorp/hashtag). Let me know if that's palatable for y'all.

Next thing. I want to Tags always to hold exactly one value, BUT I want it syntactically to default to a Hole () if not provided. In practice, it'll look like this:

()

. _ = 7 == flavor-score flavors1::chocolate

-- defaults to hole on matching too
. flavor-score =
  | #vanilla -> 9
  | #chocolate -> 7
  | #strawberry -> 3

-- these are semantically equivalent
. flavors1 : #vanilla #chocolate #strawberry
. flavors2 : #vanilla () #chocolate () #straberry ()

. _ = p1::point { x = 2, y = 6 } |> | #point { x, y } -> y

-- use records or pairs for multiple args
. p1 : #point { x : int, y : int }
. p2 : #point (pair int int)

-- a generic pair could look like this
. pair : a => b => #pair { x : a,  y : b }

I'd like to make these things structurally transparent, so the following should technically work:

()

-- specifying a type annotation to restrict usage
. h : t1 =
  | #a _ -> ()
  | #b _ -> ()

-- good: g t1
-- good: g t2
-- type error: g t3
. g =
  | #a _ -> ()
  | #b _ -> ()
  | #c 3 -> ()
  | #c _ -> ()

-- type error: f t1
-- type error: f t2
-- type error: f t3
. f =
  | #a _ -> ()

. t1 : #a int #b int
. t2 : #b int #c int
. t3 : #b int #c text

In order to make all of this work, the following structure seems like a good start:

class Tags(Obj):
  value: Obj | None
  type: dict[string, Type]

We might also have to have a Tag class for the matching syntax.

Let me know what y'all think!

surprisetalk commented 8 months ago

Ah, clarifying point on implementation:

-- { value: null, type: { a: "int", b: "int" } }
. _ = (#a int #b int)

-- { value: { a: 1 }, type: { a: "int", b: "int" } }
. _ = (#a int #b int)::a 1
tekknolagi commented 8 months ago

Seems reasonable, I think. We already have Symbol in case you want to rename that to Tag or something. It's there to be the boolean tagged union

Are you planning on making the type errors run-time type errors?

Also: you want to add nulls?

surprisetalk commented 8 months ago

Perfect, I'll take Symbol then.

The type errors should eventually be compile-time type errors. I don't think we have a type-checker yet though, so would you like me to go down that rabbit-hole first?

No nulls! haha. The reason I have a null there is to distinguish a "loaded" Tags vs. "unloaded" Tags. The object carries the value as well as its type information.

-- UNLOADED
-- Tags( value: null, type: { a: "int", b: "int" } )
. _ = (#a int #b int)

-- LOADED
-- Tags( value: { a: 1 }, type: { a: "int", b: "int" } )
. _ = (#a int #b int)::a 1
tekknolagi commented 4 months ago

Simple variants added in #123 -- but no compile-time checks, type annotations, etc

surprisetalk commented 4 months ago

Great! I have been swamped lately -- I'm going to close this PR for now

surprisetalk commented 4 months ago

Oops, I need to close my PR, not this issue haha