GithubHelp home page GithubHelp logo

Comments (4)

gallais avatar gallais commented on May 25, 2024 1

Since I stumbled upon this on my own, I don't know whether
it's recommended per se (some input on that front would be
appreciated), but it seems to work.

I would say solution 2 is the idiomatic way to do it. With the solution
number 1 you can still create two heaps in two different contexts
using two different notions of order and then merge them using a
third notion of order.

The only way to guarantee that users cannot possibly use the wrong
notion of order when manipulating your heaps is to add it as a
parameter.

In Haskell it's different because

  1. you cannot parametrise your data structures with an Ord a
  2. it's not that big of a deal given that you are only supposed to
    have one instance per type

from idris2.

edwinb avatar edwinb commented on May 25, 2024

Comment by edwinb
Tuesday Mar 17, 2020 at 21:14 GMT


The error message is bad, but there are definitely two possible results: one from the first Node, one from the second. There's no guarantee they'd be the same.

from idris2.

polendri avatar polendri commented on May 25, 2024

I've run into a similar issue and I've found a couple different solutions, neither of which involve compiler bugs (so I think this issue can be closed).

As edwinb said, there are genuinely two different Ord implementations in scope there. You might think "but it should know that they're both the same", but there's no reason that they must be: one of those Heaps could have been created in another context where the compiler selected a custom Ord elem implementation.

There are two potential solutions to this:

  1. Stop trying to enforce the constraint in the data constructors (where it isn't necessary) and instead add the constraint only to functions which act on Heap.
  2. Making it explicit that every component of a Heap must use the same Ord elem implementation.

#1 is the simplest and I know it's generally recommended as a best practice in the Haskell space. Move the Ord constraint to merge and everything works:

data Heap : Type -> Type where
  Empty : Heap elem
  Node : Int -> elem -> Heap elem -> Heap elem -> Heap elem

-- `Ord` constraint moved from data constructors to function
--       |
--       V
merge : Ord elem => Heap elem -> Heap elem -> Heap elem
merge heap Empty = heap
merge Empty heap = heap
merge h1@(Node _ elem1 left1 right1) h2@(Node _ elem2 left2 right2) =
  if elem1 <= elem2
     then makeT elem1 left1 (merge right1 h2)
     else makeT elem2 left2 (merge h1 right2)
  where
    rank : Heap elem -> Int
    rank Empty = 0
    rank (Node r _ _ _) = r

    makeT : elem -> Heap elem -> Heap elem -> Heap elem
    makeT x left right = if rank left >= rank right
                            then Node (rank right + 1) x left right
                            else Node (rank left + 1) x right left

The alternative might be necessary in certain cases, as it was in mine. In my case, my data type is an invariant for a binary tree data type, and it tracks the minimum/maximum value in the tree. So certain data constructors have an Ordered constraint, and the RHS of the data constructors themselves use functions from that interface (minimum and maximum). As such, the constraint has to be available in the data constructor.

What I found out (by pure "I wonder if this works") is that interface constraints can both be named and they can be used in type signatures, meaning that a type constructor can accept a concrete interface implementation as a parameter. Here's the same example reworked so that Heap has an added (ord : Ord elem) parameter, ensuring that every node in the heap uses the same Ord implementation:

-- Concrete `Ord` implementation as a parameter to the data type, ensuring that every component of
-- a `Heap` uses the _same_ ordering implementation.
--                                   |
--                                   V
data Heap : (elem : Type) -> (ord : Ord elem) -> Type where
  Empty : {ord : Ord elem} -> Heap elem ord
  Node : {ord : Ord elem} -> Int -> elem -> Heap elem ord -> Heap elem ord -> Heap elem ord

-- `Ord` constraint is given a name so that it can used in the `Heap` type constructor.
--       |
--       V
merge : (ord : Ord elem) => Heap elem ord -> Heap elem ord -> Heap elem ord
merge heap Empty = heap
merge Empty heap = heap
merge h1@(Node _ elem1 left1 right1) h2@(Node _ elem2 left2 right2) =
  if elem1 <= elem2
     then makeT elem1 left1 (merge right1 h2)
     else makeT elem2 left2 (merge h1 right2)
  where
    rank : Heap elem ord -> Int
    rank Empty = 0
    rank (Node r _ _ _) = r

    makeT : elem -> Heap elem ord -> Heap elem ord -> Heap elem ord
    makeT x left right = if rank left >= rank right
                            then Node (rank right + 1) x left right
                            else Node (rank left + 1) x right left

-- Define helpers for the data constructors which use type constraints, so that the compiler can
-- automatically search for an implementation when creating instances of `Heap`
empty : (ord : Ord elem) => Heap elem ord
empty = Empty

node : (ord : Ord elem) => Int -> elem -> Heap elem ord -> Heap elem ord -> Heap elem ord
node = Node

example : (ord : Ord String) => Heap String ord
example = node 10 "foo" empty (node 5 "bar" empty empty)

Since I stumbled upon this on my own, I don't know whether it's recommended per se (some input on that front would be appreciated), but it seems to work. Generally I would stick with option #1 unless it's not possible to do so.

from idris2.

gallais avatar gallais commented on May 25, 2024

The error message now mentions the binding places of these two candidates.
Unfortunately the locations are too loose and appear to be the same entire LHS:

Possible correct results:
    conArg (implicitly bound at Issue34.idr:18:26--20:67)
    conArg (implicitly bound at Issue34.idr:18:26--20:67)

from idris2.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.