2 kyu

What happened to good old if-statements?

Description
Loading description...
Functional Programming
View
AllIssues3QuestionsSuggestions3Show Resolved
  • Please sign in or sign up to leave a comment.
  • staedoix Avatar

    My solution passes sample tests, but fails to compile on submission

    test/ImperativeSpec.hs:216:1: error:
        • Uninferrable type variable k10 in
          the type synonym right-hand side:
          forall v st (s :: k10).
          HasValue st v b =>
          Var st a -> v -> Imperative @{k10} @{k10} st s s ()
        • In the type declaration for ‘Op’
        |
    216 | type Op a b = forall v st s. HasValue st v b => Var st a -> v -> Imperative st s s ()
        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    Idk, it looks like an internal problem

    • staedoix Avatar

      Seems like the original solution was automaticly ported to a new GHC version, and that broke it. The code compiles fine with NoPolyKinds language extension set, so i propose enabling it

    • mrum Avatar

      Thanks for the suggestion. I have added NoPolyKinds to the initial solution. I didn't need it in my solution, but haven't investigated where exactly the issue comes from, yet.

      Issue marked resolved by mrum 10 months ago
  • Expurple Avatar

    In case anyone is interested in creating another follow-up imperative kata, it would be cool to require correct early returns:

    testReturnFromBlock :: String
    testReturnFromBlock = def do
      if' 1 (==) 1 do
        early <- var "correct early return"
        return early
      late <- var "incorrect late return"
      return late
    
  • Expurple Avatar

    Just an observation: default signatures like

    var :: a -> Imperative st s s (Var st a)
    while :: (HasValue st v a, HasValue st w b) => v -> (a -> b -> Bool) -> w -> Imperative st s t r -> Imperative st s s ()
    

    return an Imperative … s s …, which allows putting these statements between if', elif' and else':

    -- Note how far apart are if' and else'.
    example :: String
    example = def do
        result <- var "value doesn't matter"
        if' 0 (==) 0 do
            result .= "body doesn't matter"
        varBetween <- var "value doesn't matter"
        while 0 (/=) 0 do
            result .= "body doesn't matter"
        else' do
            result .= "body doesn't matter"
        return result
    

    I'd prefer if this was illegal and the types of non-branching statements were restricted to Imperative … s NothingYet …, given the kata's focus on type correctness.

    But sure, this can be seen as an intentional language feature. And things will probably stay the same to avoid extra work, breaking existing solutions, etc. You may even add tests that cover this feature.

  • Expurple Avatar

    Submission tests expect HasValue, Imperative and Var to be exported. Consider adding these to the export list in the initial solution template

    • mrum Avatar

      The initial solution exports everything, so that isn't a problem, is it?

    • Expurple Avatar

      Hmmm, you're right. Apparently, I didn't check the initial solution before posting this. I've encountered that error because I added an export list (probably copied it from sample tests' imports). Export lists play nicely with IDEs. For instance, they allow me to "rename symbol" in VSCode. I suggest adding one

      Suggestion marked resolved by Expurple 2 years ago
  • monadius Avatar

    The meaning of the (.=) operation should be clarified (in the initial code comments). It took me some time to realized that it is an assignment operation.

    Also, I suggest to clarify that toString is supposed to work with variables only (otherwise it is necessary to use another GHC extension to type check it).

    • mrum Avatar

      Thanks for the feedback! I added these clarifications to the initial code for .= and toString.

      Suggestion marked resolved by mrum 2 years ago
  • Voile Avatar

    (+=), (-=), (*=), push and toString are not given type definition, which is understandable. However, the tests do not use the most general types for these (Num a, [a] and Show a) because only Integer and String are used in the tests. I don't think this is desirable.

    • mrum Avatar

      Good point. I made sure that each of these operations works with at least two different types (like Int and Float). That should be enough right?

      To make my life easier when testing I also added the (.=) function to implement. With that we also can have a nice isEven program, which I added aswell.

      Last change is to the export/import list. Now everything is exported/imported by default, so that I don't have to touch these anymore if I decide to change an export again.

      (These changes invalidated your solution)

    • Voile Avatar

      If you choose to import everything in user solution, it should also be qualified so things defined in user solution will not collide with things defined in the tests.

    • mrum Avatar

      Ah yes you are right. I changed the imports to back to being explicit.

    • Voile Avatar

      ;-)

      Issue marked resolved by Voile 3 years ago
  • Voile Avatar

    The initial code is kinda getting a bit too funky there (like RebindableSyntax, data TODO reused everywhere, and module Skeleton). I think at least the module name should be the actual one, aka module Imperative.

    Also, do we need to have so many unexported/untested functions and some strangely missing type definitions like def (which you can get by letting the sample tests typecheck anyway)? The former should probably be mentioned, and the latter is kinda unnecessary.

    • mrum Avatar

      The module Skeleton was a mistake on my part. I replaced it with the correct module Imperative.

      I removed the unexported functions from the initial solution, I didn't have a good reason to include them. I also added the missing type definition for def.

      Lasty, probably the biggest change, I removed the TODO types. while/if/else are now fully typed. That means that the typeclass I used also appears in the initial solution.

      The Imperative type is still hidden, because I think it's interesting to figure out how to implement it.

      Does it look better now? I am a bit worried that the variable/value typeclass thing is a bit too much at once.

      Oh and with the removal of the TODO types the initial solution now compiles, which I think is great.

    • Voile Avatar

      Is the new type definition for Imperative correct? It used to be a newtype, now it's data.

    • mrum Avatar

      I used data so that I can define the left-hand side of the type while leaving the right-hand side out. newtype won't let me do that, but for the solution either newtype or data is fine.

    • Voile Avatar

      ;-)

      Issue marked resolved by Voile 3 years ago
  • JohanWiltink Avatar

    I can't even solve your Kata, so I may be way off base here.

    Your if' and elif' look like they take 3 arguments, instead of a single Bool ( which could be expressed between parentheses, JS-style ). Wouldn't passing a Bool be more flexible for conditions, without materially affecting the kata?

    • mrum Avatar

      Thanks for the feedback!

      I use the more complicated signature to make it possible to compare both variables and values using built-in functions like (<). With Bool we wouldn't be able to compare the mutable variables we create. For example:

      prog = def do
        a <- var 1 -- a has type `Var st Int`
        while a (<) 3 do -- here we compare `Var st Int` with `Int`
          ...
      

      Does this make sense? If so I would add a hint for that.

      It looks like this kata tried a different approach in the past, where Vars were instances of Num, but ended up with a version that's similar to what I am using here.

    • JohanWiltink Avatar

      It makes sense. Could if' and friends take a monadic boolean instead of a pure one? You'd have to lift the built-in comparator and the value into the monad, of course, but that can be done polymorphically, so painlessly.

      Thing is, with the current design, a > 3 && a < 10 is never going to work, and a ( monadic or pure ) boolean would offer that flexibility. It's not the essence of the kata, but it would seem very nice to have. :]

    • mrum Avatar

      You'd have to lift the built-in comparator and the value into the monad, of course, but that can be done polymorphically, so painlessly.

      I can image this being done by redefining the operators (for example (<) = someLiftFunction P.(<)), which I would like to avoid. How can it be done more painlessly?

      Maybe my approach is already more complicated than it needs to be. For example while someVar (< 0) is a bit simpler because it only accepts a Var on the left side.

      The meat of the kata should be defining mutable variabes and the if/else control flow, while keeping some kind of clean python-like syntax.

      Let me know if you feel like there is anything else in the kata that makes it more convoluted than necessary.

    • JohanWiltink Avatar

      It would look something like while (liftM2 (<) a (pure 3)) or while ((<) <$> a <*> pure 3), which of course looks nothing like Python. If you want to avoid someLiftFunction (<), the necessary intelligence will have to be built in to while, which is not the most modular or flexible design, but looks most like Python.