profile
viewpoint

aspiwack/assert-plugin 7

A GHC plugin for rich, switchable assertions

aspiwack/finset 7

A Coq library for extensional finite sets and comprehension

aspiwack/fulltrees 6

Balancing lists: a proof pearl

aspiwack/cosa 4

A thing about Coq-verified Shape Analysis

aspiwack/milner 2

Small implementation of Milner tactics for minimal logic

aspiwack/cadence-of-hyrule 1

Cadence of Hyrule technical sheets

aspiwack/dissection-of-l 1

A dissection of L: article about focalised sequent calculus and dependent types

aspiwack/minipi 1

Experiment on depent type-checking.

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commenttweag/linear-base

Dupable wars : implementation attempt

+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneKindSignatures #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Unrestricted.Internal.Replicator (Replicator (..), consume, fmap, pure, (<*>), next, next#, extract, Elim (..)) where++import Data.Kind (Constraint, Type)+import Data.Unrestricted.Internal.ReplicationStream (ReplicationStream (..))+import qualified Data.Unrestricted.Internal.ReplicationStream as ReplicationStream+import GHC.TypeLits+import Prelude.Linear.Internal++data Replicator a where+  Moved :: a -> Replicator a+  Streamed :: ReplicationStream a %1 -> Replicator a++consume :: Replicator a %1 -> ()+consume (Moved _) = ()+consume (Streamed stream) = ReplicationStream.consume stream++fmap :: (a %1 -> b) -> Replicator a %1 -> Replicator b+fmap f = \case+  Moved x -> Moved (f x)+  Streamed stream -> Streamed $ ReplicationStream.fmap f stream++pure :: a -> Replicator a+pure = Moved++(<*>) :: Replicator (a %1 -> b) %1 -> Replicator a %1 -> Replicator b+(Moved f) <*> (Moved x) = Moved (f x)+sf <*> sx = Streamed (toStream sf ReplicationStream.<*> toStream sx)+  where+    toStream :: Replicator a %1 -> ReplicationStream a+    toStream = \case+      Moved x -> ReplicationStream.pure x+      Streamed stream -> stream++next :: Replicator a %1 -> (a, Replicator a)+next (Moved x) = (x, Moved x)+next (Streamed (ReplicationStream give dups consumes s)) =+  dups s & \case+    (s1, s2) -> (give s1, Streamed (ReplicationStream give dups consumes s2))++next# :: Replicator a %1 -> (# a, Replicator a #)+next# (Moved x) = (# x, Moved x #)+next# (Streamed (ReplicationStream give dups consumes s)) =+  dups s & \case+    (s1, s2) -> (# give s1, Streamed (ReplicationStream give dups consumes s2) #)++extract :: Replicator a %1 -> a+extract (Moved x) = x+extract (Streamed (ReplicationStream give _ _ s)) = give s++type Elim :: Nat -> Type -> Type -> Type -> Constraint+class (n ~ Arity b f) => Elim n a b f | n a b -> f, f b -> n where+  elim :: f %1 -> Replicator a %1 -> b++instance Elim 0 a b b where+  elim b r =+    consume r & \case+      () -> b+  {-# INLINE elim #-}++instance (Arity b (a %1 -> b) ~ 1) => Elim 1 a b (a %1 -> b) where+  elim f r = f (extract r)+  {-# INLINE elim #-}++instance {-# OVERLAPPABLE #-} (n ~ Arity b (a %1 -> f), Elim (n - 1) a b f) => Elim n a b (a %1 -> f) where+  elim g r =+    next r & \case+      (a, r') -> elim (g a) r'+  {-# INLINE elim #-}++type family Arity b f where

Oh, I didn't mean to leave this here indeed. I meant to remove the line altogether or make a type error (probably the latter), it was just a quickly put together placeholder. Thanks for spotting this.

tbagrel1

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config+could be:++```text+let+server = import "server.ncl",+firewall = import "firewall.ncl",+in+server & firewall+```++In the simple case where both records do not have common fields, it gives a+record which is the union of both.++If you have to merge records with fields in commons, you will use two new concepts:++- default annotation,+- overwriting,++## Default annotation++To be able to merge records with commons fields, at least one side field has to+be annotated as default. This means, if you have two records:++```text+let left = {+  firewall.enabled = true,+  firewall.type = "iptables",+  firewall.open_ports = [21, 80, 443],+} in+let right = {+  firewall.enabled = false,+  server.host.options = "TLS",+} in+left & right+```++Like it is, it's impossible to merge and will throw an unmergeable terms error.+Here, the issue is on the field `firewall.enabled`. What you have to do to fix it+is to annotate this field as default in one of the records:++```text+let left = {+  firewall.enabled | default = true,+  firewall.type = "iptables",+  firewall.open_ports = [21, 80, 443],+} in+let right = {+  firewall.enabled = false,+  server.host.options = "TLS",+} in+left & right+// => {firewall.enabled = false, ...}+```++The default annotation is generaly to give a default value to a record field.+So, this value can be changed afterward. Saying it in a more abstract way,+default indicate a lower priority to a field in case of merging. Saying that,+If both sides have been annotated `default`, the merge is not possible.++## Overwriting++The overwriting is the concept specifying the behaviour of a merge when you+overwrite a field on which depends an other one. This  feature is described in+["#573 [RFC] Merge types and terms syntax proposal"](https://github.com/tweag/nickel/pull/573)

Don't link to pull requests in the documentation. Don't link to merged RFCs either (they are internal documentation).

Well, let me temper that. You can link to them as further reading for the curious reader (links are good!). But they can't be the introduction, and the understanding of the documentation can't depend on reading them.

You are allowed to copy large bits of them though!

francois-caddet

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config+could be:++```text+let+server = import "server.ncl",+firewall = import "firewall.ncl",+in+server & firewall+```++In the simple case where both records do not have common fields, it gives a+record which is the union of both.++If you have to merge records with fields in commons, you will use two new concepts:++- default annotation,+- overwriting,++## Default annotation++To be able to merge records with commons fields, at least one side field has to+be annotated as default. This means, if you have two records:++```text+let left = {+  firewall.enabled = true,+  firewall.type = "iptables",+  firewall.open_ports = [21, 80, 443],+} in+let right = {+  firewall.enabled = false,+  server.host.options = "TLS",+} in+left & right+```++Like it is, it's impossible to merge and will throw an unmergeable terms error.+Here, the issue is on the field `firewall.enabled`. What you have to do to fix it+is to annotate this field as default in one of the records:++```text+let left = {+  firewall.enabled | default = true,+  firewall.type = "iptables",+  firewall.open_ports = [21, 80, 443],+} in+let right = {+  firewall.enabled = false,+  server.host.options = "TLS",+} in+left & right+// => {firewall.enabled = false, ...}+```++The default annotation is generaly to give a default value to a record field.+So, this value can be changed afterward. Saying it in a more abstract way,+default indicate a lower priority to a field in case of merging. Saying that,+If both sides have been annotated `default`, the merge is not possible.++## Overwriting++The overwriting is the concept specifying the behaviour of a merge when you+overwrite a field on which depends an other one. This  feature is described in+["#573 [RFC] Merge types and terms syntax proposal"](https://github.com/tweag/nickel/pull/573)+in more details.++Here, we will simply explain what appen in the following case:+We have a record with some fields annotated `default` and others depending on+these ones. An example could be:++```text+let security = {+    firewall.open_proto.http | default = true,+    firewall.open_proto.https | default = true,+    firewall.open_proto.ftp | default = true,+    firewall.open_ports = []+        @ (if firewall.open_proto.ftp then [21] else [])+	@ (if firewall.open_proto.http then [80] else [])+	@ (if firewall.open_proto.https then [443] else []),+} in // security => {firewall.open_ports = [21, 80, 443]+security & {firewall.open_proto.ftp = false} // => {firewall.open_ports = [80, 443]+```++We then see that dependent fields are updated when you overwrite the fields they+depend on.++## A word about contracts++To simplify, when merging two records, all contracts of both left and right one

Why do you want to “simplify”?

francois-caddet

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config+could be:++```text+let+server = import "server.ncl",+firewall = import "firewall.ncl",+in+server & firewall+```++In the simple case where both records do not have common fields, it gives a+record which is the union of both.++If you have to merge records with fields in commons, you will use two new concepts:++- default annotation,+- overwriting,++## Default annotation++To be able to merge records with commons fields, at least one side field has to+be annotated as default. This means, if you have two records:++```text+let left = {+  firewall.enabled = true,+  firewall.type = "iptables",+  firewall.open_ports = [21, 80, 443],+} in+let right = {+  firewall.enabled = false,+  server.host.options = "TLS",+} in+left & right+```++Like it is, it's impossible to merge and will throw an unmergeable terms error.

Explain why.

francois-caddet

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config+could be:++```text+let+server = import "server.ncl",+firewall = import "firewall.ncl",+in+server & firewall+```++In the simple case where both records do not have common fields, it gives a+record which is the union of both.++If you have to merge records with fields in commons, you will use two new concepts:++- default annotation,+- overwriting,++## Default annotation++To be able to merge records with commons fields, at least one side field has to+be annotated as default. This means, if you have two records:

As I was saying, this isn't the case.

francois-caddet

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config+could be:++```text+let+server = import "server.ncl",+firewall = import "firewall.ncl",+in+server & firewall+```++In the simple case where both records do not have common fields, it gives a+record which is the union of both.++If you have to merge records with fields in commons, you will use two new concepts:++- default annotation,+- overwriting,++## Default annotation++To be able to merge records with commons fields, at least one side field has to+be annotated as default. This means, if you have two records:++```text+let left = {+  firewall.enabled = true,+  firewall.type = "iptables",+  firewall.open_ports = [21, 80, 443],+} in+let right = {+  firewall.enabled = false,+  server.host.options = "TLS",+} in+left & right+```++Like it is, it's impossible to merge and will throw an unmergeable terms error.+Here, the issue is on the field `firewall.enabled`. What you have to do to fix it+is to annotate this field as default in one of the records:++```text+let left = {+  firewall.enabled | default = true,+  firewall.type = "iptables",+  firewall.open_ports = [21, 80, 443],+} in+let right = {+  firewall.enabled = false,+  server.host.options = "TLS",+} in+left & right+// => {firewall.enabled = false, ...}+```++The default annotation is generaly to give a default value to a record field.+So, this value can be changed afterward. Saying it in a more abstract way,+default indicate a lower priority to a field in case of merging. Saying that,+If both sides have been annotated `default`, the merge is not possible.++## Overwriting++The overwriting is the concept specifying the behaviour of a merge when you+overwrite a field on which depends an other one. This  feature is described in+["#573 [RFC] Merge types and terms syntax proposal"](https://github.com/tweag/nickel/pull/573)+in more details.++Here, we will simply explain what appen in the following case:+We have a record with some fields annotated `default` and others depending on+these ones. An example could be:++```text+let security = {+    firewall.open_proto.http | default = true,+    firewall.open_proto.https | default = true,+    firewall.open_proto.ftp | default = true,+    firewall.open_ports = []+        @ (if firewall.open_proto.ftp then [21] else [])+	@ (if firewall.open_proto.http then [80] else [])+	@ (if firewall.open_proto.https then [443] else []),+} in // security => {firewall.open_ports = [21, 80, 443]+security & {firewall.open_proto.ftp = false} // => {firewall.open_ports = [80, 443]+```++We then see that dependent fields are updated when you overwrite the fields they+depend on.++## A word about contracts++To simplify, when merging two records, all contracts of both left and right one+are applied to the resulting one.+For instence:
For instance:
francois-caddet

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config
For instance, having a server config and a firewall config, the network config
francois-caddet

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config+could be:++```text+let+server = import "server.ncl",+firewall = import "firewall.ncl",+in+server & firewall+```++In the simple case where both records do not have common fields, it gives a+record which is the union of both.
In the simple case where records `x` and `y` have no common fields, `x&y` is the union of `x` and `y`.

In addition, give an example.

francois-caddet

comment created time in 4 days

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.++For instence, having a server config and a firewall config, the network config+could be:++```text+let+server = import "server.ncl",+firewall = import "firewall.ncl",+in+server & firewall+```++In the simple case where both records do not have common fields, it gives a+record which is the union of both.++If you have to merge records with fields in commons, you will use two new concepts:++- default annotation,+- overwriting,

Are these two concepts or just one?

There are other merging cases:

  • merging two record fields.
  • merging a field against a field which merely has a contract attached (this emulate mix-ins in particular).
francois-caddet

comment created time in 4 days

PullRequestReviewEvent

Pull request review commenttweag/nickel

Manual/merging

+# Merging records++When you have a big configuration, the way to split it in several files+(e.g.: separated by cathegorie or level of abstraction) is the merging operator.+In nickel this is the `&` operator.

We'll have to change this paragraph, it's a little awkward. And probably doesn't hammer strongly enough why the reader should read on.

francois-caddet

comment created time in 4 days

PullRequestReviewEvent
PullRequestReviewEvent
PullRequestReviewEvent

pull request commenttweag/linear-base

Dupable wars : implementation attempt

Do you see where we could but the Arity thing so? In Data.Unrestricted.Linear.Internal.Arity?

I guess. There is no good place where to put it anyway. The relation with Unrestricted is kind of dubious, though. Maybe Data.Linear.Internal.Arity instead?

tbagrel1

comment created time in 4 days

Pull request review commenttweag/linear-base

Dupable wars : implementation attempt

+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-orphans #-}+{-# LANGUAGE GADTs #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Unrestricted.Internal.Replicator (RepStream (..), Replicator (..)) where++import Prelude.Linear.Internal+import Data.V.Linear.Internal.V (FunN)+import Prelude (Either(..))+import GHC.TypeLits++data RepStream a where+  RepStream :: (s %1 -> a) -> (s %1 -> (s, s)) -> (s %1 -> ()) -> s %1 -> RepStream a++data Replicator a where+  Moved :: a -> Replicator a+  CollectFrom :: RepStream a %1 -> Replicator a

Well, let's wait 9.2 before we decide to revisit this issue, then. It's not worth agonising over.

tbagrel1

comment created time in 4 days

pull request commenttweag/linear-base

Dupable wars : implementation attempt

Should I redefine Arity when writing V.Elim, or should we share it someway?

I guess it's better to use the same.

Either way, it gives some pretty unappealing types, so we will have to give pretty solid documentation to elim.

Also, I was wondering yesterday, could we add a 2 <= n extra constraint on the last Elim n a b f instance instead of creating overlapping ones? What would be the cost of such a measure?

Unfortunately it wouldn't work. GHC only looks at the instance's head (the part to the right of the =>) to determine if instances are overlapping. You could add this constraint, but it would never apply.

tbagrel1

comment created time in 4 days

pull request commenttweag/linear-base

Dupable wars : implementation attempt

I pushed a little something to restore the backward functional dependency.

As I see it, there are two solutions:

  1. Remove the n argument from the Elim type class arguments altogether. It is effectively redundant, after all. This removes all functional dependencies.
  2. We need to tell the compiler how to compute n from f and b. One way to convince oneself that GHC cannot know that n is effectively determined by f is to consider what would happen if we added an instance Elim 42 a b b? We need to make this impossible!

While (1) is definitely the simpler solution, I went for (2) in my commit, because I do like the idea of having the option of saying elim @7 when the function type is under-determined. Rather than having to state the entire function type.

What this implied is making a type family Arity b f which computes the arity of f (assuming its headed by b). And force n ~ Arity b f as a super-class constraint. This way, no instance 42. This suffices to convince GHC.

What I don't know if why I had to add Arity b (a %1 -> b) = 1, this should follow from the definition. But when I remove it, Haskell is very unhappy.

tbagrel1

comment created time in 5 days

push eventtweag/linear-base

Arnaud Spiwack

commit sha 1bf7ce93e9c564f0dd12fc9077d0e44463976702

Proposal: a solution to avoid @n in `elim` when arity is statically discoverable

view details

push time in 5 days

pull request commenttweag/linear-base

Enable doctesting through cabal-docspec

I think this will need no further modification for the time being. Please merge if you're ok with it.

andreabedini

comment created time in 5 days

Pull request review commenttweag/linear-base

Enable doctesting through cabal-docspec

+{ pkgs ? import <nixpkgs> {} }:++pkgs.stdenv.mkDerivation {+  name = "cabal-docspec";++  src = pkgs.fetchurl {+    url = "https://github.com/phadej/cabal-extras/releases/download/cabal-docspec-0.0.0.20211114/cabal-docspec-0.0.0.20211114.xz";

Honestly, considering the constraints, this may be the best solution available to us, since it seems to be working well enough.

andreabedini

comment created time in 5 days

Pull request review commenttweag/linear-base

Enable doctesting through cabal-docspec

+{ pkgs ? import <nixpkgs> {} }:++pkgs.stdenv.mkDerivation {+  name = "cabal-docspec";++  src = pkgs.fetchurl {

Presumably you could use fetchzip here, instead. This would simplify your fetch a bit.

andreabedini

comment created time in 5 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commenttweag/linear-base

Dupable wars : implementation attempt

+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-orphans #-}+{-# LANGUAGE GADTs #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Unrestricted.Internal.Replicator (RepStream (..), Replicator (..)) where++import Prelude.Linear.Internal+import Data.V.Linear.Internal.V (FunN)+import Prelude (Either(..))+import GHC.TypeLits++data RepStream a where+  RepStream :: (s %1 -> a) -> (s %1 -> (s, s)) -> (s %1 -> ()) -> s %1 -> RepStream a++data Replicator a where+  Moved :: a -> Replicator a+  CollectFrom :: RepStream a %1 -> Replicator a

Random thought: should we use unboxed pairs (and unit) in the projections? Maybe it'll be better?

tbagrel1

comment created time in 5 days

PullRequestReviewEvent

Pull request review commenttweag/linear-base

Dupable wars : implementation attempt

 instance (Movable a, Prelude.Semigroup a) => Semigroup (MovableMonoid a) where       combine (Ur x) (Ur y) = x Prelude.<> y  instance (Movable a, Prelude.Monoid a) => Monoid (MovableMonoid a)++instance Consumable (RepStream a) where+  consume (RepStream givex dupsx consumesx sx) = consumesx sx+instance Dupable (RepStream a) where+  dupR (RepStream givex dupsx consumesx sx) = CollectFrom $ RepStream+    (\sx' -> RepStream givex dupsx consumesx sx')+    dupsx+    consumesx+    sx++instance Consumable (Replicator a) where+  consume = \case+    Moved _ -> ()+    CollectFrom stream -> consume stream++instance Dupable (Replicator a) where+  dupR = \case+    Moved x -> Moved (Moved x)+    CollectFrom stream -> CollectFrom Data.<$> dupR stream

Similarly to previously, should be defined near the types, and the type-class instance should just be a wrapper around the primitive functions.

tbagrel1

comment created time in 5 days

Pull request review commenttweag/linear-base

Dupable wars : implementation attempt

 module Data.Unrestricted.Linear     Consumable (..),     Dupable (..),     Movable (..),+    RepStream (..),

I don't think that we should export RepStream's constructor. It's better left as an abstract type.

Also, since we export it, we probably want to give it a better name still (I do recognise that RepStream is significantly better than Streamish :smile: ).

tbagrel1

comment created time in 5 days

more