Open VictorTaelin opened 7 years ago
I guess my question is: what could the first-class description have that the Boehm-Berarducci encoding wouldn't?
For example, the Boehm-Berarducci encoding of the type Bool
is:
∀(Bool : *) → ∀(True : Bool) → ∀(False : Bool) → Bool
... which has the exact same information as data Bool = True | False
. Same thing for lists:
∀(a : *)
→ ∀(List : *)
→ ∀(Cons : ∀(head : a) → ∀(tail : List) → List)
→ ∀(Nil : List)
→ List
Ah, the point is that such a first class description could be used like that:
True = DeriveConstructor 0 BoolT
That would return the Boehm-Berarducci-encoded constructor. Or, for example:
DepthTreeInt = DeriveDepth (TreeT IntT)
DeriveDepth, on that case, returns a function that takes the depth of any ADT given its description. If I'm not mistaken you can't create such a DeriveDepth
function using the Boehm-Berarducci type itself, you need some kind of description of the ADT. So, basically, not an alternative to the encoding, just a convenience to derive sctructural functions for it automatically. Which makes programming on Morte easier. I remember having read a paper about something like that, but it was loong ago...
What would be the type of DeriveConstructor
, though?
DeriveDepth
is pretty easy: you just encode every type description as a record where one field is the depth and then DeriveDepth
just extracts that field.
I'm not sure what you're saying, but DeriveDepth would for example return the length
function in the case of a List... makes sense that DeriveConstructor couldn't have a type... which kinda sucks, so we can't have first-class generics like we can on the untyped λ-calculus...
Edit: actually I think it could have a type, yes, let me try something
Posted this on S.O., I wonder if you have any clue whether something like that can be typed? I'm having some trouble with the fact it is supposed to return different types and I'm not sure if I can't get it right or if it is just impossible. Do you know?
I believe it's not possible in System Fw. For anything where the type of the result doesn't change (like depth) then it's easy, but if the type of the result changes then you run into issues.
Hey it is actually possible as long as * : *
, but I think you wouldn't be very happy with that, right?
Yeah, that won't type check in System Fw (or any other consistent type system) because once the type system accepts * : *
then you can make anything type check by creating a value of type ∀(a : *) → a
that loops infinitely
How would that value look like?
I don't understand the details myself, but I believe that this paper provides the "simplest" explanation of how to construct such a value: https://www.cs.cmu.edu/~kw/scans/hurkens95tlca.pdf
Hey I found another way to make that work, you just have to give a type to □
, like, □:●
where ●
itself has no type. Then this code does it:
∀ (D: □) ->
∀ (A: □) ->
∀ (M: □) ->
∀ (data: (A -> D)) ->
∀ (add: (M -> A -> A)) ->
∀ (zer: A) ->
∀ (mul: (D -> D) -> M -> M) ->
∀ (one: M) ->
D
λ (data: #Data ) ->
(data
*
(* -> *)
(* -> *)
(λ (DT:(* -> *)) -> ∀ (T:*) -> (DT T))
(λ (CT:(* -> *)) -> λ (E:(* -> *)) -> λ (T:*) -> ((CT T) -> (E T)))
(λ (T:*) -> T)
(λ (FT:((* -> *) -> * -> *)) -> λ (E:(* -> *)) -> λ (T:*) -> ((FT (λ (x:*) -> x) T) -> (E T)))
(λ (T:*) -> T))
λ (D: *) ->
λ (A: *) ->
λ (M: *) ->
λ (data: A -> D) ->
λ (add: M -> A -> A) ->
λ (zer: A) ->
λ (mul: (D -> D) -> M -> M) ->
λ (one: M) ->
(data
(add (mul (λ (nat:D) -> nat) one)
(add one zer)))
Here, Data
is the type of ADT descriptions, Data.Type
is a function of type Data -> *
which returns a type given an ADT. We can then apply (#Data.Type #Nat.Data )
and that normalizes to ∀ (a:*) -> (a -> a) -> a -> a
! It even works for nested types like lists of nats and so on. With that, our user could write something like Type (Add (Mul Rec One) (Add One Zero))
instead of ∀ (a:*) -> (a -> a) -> a -> a
.!
That might sound worse but we could use that same expression to derive constructors, equality, functor, all at language level. Also if we had a syntax sugar for strings, we could have type (data "Succ Rec | Zero")
, where data
parses a string to get an ADT, using dependent types to fail at compile time if it isn't a valid syntax. Basically a template system but with just functions.
Just to be clear, I know the same can be achieved by features outside the language such as Annah, but the reason I insist on this so much is that the core principle of functional languages is that, whenever you find something complicated enough, you abstract over it with functions. All those: equality, constructors, zip, map, fold, match, read, show, serialize, deserialize, and possibly more, are difficult to write by hand, but can be mechanically generated from a datatype description. So why, in 2016, should a programmer be writing them manually? I find it a shame that generic programming is so complex in Haskell and often needs outside help ("templates"). Hopefully that will not be the case with CoC, and we'll be able to derive tons of stuff from just a single ADT definition.
@MaiaVictor (* : *)
gives Russell's paradox:
Hey cool, that is quite short. I wonder how is that in Morte.
@MaiaVictor Also, if this sort of thing interests you, you might find this paper interesting: http://web.cs.ucla.edu/~palsberg/paper/popl16-full.pdf
Gabriel, I'm wondering if you're aware of any first-class description of datatypes. I don't mean the encoding themselves, but an actual representation of the types per see. For example, the description of
Bool
would somehow encode the information indata Bool = True | False
and, with it, one could create both constructors generically. The description ofList
would be a functionDescription -> Description
, which could, again, be used to create thecons Type
andnil Type
constructors. I guess you get what I'm talking about. Is there any reference for that?