Skip to content

Refined types#1118

Merged
raulraja merged 29 commits into
masterfrom
cotel-refined-types
Dec 19, 2018
Merged

Refined types#1118
raulraja merged 29 commits into
masterfrom
cotel-refined-types

Conversation

@Cotel
Copy link
Copy Markdown
Member

@Cotel Cotel commented Nov 13, 2018

Close #1099

The goal of this PR is to provide a Refinement<F, A> typeclass for creating refined types and also offering a basic set of them.

@Cotel Cotel self-assigned this Nov 13, 2018
@Cotel Cotel requested review from pakoito and raulraja November 13, 2018 07:09

override fun A.refinement(): Boolean = this != 0

fun A.nonZero(): Kind<F, A> = refine(this)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I add a method fun nonZero(a: A): Kind<F, A> = a.nonZero() the IDE tells me its signature clashes with this method 🤔

@Cotel Cotel force-pushed the cotel-refined-types branch from 31e2145 to f4d4ca0 Compare November 13, 2018 07:59
@Cotel Cotel changed the title Introducing Refinement typeclass and NonZero refined type example Refined types Nov 13, 2018
@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Nov 13, 2018

I'm trying to create the same refining mechanism as in https://github.com/fthomas/refined but I don't think I'm doing it right or that if that will work for Kotlin 🤔

I've defined interface GreaterEqual<F, A> : Not<F, A, Less<F, A>> and it needs an overriden Refinement which Not will use to negate the refinement. The problem is that, as you can see in GreaterEqualTest you need to pass a Less instance when doing x.greaterEqual() and I there is no place where I can get that instance.

Besides, GreaterEqual in the Scala's refined library is just a typealias and I was expecting to do the same but it seems that is not possible. Maybe it is even better to define this refined type from 0 like Less instead of extending Not.


"Can create GreaterEqual for every number greater or equal than min defined by instance" {
forAll(GreaterEqualGen(min)) { x: Int ->
x.greaterEqual(LESS(min)).isValid
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to have a function with the same signature as less(Ord<Int>, max: Int) instead of passing a Less instance 🤷‍♂️

@pakoito
Copy link
Copy Markdown
Member

pakoito commented Nov 13, 2018

We wanted to do away with arrow-validation soon. Do you mind keeping this open for a few more days while we decide?

@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Nov 13, 2018

Sure there is no hurry 👌

@raulraja
Copy link
Copy Markdown
Member

Arrow validation has to change from the current use of Either to be a validation library similar in scope to refined. I think this code can be merged in as is as we evolve and deprecate other functions in Arrow Validation which for now can just be ignored for this development.

@Cotel Cotel force-pushed the cotel-refined-types branch from 8af06ef to 04049cb Compare November 19, 2018 08:46
@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Nov 19, 2018

I've been trying to implement Modulo and Positive but I'm having some troubles.

For Modulo I need to have access to the Int.rem(x: Int): Int operation but as I'm working with Modulo<F, A: Number> Number has not access to this operation. I would need A to be a concrete type but I would have to write lots of instances (ValidatedModuloInt, EitherModuloInt, ValidatedModuloFloat, EitherModuloFloat...)

For Positive I can define it like interface Positive<F, A : Number> : Greater<F, A> and then override the fun min(): A function from Greater to always return 0. But it depends on the concrete type again which 0 to return. In Scala's refined library they make use of _0 from shapeless but I cannot do something similar in Kotlin or at least I don't know how to do it.

For the moment I think I will stick to the refined types defined in https://github.com/gcanti/newtype-ts which I think I can port them all even though they are very few and silly. Besides, I don't like the 101.greaterEqual(Less(100)) which I told in my previous comments so I will remove the Not refined type and remake GreaterEqual and LessEqual to be more like their non-Equal types. Once I have them implemented we can work from there.

Tell me your opinions pls.

@raulraja
Copy link
Copy Markdown
Member

Sounds like a good path forward, let me know when ready for review.

@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Nov 26, 2018

All the refined types (besides their Integer specific version) from fp-ts are implemented now. This is ready for review.

Which should be our next step? Should I write some documentation on the usage and the definition of custom refined types or should I try to implement more types from the scala library? I would like to stay in this branch if there is no hurry in finishing this 😄

Copy link
Copy Markdown
Member

@raulraja raulraja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Cotel This looks great, outstanding work 👏 Let's add the kdocs with runnable examples in all public api's and this is good to merge. See the AQL or Bracket Kdoc for an example.


override fun B.refinement(): Boolean = REF().run { !refinement() }

fun B.not(): Kind<F, B> = refine(this)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make these operator?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean infix functions? I can overload the not operator which I suppose it would be ! but I cannot overload greater as an operator 🤔


override fun A.refinement(): Boolean = isGreaterThan(ORD(), this, min())

fun A.greater(): Kind<F, A> = refine(this)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we make these operator?


interface NonZero<F, A : Number> : Refinement<F, A> {

override fun A.refinement(): Boolean = isNonZero(this)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these are lawful type classes. We should consider including the laws of each one of them or just for Refinement for now.
That will give users a framework to test their new refined rules that are composed with multiple refinements. No need to be in this PR but would be great to have.

@Cotel Cotel force-pushed the cotel-refined-types branch from 59e727c to 215df8a Compare November 29, 2018 07:29
@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Nov 29, 2018

I'm trying to run dokka but it tells me that import arrow.validation.xxx <- unresolved reference: validation for every refined type 🤔

@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Dec 3, 2018

I still don't know whats going on with dokka/ank. I have tried to compile the arrow-validation project in the arrow-docs project but then ank won't parse a single file. Could anyone look into this? The branch is almost finished except for the docs.

@1Jajen1
Copy link
Copy Markdown
Member

1Jajen1 commented Dec 3, 2018

Ank fails because the import is not correct. It should be import arrow.validation.refinedTypes.numeric.validated.nonZero.nonZero (or import arrow.validation.refinedTypes.numeric.validated.nonZero.*) instead. A good way to test snippets like this is to create a small class (top level or something to not implicitly import anything) and then actually try to compile and run the code in the snippets.

@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Dec 11, 2018

Soo I've been trying to fix this for some days now but I have no idea of what to do. The only way I can fix the imports is by including arrow-validation as a dependency for arrow-docs but then Ank won't run on a single file. It fails on the first document it needs to generate. Any ideas ?

@raulraja
Copy link
Copy Markdown
Member

@Cotel Can we see the output and command you are using?

@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Dec 12, 2018

I'm doing the same the CI is doing (clean build dokka :arrow-docs:runAnk)

And its failing with this

> Task :arrow-docs:runAnk
�[35m      :::     ::::    ::: :::    :::
    :+: :+:   :+:+:   :+: :+:   :+:
   +:+   +:+  :+:+:+  +:+ +:+  +:+
  +#+     ++: +#+ +:+ +#+ +#++:++
  +#+     +#+ +#+  +#+#+# +#+  +#+
  #+#     #+# #+#   #+#+# #+#   #+#
  ###     ### ###    #### ###    ###�[0m
�[31m[50%] ✗ /home/travis/build/arrow-kt/arrow/modules/docs/arrow-docs/build/site/docs/effects/effect/README.md [1 of 2]�[0m
Exception in thread "main" 

"""
import arrow.reflect.*
import arrow.effects.typeclasses.*

TypeClass(Effect::class).dtMarkdownList()
"""

�[31mjava.lang.ExceptionInInitializerError
	at arrow.reflect.TypeClassKt.extensions(TypeClass.kt:17)
	at arrow.reflect.TypeClassKt.supportedDataTypes(TypeClass.kt:41)
	at arrow.reflect.ExtensionsHelperKt.dtMarkdownList(ExtensionsHelper.kt:31)
Caused by: java.lang.ClassNotFoundException: arrow.core.Eitherrrow.data.NonEmptyList
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:264)
	at arrow.reflect.TypeClassExtensionKt.unKind(TypeClassExtension.kt:57)
	at arrow.reflect.TypeClassExtensionKt.asClassTypeExtension(TypeClassExtension.kt:25)
	at arrow.reflect.TypeClassExtensionKt.<clinit>(TypeClassExtension.kt:51)
	at arrow.reflect.TypeClassKt.extensions(TypeClass.kt:17)
	at arrow.reflect.TypeClassKt.supportedDataTypes(TypeClass.kt:41)
	at arrow.reflect.ExtensionsHelperKt.dtMarkdownList(ExtensionsHelper.kt:31)
	at Line_1.<init>(Unknown Source)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at org.jetbrains.kotlin.cli.common.repl.GenericReplEvaluator.eval(GenericReplEvaluator.kt:95)
	at org.jetbrains.kotlin.cli.common.repl.GenericReplCompilingEvaluator.eval(GenericReplCompilingEvaluator.kt:78)
	at org.jetbrains.kotlin.cli.common.repl.GenericReplCompilingEvaluator.compileAndEval(GenericReplCompilingEvaluator.kt:53)
	at org.jetbrains.kotlin.cli.common.repl.ReplAtomicEvalAction$DefaultImpls.compileAndEval$default(ReplApi.kt:168)
	at org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase.compileAndEval(KotlinJsr223JvmScriptEngineBase.kt:61)
	at org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase.eval(KotlinJsr223JvmScriptEngineBase.kt:31)
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
	at arrow.ank.InterpreterKt$monadDeferInterpreter$1$compileCode$$inlined$run$lambda$1$1.invokeSuspend(interpreter.kt:142)
	at arrow.ank.InterpreterKt$monadDeferInterpreter$1$compileCode$$inlined$run$lambda$1$1.invoke(interpreter.kt)
	at arrow.typeclasses.Monad$binding$wrapReturn$1.invokeSuspend(Monad.kt:56)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:127)
	at arrow.typeclasses.Monad$DefaultImpls.binding(Monad.kt:57)
	at arrow.typeclasses.MonadError$DefaultImpls.binding(MonadError.kt)
	at arrow.typeclasses.MonadThrow$DefaultImpls.binding(MonadError.kt)
	at arrow.effects.typeclasses.MonadDefer$DefaultImpls.binding(MonadDefer.kt)
	at arrow.effects.instances.IOMonadDeferInstance$DefaultImpls.binding(io.kt)
	at arrow.effects.instances.io.monadDefer.IOMonadDeferInstanceKt$monadDefer$1.binding(IOMonadDeferInstance.kt:83)
	at arrow.typeclasses.MonadContinuation.binding(MonadContinuations.kt)
	at arrow.ank.InterpreterKt$monadDeferInterpreter$1$compileCode$$inlined$run$lambda$1.invokeSuspend(interpreter.kt:133)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
	at arrow.typeclasses.MonadContinuation$bind$$inlined$suspendCoroutineUninterceptedOrReturn$lambda$1.invoke(MonadContinuations.kt:61)
	at arrow.typeclasses.MonadContinuation$bind$$inlined$suspendCoroutineUninterceptedOrReturn$lambda$1.invoke(MonadContinuations.kt:15)
	at arrow.effects.IO$flatMap$1.invoke(IO.kt:89)
	at arrow.effects.IO$flatMap$1.invoke(IO.kt:34)
	at arrow.effects.IORunLoop.step(IORunLoop.kt:120)
	at arrow.effects.IO.unsafeRunTimed(IO.kt:124)
	at arrow.effects.IO.unsafeRunSync(IO.kt:121)
	at arrow.ank.main.main(main.kt:14)�[0m

> Task :arrow-docs:runAnk FAILED

@raulraja
Copy link
Copy Markdown
Member

I believe @nomisRev fixed a very similar issue on his branch recently. @nomisRev do you know if your changes made it to master? @Cotel is seeing the same issue you had with dtMarkDownList

@raulraja
Copy link
Copy Markdown
Member

@Cotel let's get this merged in once CI passes and continue with more granular features. More action and contributions are coming to arrow-validated and we should make sure all this great work is considered. Thoughts?

@Cotel
Copy link
Copy Markdown
Member Author

Cotel commented Dec 19, 2018

I think this is a good start. We can merge and continue from this point 👍

@raulraja raulraja merged commit 2a8b575 into master Dec 19, 2018
@raulraja raulraja deleted the cotel-refined-types branch December 19, 2018 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants