Ad
  • Custom User Avatar

    Now I see what "tagless" means.

  • Custom User Avatar

    It seems my solution is a hack which translates the terms into ordinary terms in ADT after I read others' solutions. 😂

  • Custom User Avatar

    It seems compareTo is not necessary either. I simplified it a bit. What about this then?

    // The class `Fraction` is translated from the Java one.
    // Feel free to modify the code to be more concise and idiomatic in Kotlin as long as the function `fraction`'s type and the type `Fraction` are preserved.
    
    fun fraction(numerator: Long, denominator: Long): Fraction = TODO()
    
    class Fraction(var numerator: Long, var denominator: Long) {
        override fun hashCode(): Int =
            17 * numerator.hashCode() + denominator.hashCode()
    
        override fun equals(other: Any?): Boolean =
            other is Fraction && numerator * other.denominator == other.numerator * denominator
    
        operator fun plus(other: Fraction): Fraction = TODO()
    
        override fun toString(): String = TODO()
    }
    
    /* Or as an alternative, make Fraction an immutable data class and always reduced:
    data class Fraction(val numerator: Long, val denominator: Long) {
        operator fun plus(other: Fraction): Fraction = TODO()
    
        override fun toString(): String = TODO()
    }
    */
    
    
  • Custom User Avatar

    a function whose only purpose is calling the constructor

    In the val case its purpose can be adapted to also reduce the fraction. I believe this functional option is indeed necessary as I have explained in my first reply. So do you think it would be better if I make this function body a TODO()?

    a class with lots of poorly implemented boilerplate code (a very bad hashCode, and equalsTo which allows the fractions to not be reduced and introduces a vulnerability as tests rely on its implementation which you do not control)

    This is mainly because it's translated from the Java solution setup. hashCode is more a Java convention and I tested that the solution actually works even if you remove it. The equals issue remains in the solution setups in all other languages (C#, Haskell, Java, and Python). This is an inherited problem and if you think we should fix this maybe we should fix all other language versions first.

    a vulnerability as tests rely on its implementation

    And I don't think this is a vulnerability because this equals() works no matter the Fraction is reduced or not. It's just inefficient if the Fraction is always reduced.

    IMO, it'd be much better to change everything to this:

    As you have said, this is your opinion. Immutability in the functional way is also an alternative opinion. See Immutability in Coding Conventions. I think both opinions should be allowed.

    Let me show you the Haskell solution setup for example:

    module Fraction (Fraction, fraction) where
    
    data Fraction = Fraction Integer Integer
    
    fraction :: Integer -> Integer -> Fraction
    fraction = error "TODO: Fraction constructor"
    
    instance Eq Fraction where
      Fraction a b == Fraction c d = a*d == b*c
    
    instance Show Fraction where
      show = error "TODO: show minimal representation"
    
    instance Num Fraction where
      (+) = error "TODO: add fractions"
      (*) = undefined
      negate = undefined
      abs = undefined
      signum = undefined
      fromInteger = undefined
    

    As you can see, there is a fraction function and instance Eq Fraction also allows the fraction to not be reduced.

  • Custom User Avatar

    True. I had wasted a lot of time thinking what went wrong until I saw your comment. By convention, the minus sign is never placed in the denominator in a reduced fraction. I think this should be an issue rather than a suggestion.

  • Custom User Avatar

    But the initial solution is a class, not data class, so all this "if I implement, if I make" talk is meaningless. Either declare Fraction a data class to do what you say and justify such design, or get rid of that function and let users do whatever they want in their code.

    It's true and that's why I added the following comment to the code:

    // The class `Fraction` is translated from the Java one.
    // Feel free to modify the code to be more idiomatic in Kotlin as long as the function `fraction` and the type `Fraction` passes the tests.
    

    Or a possible fixed and clearer version:

    // The class `Fraction` is translated from the Java one.
    // Feel free to modify the code to be more concise and idiomatic in Kotlin as long as the function `fraction`'s type and the type `Fraction` are preserved.
    

    Your invoke() solution does work but IDEA shows the following warning:

    Private primary constructor is exposed via the generated 'copy()' method of a 'data' class.
    

    Besides, doing this is more tricky and less idiomatic, and it requires the coder to be more familiar with the Kotlin language and might make this Kata more difficult than necessary. I don't think this is what this Kata is intended for and there is a Tricky Kotlin collection specially intended for this.

    Anyway, from what I understand, you are trying the make the solution setup simpler to understand, while I am trying the make the solution setup more cumbersome but it allows both beginner (those whose write Java-style Kotlin) and experienced Kotlin coders to work out their own solutions, both being reasonably difficult, and the latter one being more idiomatic. Both have their merits. If you insist on yours, we can make it so. Or we can decide by which one gets more votes.

  • Custom User Avatar

    See my solution and the Haskell version. If I implement the Fraction class to represent only reduced fractions whose both field are immutable vals, and I also want to make the class a data class to eliminate the need to write hashCode and equals, this is necessary. This is more idiomatic as Kotlin promotes immutability and some other practical functional ideas. From the Haskell perspective, a data constructor is only directly called when its fields are explicitly given, and constructing data through other conversions should be defined with functions with explicit names, Kotlin is similar in this way. For example, to create a BigInteger from a s: String, s.toBigInteger() is preferred over BigInteger(s).

  • Custom User Avatar
  • Custom User Avatar

    This comment is hidden because it contains spoiler information about the solution

  • Custom User Avatar

    This comment is hidden because it contains spoiler information about the solution

  • Custom User Avatar

    I have thought of a quicker algorithm but there are so many cases to consider and they are realy cumbersome! I can never think of all of them without the test cases. This is basically test-driven programming.

  • Custom User Avatar

    Thanks. I do think "Modular operations" is overranked. It takes sometime to get familiar with Fin and then work with its tricky definition, but the overall solution is usually not long: mine is about 40 lines and it's a lot simpler if you know the mod function as in andersk's solution. So I'd say it should be 2 kyu, for someone who knows mod maybe it should even be 3 kyu. The rank for "String formatting" is about right, it takes time to get used to defining functions that work on types and come with up that idea for the Kata as a learner.

  • Custom User Avatar

    I am currently learning Agda. I completed the 1 kyu Kata "Modular operations on Finite data type" and then started to try this Kata becasue I haven't learned Cubical Agda but I learned about this in our math lessons so I thought this would be easier to start with. However, it took me a lot more time than expected.

    I defined the Cantor pairing function and its inverse in the primitive with only addition, substraction, and recursion. Then proving the bijection using well-founded induction is a lot trouble and took a lot of time. I thought maybe I myself made the problem more complicated than it should be in this way, but I took a rough look at some other solutions and they all turned out to be just as lengthy.

    So just a suggestion: maybe this Kata should be ranked 1 kyu instead of 2 kyu to be less misleading, considering that I spent much more time to complete this than a 1 kyu Kata, that all the solutions I see seem to quite lengthy, and that the total times this has been completed is actually less than 3 of the 4 currently existing 1 kyu Agda Katas. Or possibly in the other way, maybe "Modular operations on Finite data type" should be ranked down since I see some really short solutions there.

    And a tip for those who think this should be easy like I did: it is not, especially if it's your first time to use well-founded induction! 😭

  • Custom User Avatar

    I'm currently learning functional programming and type theory and this has been a really great Kata to introduce me to the concept.

    I took some time to add a Scala Translation. Please review.

  • Custom User Avatar

    This comment is hidden because it contains spoiler information about the solution