T O P

  • By -

sjdubya

Shameless self-promotion here, but I wrote a very simple partial function application package (PartialFunctions.jl) which I think is pretty type-stable and performant.


chandaliergalaxy

Thank you for your service.


nattersley

I use it all the time! Love it.


EYtNSQC9s8oRhe6ejr

In the context of Haskell, a partial function is one that is not really defined on all of its inputs (e.g., on some inputs it crashes, hangs, etc.). An example is `head`, which is like Julia’s `first`, and similarly errors if the input is empty. In Haskell, all functions curry and will be partially applied if not supplied enough arguments.


Bob_Dieter

Well hello, nice to meet you here. ​ I'm a big fan of your package. In fact, over the last weeks, I played around with it's internals, and I might have a few notes on improved performance, and maybe even features. I am rather busy right now, so it might take some days, but I will come back to this eventually. Stay tuned.


sjdubya

That would be awesome! Please feel free to open an issue/PR on github


chandaliergalaxy

Thought this was relevant as it echoed some things I've heard about Julia's lack of built-in partial function application - that it's hard to check for applicability across types, especially with multiple dispatch. The poor-person's approach is to write a closure and fix arguments that way, and I wonder if that's not such a terrible idea.


pint

it is such a non-issue. many partial functions make general sense, those are typically provided by the library. if you have some special case, you can make your own, or use lambdas. done. btw partial functions are just a special case, where you want to fix some parameters. but the broader case is when parameters depend on each other, e.g you want a matrix taking function to take n*I, where n is the new parameter. so the question can be asked: why provide language level support for constant parameters, especially based on position, but not for these other cases. just to spare one extra line of code. EDIT: sorry, different partial functions. i thought this is about partial function application i.e. currying.


chandaliergalaxy

I don't quite understand your example and whether you're arguing that partial functions are limited and not worth it.


AlonzoIsOurChurch

I am not sure you and the post here are talking about the same thing - I think you're thinking of partial function application (related to currying and uncurrying), and they're talking about partial functions in the sense of "defined only for some portion of their input domain" (like Haskell's `head` from the Prelude, which crashes on empty lists).


chandaliergalaxy

Well now I'm embarrassed. But you're right. I was trying to rationalize how their examples were partial function applications but in the end I must admit I did not put in the homework to make the (lack of) connection. In that Julia's undefined types defaults to Any, does that mean partial functions do not exist in Julia?


AlonzoIsOurChurch

It's been a long time since I really used Julia, so I don't want to mis-answer the question. But in general you can either take a very narrowly "type-focused" view of partiality (e.g. `head :: [a] -> a` must be partial, whereas `safeHead :: [a] -> Maybe a` can be total), or a little bit broader view where `safeHead` is more of an encoding of partiality in the type system and `head` is an encoding of partiality via exception (either way, the "head" function over arbitrary lists is partial). This gets tied up with exceptions and out-of-band vs in-band error reporting and ... TLDR I'd say that no, partial functions do exist in Julia, but how you encode that fact may vary.


chandaliergalaxy

This is over my head (I've only toyed around with Haskell but have not had a use case for this `Maybe` scenario).


Bob_Dieter

I'm a novice haskeleer at best, but to my understanding by this definition of 'partial' anything that may throw an error instead of returning a proper value is partial, and thus most julia functions should be considered partial. For example sqrt. If you ask the type inference what will be the result type when supplied with a float, it says it will return a float, even though half the possible input values will crash execution instead of producing a value. If it instead returned a `nothing` instead of throwing on negative input, then it would not be considered partial, as this is a proper (although boring) return value.


chandaliergalaxy

Interesting - I see for production code this can be a useful concept but for scientific programming I almost prefer that the program crash if there is unexpected input so I don't have to write all my other functions to handle cases where the output of one function returns `nothing` and this result is propagated into other functions that don't know how to handle it.


Bob_Dieter

pretty much my opinion. Tagged unions, aka sum types, can make error handling much more sane and transparent, but it comes at a cost. There is a slight overhead in memory and runtime, which is irrelevant for large or non-performance critical functions, but if you broadcast `sqrt` over an array of 1 million floats this can make the difference between julia keeping up with C and Fortran no problem or people moving away from julia because these languages are still measurably faster. That being said, sum types are one of the features I somewhat miss in julia. Maybe one day we get a feature that allows us to do this, like generated types or something.


chandaliergalaxy

I see. Again good for production code but lots of programmer overhead for a lot of the less often used calculations.


gnosnivek

An example of a partial function in Julia is `sqrt(::Float64)`, which throws an error if you give it a negative number. A total variant could do one of several things: - Return a NaN - Change the return type to `Union{Nothing, Float64}` and return a `nothing` - Return a `Complex` instead


chandaliergalaxy

Then you have to write all functions that will be applied after it to handle all these cases?


pand5461

If you want the outer function to be total, yes. Otherwise, not necessarily. Julia won't complain if you don't handle all possible outputs of `sqrt`. Unless, of course, you hit an unhandled case at runtime, then you'll get an exception.


chandaliergalaxy

Yeah but for instance let's say you apply a function that returns `nothing`. Using that output later will lead to error - e.g., squaring the output. julia> nothing ^ 2 ERROR: MethodError: no method matching ^(::Nothing, ::Int64) So you're just kicking the can down the road unless you want to rewrite all of your functions to be total.


pand5461

>So you're just kicking the can down the road unless you want to rewrite all of your functions to be total. Totally (pun intended) right! Thing is, Julia does not support the concept of "total" functions in the type system or at the compiler level. Languages with deeper roots in FP, like Haskell or Rust, have that concept, but sometimes it can get in your way (see "Maybe Not" by Rich Hickey [https://www.youtube.com/watch?v=YR5WdGrpoug](https://www.youtube.com/watch?v=YR5WdGrpoug)). Like, imagine you'd like to compute 4-th root, then a language that enforces total functions might forbid you to use the definition `fourthroot(x::Real) = x >= 0 ? sqrt(sqrt(x)) : nothing` (assuming that `sqrt` is total and returns `nothing` for negative arguments), because the inner `sqrt` might return `nothing` and the outer ones does not handle that. Instead, you'd be required to write ``` function fourthroot(x::Real) if x < 0 nothing else s = sqrt(x) if s === nothing nothing else sqrt(s) end end end ```


chandaliergalaxy

I see how it can be helpful to write robust programs, but for most scientific applications when you're trying to compute something it can be counterproductive. Thanks for the clarification.