implicit conversions with user defined types? - type-conversion

With the following code:
import Base.convert
abstract type MySequential end
struct MyFiniteSequence{T} <: MySequential
vec::NTuple{N,T} where N
end
Base.convert(MyFiniteSequence, r) = MyFiniteSequence{typeof(r)}((r,))
Now try:
julia> convert(MyFiniteSequence, 1)
MyFiniteSequence{Int64}((1,))
So far so good. Now try to do an implict conversion:
julia> MyFiniteSequence(1)
ERROR: MethodError: no method matching MyFiniteSequence(::Int64)
Closest candidates are:
MyFiniteSequence(::Tuple{Vararg{T,N}} where N) where T at REPL[2]:2
Stacktrace:
[1] top-level scope at REPL[5]:1
I think there is a problem because of the {T, N} annotations, but am unsure how the syntax of convert needs to be changed. Is there a way to define convert to get the implicit conversion from Int to struct?

I believe implicit constructor calls to convert were removed in the transition from julia 0.7 to 1.0. But you can just define a constructor that calls convert if that's what you want:
julia> MyFiniteSequence(x) = Base.convert(MyFiniteSequence, x)
MyFiniteSequence
julia> MyFiniteSequence(1)
MyFiniteSequence{Int64}((1,))

Related

How to decorate function taking a named tuple as an argument?

Is there a cleaner way specify numba signature for a function that has NamedUniTuple as an argument? I came up with following code, but len(A._fields) looks like a hack. In the real life I have tuples with different number of fields, counting them to specify a literal is error-prone. The constructor knows about _fields, so why the tuple's length cannot be computed internally?
I could not find anything in the documentation, maybe NamedUniTuple is not public?
from typing import NamedTuple
import numba as nb
class A(NamedTuple):
a: float
b: float
#nb.njit(nb.float64(nb.types.NamedUniTuple(nb.float64, len(A._fields), A)))
def f(x: A) -> float:
return x.a + x.b
x = A(1.0, 2.0)
f(x)

Function composition results in missing argument list for method first

Suppose we have the following functions:
def first(a: A): B = ???
def second(b: B): C = ???
I would like to compose them using andThen, but the following code:
(first andThen second)(a)
results in:
<console>:14: error: missing argument list for method first
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `first _` or `first(_)` instead of `first`.
(first andThen second) (1)
andThen() is a method on a Function. As you've defined it, first() is a method (def), not a function. Those are different entities.
You could define it as a function...
val first : A => B = (a:A) => ???
... or you can use eta expansion to promote it to function status.
(first _ andThen second)
Word has it that Scala 3 will offer a more transparent eta expansion mechanism so that the distinction between method and function won't be quite as cumbersome.

Scala currying example in the tutorial is confusing me

I'm reading a bit about Scala currying here and I don't understand this example very much:
def foldLeft[B](z: B)(op: (B, A) => B): B
What is the [B] in square brackets? Why is it in brackets? The B after the colon is the return type right? What is the type?
It looks like this method has 2 parameter lists: one with a parameter named z and one with a parameter named op which is a function.
op looks like it takes a function (B, A) => B). What does the right side mean? It returns B?
And this is apparently how it is used:
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val res = numbers.foldLeft(0)((m, n) => m + n)
print(res) // 55
What is going on? Why wasn't the [B] needed when called?
In Scala documentation that A type (sometimes A1) is often the placeholder for a collection's element type. So if you have...
List('c','q','y').foldLeft( //....etc.
...then A becomes the reference for Char because the list is a List[Char].
The B is a placeholder for a 2nd type that foldLeft will have to deal with. Specifically it is the type of the 1st parameter as well as the type of the foldLeft result. Most of the time you actually don't need to specify it when foldLeft is invoked because the compiler will infer it. So, yeah, it could have been...
numbers.foldLeft[Int](0)((m, n) => m + n)
...but why bother? The compiler knows it's an Int and so does anyone reading it (anyone who knows Scala).
(B, A) => B is a function that takes 2 parameters, one of type B and one of type A, and produces a result of type B.
What is the [B] in square brackets?
A type parameter (that's also known as "generics" if you've seen something like Java before)
Why is it in brackets?
Because type parameters are written in brackets, that's just Scala syntax. Java, C#, Rust, C++ use angle brackets < > for similar purposes, but since arrays in Scala are accessed as arr(idx), and (unlike Haskell or Python) Scala does not use [ ... ] for list comprehensions, the square brackets could be used for type parameters, and there was no need for angular brackets (those are more difficult to parse anyway, especially in a language which allows almost arbitrary names for infix and postfix operators).
The B after the colon is the return type right?
Right.
What is the type?
Ditto. The return type is B.
It looks like this method has 2 parameter lists: one with a parameter named z and one with a parameter named op which is a function.
This method has a type parameter B and two argument lists for value arguments, correct. This is done to simplify the type inference: the type B can be inferred from the first argument z, so it does not have to be repeated when writing down the lambda-expression for op. This wouldn't work if z and op were in the same argument list.
op looks like it takes a function (B, A) => B.
The type of the argument op is (B, A) => B, that is, Function2[B, A, B], a function that takes a B and an A and returns a B.
What does the right side mean? It returns B?
Yes.
What is going on?
m acts as accumulator, n is the current element of the list. The fold starts with integer value 0, and then accumulates from left to right, adding up all numbers. Instead of (m, n) => m + n, you could have written _ + _.
Why wasn't the [B] needed when called?
It was inferred from the type of z. There are many other cases where the generic type cannot be inferred automatically, then you would have to specify the return type explicitly by passing it as an argument in the square brackets.
This is what is called polymorphism. The function can work on multiple types and you sometimes want to give a parameter of what type will be worked with. Basically the B is a type parameter and can either be given explicitly as a type, which would be Int and then it should be given in square brackets or implicitly in parentheses like you did with the 0. Read about polymorphism here Scala polymorphism

Workaround for chained indexing Scala compiler error?

Chained indexing in ND4S and Breeze produces a Scala compiler error:
var m = Nd4j.create(2, 3)
var a = m(0, ->)
var b = a(0)
var c = m(0, ->)(0)
The first two work but the third produces:
not enough arguments for method apply: (implicit ev: org.nd4s.NDArrayEvidence[org.nd4j.linalg.api.ndarray.INDArray,B], implicit ev2: scala.reflect.Manifest[B])org.nd4j.linalg.api.ndarray.INDArray in class RichINDArray.
I tried with (0, 0) instead of (0) since it is still 2d, but that didn't matter. And many variations of extra parenthesis.
Is this the Scala parser thinking it is some other construct or a bug in ND4S? Is there some workaround syntax that can make chaining work?
EDIT:
Under Breeze:
var m = DenseMatrix.zeros[Int](5,5)
var a = m(0, ::)
var b = a(0)
var c = m(0, ::)(0)
a and b work but c produces this compiler error:
type mismatch; found : Int(0) required: breeze.linalg.support.CanSlice2[breeze.linalg.DenseMatrix[Int],Int,collection.immutable.::.type,?]
Maybe this is parsing as curried function calls, or a macro expands in a way that doesn't work here. And while I guess a person never needs to chain indexing, since m(0, 0) works above and any indexing can be collapsed, is there some solution for the general case of tricking Scala in these situations?
Also, chaining works with multidimensional arrays:
var n = Array.ofDim[Double](2, 3)
var x = n(0)(0)
Both methods m(0, ->) and m(0, ::) take a second implicit parameter list. Thus chaining the calls is interpreted by Scala as trying to provide this implicit parameter list explicitly. That's why it reports the argument number mismatch and the argument type mismatch.
Try using (m(0, ->))(0) or m(0, ->).apply(0) (or the Breeze analogue). This forces Scala to provide the implicit parameter list implicitly, and your second call then becomes a normal function call as you had intended.

Scala Function.tupled and Function.untupled equivalent for variable arity, or, calling variable arity function with tuple

I was trying to do some stuff last night around accepting and calling a generic function (i.e. the type is known at the call site, but potentially varies across call sites, so the definition should be generic across arities).
For example, suppose I have a function f: (A, B, C, ...) => Z. (There are actually many such fs, which I do not know in advance, and so I cannot fix the types nor count of A, B, C, ..., Z.)
I'm trying to achieve the following.
How do I call f generically with an instance of (A, B, C, ...)? If the signature of f were known in advance, then I could do something involving Function.tupled f or equivalent.
How do I define another function or method (for example, some object's apply method) with the same signature as f? That is to say, how do I define a g for which g(a, b, c, ...) type checks if and only if f(a, b, c, ...) type checks? I was looking into Shapeless's HList for this. From what I can tell so far, HList at least solves the "representing an arbitrary arity args list" issue, and also, Shapeless would solve the conversion to and from tuple issue. However, I'm still not sure I understand how this would fit in with a function of generic arity, if at all.
How do I define another function or method with a related type signature to f? The biggest example that comes to mind now is some h: (A, B, C, ...) => SomeErrorThing[Z] \/ Z.
I remember watching a conference presentation on Shapeless some time ago. While the presenter did not explicitly demonstrate these things, what they did demonstrate (various techniques around abstracting/genericizing tuples vs HLists) would lead me to believe that similar things as the above are possible with the same tools.
Thanks in advance!
Yes, Shapeless can absolutely help you here. Suppose for example that we want to take a function of arbitrary arity and turn it into a function of the same arity but with the return type wrapped in Option (I think this will hit all three points of your question).
To keep things simple I'll just say the Option is always Some. This takes a pretty dense four lines:
import shapeless._, ops.function._
def wrap[F, I <: HList, O](f: F)(implicit
ftp: FnToProduct.Aux[F, I => O],
ffp: FnFromProduct[I => Option[O]]
): ffp.Out = ffp(i => Some(ftp(f)(i)))
We can show that it works:
scala> wrap((i: Int) => i + 1)
res0: Int => Option[Int] = <function1>
scala> wrap((i: Int, s: String, t: String) => (s * i) + t)
res1: (Int, String, String) => Option[String] = <function3>
scala> res1(3, "foo", "bar")
res2: Option[String] = Some(foofoofoobar)
Note the appropriate static return types. Now for how it works:
The FnToProduct type class provides evidence that some type F is a FunctionN (for some N) that can be converted into a function from some HList to the original output type. The HList function (a Function1, to be precise) is the Out type member of the instance, or the second type parameter of the FnToProduct.Aux helper.
FnFromProduct does the reverse—it's evidence that some F is a Function1 from an HList to some output type that can be converted into a function of some arity to that output type.
In our wrap method, we use FnToProduct.Aux to constrain the Out of the FnToProduct instance for F in such a way that we can refer to the HList parameter list and the O result type in the type of our FnFromProduct instance. The implementation is then pretty straightforward—we just apply the instances in the appropriate places.
This may all seem very complicated, but once you've worked with this kind of generic programming in Scala for a while it becomes more or less intuitive, and we'd of course be happy to answer more specific questions about your use case.